GeekFactory

int128.hatenablog.com

OpenID認証によるシングルサインオン

App Engine 1.3.4でOpenID認証に対応しました。調べてみると意外と普通にシングルサインオンできたので、使い方と仕組みを説明します。

まず、OpenID認証を要求するURLパターンをweb.xmlに書きます。

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Authentication required url</web-resource-name>
      <url-pattern>/user/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>*</role-name>
    </auth-constraint>
  </security-constraint>

例えば http://xxx.appspot.com/user/hoge にアクセスしようとすると、App Engineは認証が必要なページと判断して下記のURLにリダイレクトします。

そこで、/_ah/login_required に対応するサーブレットを定義します。

  <servlet>
    <description></description>
    <display-name>LoginRequiredServlet</display-name>
    <servlet-name>LoginRequiredServlet</servlet-name>
    <servlet-class>org.hidetake.app.xxx.controller.LoginRequiredServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>LoginRequiredServlet</servlet-name>
    <url-pattern>/_ah/login_required</url-pattern>
  </servlet-mapping>

ログインページにフォワードするサーブレットでOKです。

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException
	{
		String backUrl = request.getParameter("continue");
		if(backUrl == null) {
			response.sendRedirect("/");
			return;
		}
		
		request.setAttribute("backUrl", backUrl);
		request.getRequestDispatcher("/loginRequired.jsp").forward(request, response);
	}

ログインページは気合いを入れて作りましょう。はてなIDとかmixi IDとかアイコンで選べると格好いいですよね。

<form action="/login" method="post">
<fieldset>
<input type="text" name="uid" />
<input type="hidden" name="backUrl" value="${f:h(backUrl)}" />
<input type="submit" />
</fieldset>
</form>

ログインページは下記のURLにIdentifierをPOSTします。

そこで、/login に対応するサーブレットを定義します。

// この例ではSlim3を使ってますが、サーブレットでも同じです
public class LoginController extends Controller
{

	@Override
	public Navigation run() throws Exception
	{
		String backUrl = asString("backUrl");
		String uid = asString("uid");
		if (backUrl == null || uid == null) {
			return redirect("/");
		}

		UserService userService = UserServiceFactory.getUserService();
		User currentUser = userService.getCurrentUser();
		if (currentUser != null) {
			return redirect("/");
		}

		Set<String> attributesRequest = Sets.newHashSet(
				"openid.mode=checkid_immediate",
				"openid.ns=http://specs.openid.net/auth/2.0",
				"openid.return_to=" + backUrl);

		String loginUrl = userService.createLoginURL(
				backUrl,
				request.getServerName(),
				uid,
				attributesRequest);

		return redirect(loginUrl);
	}

}

サーブレットではOpenID認証に必要なURLを生成し、OpenID Providerにリダイレクトします。

OpenID Providerはパスワード認証等で本人確認を行った後、App Engineにリダイレクトします。App Engineは認証ステートをCookieに書いて、本来アクセスしようとしたURLにリダイレクトします。ここで初めて本来アクセスしようとしたページが表示されます。

認証済みのIdentifierを取得するには下記のようにすればOKです。

	public Navigation run() throws Exception
	{
		requestScope("userId", request.getUserPrincipal().getName());
		return forward("index.jsp");
	}

App Engineで認証ステートの管理までやってくれるので便利ですね。アカウント情報管理やアクティベーションの仕組みを汎用化してライブラリにできると面白そう。