LoginSignup
7
7

More than 5 years have passed since last update.

PlayFramework + pac4jでOAuthを使う ①Twitter認可

Last updated at Posted at 2016-09-15

pac4j

javaで作られた認証・認可ライブラリ
多くのサービスとjavaフレームワークに対応している
比較的簡単に実装できる(と思う)
内部でscribejavaを使っている模様
play-pac4jにはDeadbolt2が統合されており、シームレスにこれを使用できる

Play + pac4jエントリー一覧

PlayFramework + pac4jでOAuthを使う ①Twitter認可
PlayFramework + pac4jでOAuthを使う ②DB連携

本記事のゴール

OAuthを用いてTwitterユーザーを認可する

全コード

実装

build.sbt

libraryDependenciesにplay-pac4j2.5.0とpac4j-oauth1.9.2を追記

libraryDependencies ++= Seq(
  javaJdbc,
  cache,
  javaWs,
  "org.pac4j" % "play-pac4j" % "2.5.0",
  "org.pac4j" % "pac4j-oauth" % "1.9.2"
)

app/modules

DemoHttpActionAdapter

全てのレスポンスを改変できるクラス
今回はhttpステータスコードが401か403の場合、それぞれ特殊なページをレンダリングするようにした
また、401(認証エラー)の場合にはログインセッションを削除しておく
削除しないと値が空のpac4j用クッキーが作られる

DemoHttpActionAdapter.java
package modules;

import org.pac4j.core.context.HttpConstants;
import org.pac4j.core.context.Pac4jConstants;
import org.pac4j.play.PlayWebContext;
import org.pac4j.play.http.DefaultHttpActionAdapter;
import play.mvc.Result;

import static play.mvc.Results.*;

public class DemoHttpActionAdapter extends DefaultHttpActionAdapter {

    @Override
    public Result adapt(int code, PlayWebContext context) {
        if (code == HttpConstants.UNAUTHORIZED) {
            // セッションを削除
            context.getJavaContext().session().remove(Pac4jConstants.SESSION_ID);
            return unauthorized("401 UNAUTHORIZED");
        } else if (code == HttpConstants.FORBIDDEN) {
            return forbidden("403 FORBIDDEN");
        } else {
            return super.adapt(code, context);
        }
    }
}

SecurityModule

pac4jの設定を行うクラス
実際に動作させるときは、twIdをAPI Keyに、twSecretをAPI Secretの文字列に置き換える

SecurityModule.java
package modules;

import com.google.inject.AbstractModule;
import org.pac4j.core.authorization.authorizer.RequireAnyRoleAuthorizer;
import org.pac4j.core.client.Clients;
import org.pac4j.core.config.Config;
import org.pac4j.oauth.client.TwitterClient;
import org.pac4j.play.ApplicationLogoutController;
import org.pac4j.play.CallbackController;
import org.pac4j.play.store.PlayCacheStore;
import org.pac4j.play.store.PlaySessionStore;

import play.Configuration;
import play.Environment;

public class SecurityModule extends AbstractModule {

    private final Configuration configuration;

