LoginSignup
2
2

More than 3 years have passed since last update.

OpenID Connect 1.0 Implicit Flow OPを実装してみる

Last updated at Posted at 2016-05-07

はじめに

Enterprise Identity WGが『OpenID ConnectとSCIMのエンタープライズ利用ガイドライン』と『(同)実装ガイドライン』を公開しました。
https://www.openid.or.jp/news/2016/03/eiwg-guideline.html
非常に読み応えのある内容で、実装ガイドラインにはRubyによる実装例も公開されていました。
https://github.com/openid-foundation-japan/eiwg-guideline-samples

この記事ではJavaによる実装例を紹介します。

実行環境

下記バージョンで動作確認しています。
- Java 8(1.8.0_91)
- Tomcat 8(8.0.33)
- JSR 353: Java API for JSON Processing(1.0.4)
- JSON Web Token for Java and Android(0.6.0)
- FasterXML Jackson(2.7.0)
- Apache Commons Lang(3-3.4)
- Apache Log4j 2(2.5)

実装方針

シンプルにSERVLET/JSPでJavaDBを利用しました。以下のライブラリを利用しました。
- servlet-api.jar
- derby.jar
- commons-lang3-3.4.jar
- jackson-annotations-2.7.0.jar
- jackson-core-2.7.0.jar
- jackson-databind-2.7.0.jar
- javax.json-1.0.4.jar
- jjwt-0.6.0.jar

以下のテーブルを作成します。


create table profile(uid varchar(128) not null primary key, passwd varchar(256) not null, email varchar(256), phone_number varchar(256), name varchar(256), given_name varchar(256), family_name varchar(256), middle_name varchar(256), nickname varchar(256), address varchar(1024));
create table client(client_id varchar(128) not null primary key,  secret varchar(256) not null, scope varchar(256) not null, redirect_uri varchar(256));
create table session(uid varchar(128) not null, access_token varchar(256) not null primary key, issued_in timestamp not null, scope varchar(256) not null,  client_id varchar(256) not null);

ログ出力

ログ出力にはApache Log4j 2ライブラリを利用します。アクセスログ、エラーログはINFO、デバッグログはTRACEとします。
- log4j-api-2.5.jar
- log4j-core-2.5.jar
- log4j-web-2.5.jar

log4j2.json


{
  "configuration": {
    "status": "off",
    "appenders": {
        "File": {"name": "File",
                 "fileName": "logs/myop.log",
                 "PatternLayout": {"pattern": "%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"}
        }
    },
    "loggers": {
        "root": {"level": "info",
                 "AppenderRef": {"ref": "File"}
        }
    }
  }
}

DISCOVERY

/.well-known/openid-configurationにアクセスが来たら以下のファイルを表示します。


openid-configuration.json

/jks_uriにアクセスが来たら以下のファイルを表示します。


jks_uri.json

AUTHORIZE

Implicit Flowの下記パラメータを実装しています。
- response_type
- prompt
- login_hint
- max_age
- client_id
- redirect_uri
- scope
- state
- nonce

access_tokenは以下で生成します。DBのPRIMARY KEYにすることで一意性は保証されます。


access_token = RandomStringUtils.randomAlphanumeric(32);

at_hashは以下で生成します。


md.update(access_token.getBytes());
cipher_byte = md.digest();
byte[] half_cipher_byte = Arrays.copyOf(cipher_byte, (cipher_byte.length / 2));
String at_hash = Base64.getEncoder().withoutPadding().encodeToString(half_cipher_byte);

秘密鍵は以下で読み込みます。


path = context.getRealPath("/WEB-INF/private.der");
File filePrivateKey = new File(path);
FileInputStream fis = new FileInputStream(path);
byte[] encodedPrivateKey = new byte[(int) filePrivateKey.length()];
fis.read(encodedPrivateKey);
fis.close();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);

id_tokenは以下で生成します。


Calendar exp = Calendar.getInstance();
exp.add(Calendar.SECOND, access_token_time);
id_token = Jwts.builder().setHeaderParam("alg", "RS256").setHeaderParam("typ", "JWT").setHeaderParam("kid", kit).setIssuer(issuer).claim("at_hash",at_hash).setSubject(username).setAudience(client_id).claim("nonce",nonce).setSubject(username).setExpiration(exp.getTime()).setIssuedAt(Calendar.getInstance().getTime()).claim("auth_time",String.valueOf(Calendar.getInstance().getTime().getTime()).substring(0,10)).signWith(SignatureAlgorithm.RS256,privateKey).compact();

LOGIN

認証処理のために下記パラメータを実装しています。ログイン成功の場合はシングルサインオンのためにセッションで保持しておきます。
- username
- password

入力されたパスワードはハッシュ化してDBのパスワードと比較します。


byte[] cipher_byte;
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(password.getBytes());
cipher_byte = md.digest();
String sha256_password = Base64.getEncoder().withoutPadding().encodeToString(cipher_byte);

promptパラメータにconsentが含まれる場合は同意処理フラグconsentをtrueに設定します。

CONSENT

同意処理のために下記パラメータを実装しています。
- consent
- scope
- openid
- profile
- email
- phone
- address

scopeを各パラメータのチェックボックスに分解します。


<input type="checkbox" name="openid" value="openid" checked="true">openid<br>
<% if (authorize.getScope().contains("profile")) { %>
<input type="checkbox" name="profile" value="profile" checked="true">profile<br>
<% } if (authorize.getScope().contains("email")) { %>
<input type="checkbox" name="email" value="email" checked="true">email<br>
<% } if (authorize.getScope().contains("phone")) { %>
<input type="checkbox" name="phone" value="phone" checked="true">phone<br>
<% } if (authorize.getScope().contains("address")) { %>
<input type="checkbox" name="address" value="address" checked="true">address<br>
<% } %>

OKの場合は同意処理フラグconsentをfalseに設定してauthorizeにPOSTします。CANCELの場合はエラーをredirect_uriに#フラグメントで返します。


<input type="hidden" name="consent" value="false">

LOGOUT

セッションを無効化してログアウトページを表示します。


HttpSession session = request.getSession(false);
if (session != null) {
    session.invalidate();
}
RequestDispatcher rd = ctx.getRequestDispatcher("/logout.jsp");
rd.forward(request, response);

ERROR

異常処理のために下記エラーを実装しています。エラーはredirect_uriに#フラグメントで返します。



error=access_denied&error_description=User%20authentication%20failed.

error=invalid_scope&error_description=The%20scope%20value%20is%20not%20supported.

error=unauthorized_clienti&error_description=Client%20authentication%20failed.

error=unsupported_response_type&error_description==The%20response_type%20value%20%22nnn%22%20is%20not%20supported.

error=invalid_request&error_description=redirect_uri%20is%20not%20valid.

error=invalid_request&error_description=nonce%20is%20not%20valid.

ダウンロード

このリポジトリではOpenIDファウンデーション・ジャパン Enterprise Identity WG が公開した『OpenID ConnectとSCIMのエンタープライズ実装ガイドライン』に合わせたスクラッチ実装のソースコードを公開しています。

ASP.NET Identityに対応しました。(2020/4/18)
https://qiita.com/namikitakeo/items/0de598b8e43eb5b1ff94

2
2
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
2
2