PicketLink

PicketLinkはJBossで開発されているセキュリティー管理システムです。
PicketLinkを使えば、フィルタで独自の認証の作り込みをしなくても済むようになります。
複数の使用例がここにあります。
そのなかから、picketlink-http-servlet-integrationの説明をします。

この例では、PicketLinkとサーブレットの認証機能を統合できることを示しています。
例の著作権表示は、次の通りです。

JBoss, Home of Professional Open Source
Copyright 2013, Red Hat, Inc. and/or its affiliates, and individual
contributors by the @authors tag. See the copyright.txt in the 
distribution for a full listing of individual contributors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,  
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

実行環境
PicketLink 2.7.0.Final
WildFly 8.2.0.Final

前提条件
CDI
Servlet 3.0
EJB 3.1

picketlink-http-servlet-integration.war
warファイルの内容は次の通りです。
picketlink-http-servlet-int

/WEB-INF/beans.xml

<!-- Marker file indicating CDI should be enabled -->
<beans xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
      http://java.sun.com/xml/ns/javaee 
      http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>

CDIを有効化するためにこのファイルを作成します。

SecurityInitializer.java

package org.picketlink.quickstarts.http.servlet;

import org.picketlink.idm.IdentityManager;
import org.picketlink.idm.PartitionManager;
import org.picketlink.idm.RelationshipManager;
import org.picketlink.idm.credential.Password;
import org.picketlink.idm.model.basic.Role;
import org.picketlink.idm.model.basic.User;

import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.inject.Inject;

import static org.picketlink.idm.model.basic.BasicModel.grantRole;

/**
 * This startup bean creates a number of default users, groups and roles when the application is started.
 * 
 * @author Pedro Igor
 */
@Singleton
@Startup
public class SecurityInitializer {

    @Inject
    private PartitionManager partitionManager;

    @PostConstruct
    public void create() {
        // Create user john
        User john = new User("john");

        IdentityManager identityManager = this.partitionManager.createIdentityManager();

        identityManager.add(john);
        identityManager.updateCredential(john, new Password("john"));

        Role userRole = new Role("User");

        identityManager.add(userRole);

        // Create application role "adminRole"
        Role adminRole = new Role("Administrator");

        identityManager.add(adminRole);

        RelationshipManager relationshipManager = this.partitionManager.createRelationshipManager();

        // Grant the "userRole" application role to john
        grantRole(relationshipManager, john, userRole);
    }
}

22行目の@Singletonは、このクラスがシングルトンセッションビーンであることを表しています。
23行目に@Startupがあるので、アプリケーションがスタートするときにこのクラスが実行されます。さらに29行目のcreateメソッドに@PostConstructが定義されているので、このメソッドが呼び出されます。
32行目のUserは、PicketLinkで用意されているオブジェクトで、ユーザ情報を扱います。ここでは、ユーザjohnを作成しています。
その後の行で、ユーザjohnの登録と、パスワードの設定をしてます。ここでは、パスワードをjohnとしています。
39行目以降でロールを作成しています。ここでは、UserとAdministratorの2つのロールを作成しています。
51行目でユーザjohnにUserロールを設定しています。

HttpSecurityConfiguration.java

package org.picketlink.quickstarts.http.servlet;

import org.picketlink.event.SecurityConfigurationEvent;

import javax.enterprise.event.Observes;

/**
 * @author Pedro Igor
 */
public class HttpSecurityConfiguration {

    /**
     * <p>Tells PicketLink to listen for all paths of the application. Please, notice that no security policy was defined
     * such as authentication or authorization. We're just enabling the PicketLink Security Filter.</p>
     *
     * @param event
     */
    public void onInit(@Observes SecurityConfigurationEvent event) {
        event.getBuilder()
            .http()
                .allPaths();
    }

}

18行目のonInitメソッドの引数に@Observes SecurityConfigurationEventが指定されています。SecurityConfigurationEventが通知されると、CDIの機能によりこのメソッドが実行されます。
21行目のallPathsメソッドですべてのパスにおいてPicketLinkが使えるようにしています。ここでは、セキュリティ制限の設定を行っていないので注意が必要です(allPathsメソッドのみでは、セキュリティ制限が設定されません)。

AuthenticationServlet.java

package org.picketlink.quickstarts.http.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author Pedro Igor
 */
@WebServlet ("/servlet")
public class AuthenticationServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        if (isLogout(req)) {
            req.logout();
        }

        tryAuthentication(req);

        resp.sendRedirect(req.getContextPath() + "/index.jsp");
    }

    private boolean isLogout(HttpServletRequest req) {
        return req.getUserPrincipal() != null && req.getParameter("logout") != null;
    }

    private void tryAuthentication(HttpServletRequest req) throws ServletException {
        if (req.getUserPrincipal() == null) {
            String username = req.getParameter("username");
            String password = req.getParameter("password");

            if (username != null && password != null) {
                req.login(username, password);
            }
        }
    }
}

13行目に@WebServlet (“/servlet”)があるので、/servletがリクエストされたとき、このサーブレットのserviceメソッドが呼び出されます。
18行目では、認証済みのユーザがlogoutパラメータを送ってきたときにログアウトを実行しています。19行目のlogoutメソッドは、サーブレットのメソッドです。
22行目のtryAuthenticationメソッドでユーザの認証を行っています。37行目のloginメソッドもサーブレットのメソッドを使っています。
24行目で/index.jspへリダイレクトしています。ここでは認証に成功してもしなくてもリダイレクトしています。

/index.jsp

<!-- Plain HTML page that kicks us into the app -->

<html>
<body>
    <%
        if (request.getUserPrincipal() == null) {
    %>
        <form method="post" action="<%= request.getContextPath() %>/servlet">
            Username: <input type="text" name="username"/><br/>
            Password: <input type="password" name="password"/>
            <br/>
            <br/>
            <input type="submit" value="Ok"/>
        </form>
    <%
        } else {
    %>
        Welcome, <%= request.getUserPrincipal().getName() %> !!
        <br/>
        <br/>
        Are you granted with the User Role ? request.isUserInRole("User") == <%= request.isUserInRole("User") %>.
        <br/>
        Are you an Administrator ? request.isUserInRole("Administrator") == <%= request.isUserInRole("Administrator") %>.
        <br/>
        <br/>
        Click here to <a href="<%= request.getContextPath() %>/servlet?logout=true">logout</a>.
    <%
        }
    %>
</body>
</html>

初期表示では認証前なので、6行目のrequest.getUserPrincipal()はnullになり、認証用のフォームが表示されます。
picketlink-http-servlet-for
ユーザ名とパスワードを入力して[Ok]ボタンをクリックすると、AuthenticationServlet.javaが実行され、認証が成功してもしなくてもこのファイルにリダイレクトされます。
ユーザ名とパスワードはSecurityInitializer.javaで設定した通りです(ユーザ名john、パスワードjohn)。
認証に失敗した場合は、request.getUserPrincipal()はnullなので、認証フォームが表示されます。
認証に成功した場合は、メッセージが表示されます。
picketlink-http-servlet-log
SecurityInitializer.javaでユーザjohnにUserロールを設定しているので、21行目のrequest.isUserInRole(“User”)はtrueを返し、23行目のrequest.isUserInRole(“Administrator”)はfalseを返します。
logoutリンクをクリックすると、AuthenticationServlet.javaが実行され、ログアウト後にこのファイルにリダイレクトされます。
ログアウト後は、request.getUserPrincipal()はnullになり、認証用のフォームが表示されます。

追記
2015-07-09 /WEB-INF/beans.xmlの説明を修正しました。