    public SecurityModule(final Environment environment, final Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    protected void configure() {
        bind(PlaySessionStore.class).to(PlayCacheStore.class);

        // TwitterのAPI KeyとAPI Secretを設定
        TwitterClient twitterClient = new TwitterClient("twId", "twSecret");

        // コールバックURLとクライアントを設定、今回はtwitterのみ
        Clients clients = new Clients("http://localhost:9000/callback", twitterClient);

        Config config = new Config(clients);

        // Authorizer:認証ロジック 複数のAuthorizerをadd可能
        config.addAuthorizer("admin", new RequireAnyRoleAuthorizer<>("ROLE_ADMIN"));

        // ActionAdapterの設定
        config.setHttpActionAdapter(new DemoHttpActionAdapter());

        bind(Config.class).toInstance(config);

        // callback
        CallbackController callbackController = new CallbackController();
        callbackController.setDefaultUrl("/");
        bind(CallbackController.class).toInstance(callbackController);

        // logout
        ApplicationLogoutController logoutController = new ApplicationLogoutController();
        logoutController.setDefaultUrl("/");
        bind(ApplicationLogoutController.class).toInstance(logoutController);
    }
}

app/conf/application.conf

enabled += "modules.SecurityModule"を追記してSecurityModuleを有効化する

application.conf
...

play.modules {
  # By default, Play will load any class called Module that is defined
  # in the root package (the "app" directory), or you can define them
  # explicitly below.
  # If there are any built-in modules that you want to disable, you can list them here.
  #enabled += my.application.Module
  enabled += "modules.SecurityModule"

  # If there are any built-in modules that you want to disable, you can list them here.
  #disabled += ""
}

...

app/view

index.scala.html

トップページ
org.pac4j.core.context.Pac4jConstants.SESSION_ID(この定数の値は"pac4jSessionId")の有無でログイン状態を判定する
未ログインならTwitterLogin用のリンクを表示、ログインしていればLogoutリンクも表示

index.scala.html
@(message: String)

@main("index") {
    @if(org.pac4j.core.util.CommonHelper.isBlank(session.get(org.pac4j.core.context.Pac4jConstants.SESSION_ID))) {
        <a href="@controllers.routes.LoginController.twitterLogin()">Twitter Login</a>
    }else{
        <a href="@controllers.routes.LoginController.twitterLogin()">Twitter Login</a>
        <br />
        <a href="@org.pac4j.play.routes.ApplicationLogoutController.logout()">Logout</a>
    }
}

protectedIndex.scala.html

ログインしていないと見れないページ
プロファイル(OAuthで認可したユーザーの情報)を受け取って表示
定数で引っ張れるログインセッションの値もついでに表示

protectedIndex.scala.html
@(profiles: List[org.pac4j.core.profile.CommonProfile])

@main("テストページ") {
    <h1>protected area</h1>
    <a href="..">Back</a><br />
    <br /><br />
    profiles: @profiles<br />
    @if(org.pac4j.core.util.CommonHelper.isBlank(session.get(org.pac4j.core.context.Pac4jConstants.SESSION_ID))) {
        <font color="red">session NOT exist</font>
    }else{
        <font color="red">@session.get(org.pac4j.core.context.Pac4jConstants.SESSION_ID)</font>
    }
}
※ pac4jのセッションについて

playframework用のログインセッションはplay-pac4jにあるPlayCacheStoreによって作られる
https://github.com/pac4j/play-pac4j/blob/master/src/main/java/org/pac4j/play/store/PlayCacheStore.java
getOrCreateSessionIdメソッド内でUUIDを生成し、定数Pac4jConstants.SESSION_IDをキーにしてセッションにputしている

app/controller/LoginController.java

LoginController.java
package controllers;

import org.pac4j.core.profile.CommonProfile;
import org.pac4j.core.profile.ProfileManager;
import org.pac4j.play.PlayWebContext;
import org.pac4j.play.java.Secure;
import org.pac4j.play.store.PlaySessionStore;
import play.mvc.Controller;
import play.mvc.Result;
import views.html.protectedIndex;

import javax.inject.Inject;
import java.util.List;

public class LoginController extends Controller {

    @Inject
    protected PlaySessionStore playSessionStore;

    private List<CommonProfile> getProfiles() {
        final PlayWebContext context = new PlayWebContext(ctx(), playSessionStore);
        final ProfileManager<CommonProfile> profileManager = new ProfileManager<>(context);
        return profileManager.getAll(true);
    }

    @Secure(clients = "TwitterClient")
    public Result twitterLogin() {
        return ok(protectedIndex.render(getProfiles()));
    }

}

@Inject protected PlaySessionStore playSessionStore;

セッション操作用のインターフェース
中身にはPlayCacheStoreのインスタンスが入る

private List getProfiles()

OAuthで認可したユーザーのプロファイルをコンテキストから取得する処理

@Secure(clients = "TwitterClient")

Secureアノテーションを付けたメソッドはプロテクトされ、認可、認証済みのユーザーでないと実行できなくなる
未認可状態でアクセスすると認可を求められる
clientsにTwitterClientを記述することで、Twitterアカウントの連携を要求する

app/conf/routes

indexとprotectedIndexの設定
pac4j用にcallbackとlogoutを設定


# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# An example controller showing a sample home page
GET     /                           controllers.HomeController.index

#Social
GET     /tw                                 controllers.LoginController.twitterLogin

# pac4j
GET         /callback                         org.pac4j.play.CallbackController.callback()
GET         /logout                           org.pac4j.play.ApplicationLogoutController.logout()

# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)

動作確認

protectedIndexにアクセスしてTwitterアカウントに連携を許可し、以下のような画面になれば成功
testpage.png

Next

取得したプロファイル情報をDBに格納し、突合せによるログインログアウトを実装

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7