目次
Spring Bootでの実用的な機能をわかりやすく解説中
Spring Boot における OAuth2 のサンプルコードをまとめます。OAuth 1.0A と区別します。Spring Security プロジェクト配下の Spring Security OAuth を利用することで、OAuth2 プロバイダ (サーバー) および OAuth2 コンシューマ (クライアント) を簡単に構築できます。
- Spring Boot OAuth2 チュートリアル
- Spring Boot ユーザーガイド / OAuth2
- Pivotal-Japan / From Zero to Hero with REST and OAuth2
OAuth2 サーバーのサンプル
OAuth2 ではアクセストークンの発行を行う認可サーバー Authorization Server と アクセストークンをもとにユーザー情報などを返す Resource Server の二つのサーバーが登場します。負荷分散などの必要がない通常の場合、これらのサーバーは同一のものになりますが、構造上分離できるということになります。それぞれ、Spring Security OAuth のアノテーションが用意されており、@EnableAuthorizationServer
および @EnableResourceServer
を設定するだけで、OAuth2 の基本的な機能を提供できます。
.
|-- build.gradle
|-- gradle
| `-- wrapper
| |-- gradle-wrapper.jar
| `-- gradle-wrapper.properties
|-- gradlew
|-- gradlew.bat
`-- src
`-- main
|-- java
| `-- hello
| |-- Application.java
| `-- HelloController.java
`-- resources
`-- application.yml
build.gradle
spring-security-oauth2
を設定します。
buildscript {
ext {
springBootVersion = '1.5.3.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
jar {
baseName = 'gs-spring-boot'
version = '0.1.0'
}
repositories {
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.security.oauth:spring-security-oauth2')
}
src/main/java/hello/Application.java
前述のとおり @EnableAuthorizationServer
および @EnableResourceServer
の二つのアノテーションを設定します。その際、@EnableAuthorizationServer
に関する設定を二つ行っています。
OAuth2 コンシューマの登録
OAuth2 では事前にコンシューマをプロバイダである本アプリケーションに登録しておく必要があります。Spring Security OAuth は既定では application.yml などで inMemory()
にコンシューマを設定できるようになっています。後述の allowFormAuthenticationForClients()
を設定する都合上、ここでは Java で設定しています。コンシューマの client-id と client-secret をそれぞれ demo
とし、発行されたアクセストークンで本アプリケーションに対して read
と write
という権限範囲 scope で許可されたリクエストを行えるようにします。認可サーバーは OAuth2 アクセストークンをいくつかの方法 authorized-grant-types
で発行することができます。今回は特に authorization_code
での発行を許可するように設定します。また、認可コードを含んだ状態でユーザをリダイレクトする先の URL を前方一致条件で限定することができ、ここでは検証用に http://localhost:8080
と http://oauth-callback.com
を許可しています。
アクセストークンを取得する際に Basic 認証を強制しない
OAuth2 において、http://localhost:18080/myprovider/oauth/token
に POST してアクセストークンの発行をリクエストする際、OAuth2 コンシューマは自身の client-id と client-secret を Basic 認証の id と password として含めます。しかしながら、後に構築するアプリケーションのように、Basic 認証に対応していないクライアントが存在するため、allowFormAuthenticationForClients()
で client-id と client-secret を HTTP POST のボディに含めてリクエストすることを許可しています。
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
@SpringBootApplication
@EnableAuthorizationServer
@EnableResourceServer
public class Application extends AuthorizationServerConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("demo").secret("demo").scopes("read", "write")
.authorizedGrantTypes("authorization_code")
.redirectUris("http://localhost:8080", "http://oauth-callback.com");
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.allowFormAuthenticationForClients();
}
}
src/main/java/hello/HelloController.java
リソースサーバとしてのエンドポイントを一つだけ追加してみます。簡単のため Authentication
をすべてそのまま返していますが、実際には情報を限定して返すべきです。
package hello;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@RestController
public class HelloController {
@RequestMapping(path = "/userinfo", method = RequestMethod.GET)
Object userinfo(Authentication authentication) {
return authentication;
}
}
src/main/resources/application.yml
OAuth2 プロバイダ上のユーザー登録
今回のように特に設定しない場合は自動的にユーザー名 user
が作成されます。今回はパスワードを mypassword
と設定しました。OAuth2 コンシューマがアクセストークンで情報を取得することができるユーザーです。
パスとポートの設定
後述の OAuth2 コンシューマから本 OAuth2 プロバイダを利用する際に、ドメインが localhost
で共通のため、ポート番号およびパスを設定しています。パスの設定は JSESSIONID
という名称のクッキーをプロバイダとコンシューマで別々に持たせるための回避策です。OAuth2 の本質的な設定ではありません。
ログの出力設定
OAuth2 サーバーへの HTTP リクエストを確認する検証目的で、ログレベルをデバッグに設定しています。
security:
user:
password: mypassword
server:
port: 18080
context-path: /myprovider
logging:
level:
root: DEBUG
curl を OAuth2 クライアントとして利用
後述の Spring Boot アプリケーションなどの Web アプリケーションを OAuth2 クライアントとして利用することが一般的ですが、動作検証を兼ねて以下のコマンドを実行すると user 情報が取得できることが分かります。
read scope 認可コードの発行
http://localhost:18080/myprovider/oauth/authorize?response_type=code&client_id=demo&redirect_uri=http://oauth-callback.com&scope=read にアクセスして、Basic 認証で user
, mypassword
を入力します。この Basic 認証は Spring フレームワークの都合上、簡単のため認証で利用されているだけです。OAuth2 におけるアクセストークン発行の Basic 認証と区別します。認証が通ったら、Approve を選択してから Authorize をクリックします。以下のような URL にリダイレクトされて、発行された認可コードが確認できます。
http://oauth-callback.com/?code=bbSOgq
アクセストークンの発行
OAuth2 認可サーバーは Basic 認証の -u demo:demo
で OAuth2 コンシューマを識別します。
$ curl -s -u demo:demo http://localhost:18080/myprovider/oauth/token -d grant_type=authorization_code -d code=bbSOgq -d redirect_uri=http://oauth-callback.com | jq
{
"access_token": "91c3fa7a-ac6c-479f-b779-de45236f4b67",
"token_type": "bearer",
"expires_in": 43199,
"scope": "read"
}
allowFormAuthenticationForClients()
しているため、以下のように Basic 認証なしでもアクセストークンを発行できます。
$ curl -s http://localhost:18080/myprovider/oauth/token -d grant_type=authorization_code -d code=bbSOgq -d redirect_uri=http://oauth-callback.com -d client_id=demo -d client_secret=demo | jq
{
"access_token": "91c3fa7a-ac6c-479f-b779-de45236f4b67",
"token_type": "bearer",
"expires_in": 43047,
"scope": "read"
}
アクセストークンの利用
$ curl -s http://localhost:18080/myprovider/userinfo -H 'Authorization: Bearer 91c3fa7a-ac6c-479f-b779-de45236f4b67' | jq
{
"authorities": [
{
"authority": "ROLE_USER"
}
],
"details": {
"remoteAddress": "0:0:0:0:0:0:0:1",
"sessionId": null,
"tokenValue": "91c3fa7a-ac6c-479f-b779-de45236f4b67",
"tokenType": "Bearer",
"decodedDetails": null
},
"authenticated": true,
"userAuthentication": {
"authorities": [
{
"authority": "ROLE_USER"
}
],
"details": {
"remoteAddress": "0:0:0:0:0:0:0:1",
"sessionId": "01D3FAB19EEB918904162D84AF8D1F85"
},
"authenticated": true,
"principal": {
"password": null,
"username": "user",
"authorities": [
{
"authority": "ROLE_USER"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true
},
"credentials": null,
"name": "user"
},
"clientOnly": false,
"oauth2Request": {
"clientId": "demo",
"scope": [
"read"
],
"requestParameters": {
"code": "bbSOgq",
"grant_type": "authorization_code",
"scope": "read",
"response_type": "code",
"redirect_uri": "http://oauth-callback.com",
"client_secret": "demo",
"client_id": "demo"
},
"resourceIds": [],
"authorities": [],
"approved": true,
"refresh": false,
"redirectUri": "http://oauth-callback.com",
"responseTypes": [
"code"
],
"extensions": {},
"grantType": "authorization_code",
"refreshTokenRequest": null
},
"credentials": "",
"principal": {
"password": null,
"username": "user",
"authorities": [
{
"authority": "ROLE_USER"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true
},
"name": "user"
}
OAuth2 クライアントのサンプル
以下のサンプルにおいて、いずれも Gradle プロジェクト構成および build.gradle
の設定内容は前述「OAuth2 サーバーのサンプル」と同じです。
Single Sign On によるログイン
OAuth2 コンシューマがアクセストークンを取得するためには、OAuth2 コンシューマが動作するアプリケーションを利用するユーザーによる OAuth2 プロバイダ認証がとおる必要があります。そのため、取得したアクセストークンで OAuth2 プロバイダのリソースサーバから確かにユーザーの情報が取得できることを確認することを、現在 OAuth2 コンシューマが動作するアプリケーションを利用しているユーザーの Sign On 処理に代えることができます。ユーザーは OAuth2 コンシューマが動作するアプリケーションではログイン処理を行わず、OAuth2 プロバイダでのみログイン認証を行えばよいため、これは Single Sign On の実現方法のひとつです。この Single Sign On を実現するために OAuth2 コンシューマとして必要な処理は、Spring Security OAuth の @EnableOAuth2Sso
アノテーション設定で実現できます。
Application.java (@EnableOAuth2Sso
を設定)
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
@SpringBootApplication
@EnableOAuth2Sso
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
HelloController.java (動作確認のための /
エンドポイントを設定)
package hello;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@RequestMapping("/")
Object userinfo(Authentication authentication) {
return authentication;
}
}
application.yml (GitHub の OAuth2 プロバイダで SSO 動作検証)
security:
oauth2:
client:
client-id: bd1c0a783ccdd1c9b9e4
client-secret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1
access-token-uri: https://github.com/login/oauth/access_token
user-authorization-uri: https://github.com/login/oauth/authorize
client-authentication-scheme: form
resource:
user-info-uri: https://api.github.com/user
server:
port: 8080
設定概要は以下のとおりです。
client-id
,client-secret
,port
localhost:8080
に対して GitHub に登録された OAuth2 コンシューマの id と secret です。前述のcontext-path
を設定してしまうとパスが GitHub に登録済みの情報と一致せず動作しなくなるため注意します。
user-authorization-uri
- ユーザーが認証を行うための GitHub のページです。Sign On していない状態で
localhost:8080
にリクエストがなされるとリダイレクトされます。
- ユーザーが認証を行うための GitHub のページです。Sign On していない状態で
access-token-uri
- ユーザーが認証を終えると GitHub から
localhost:8080
にリダイレクトされます。その際 URL に認可コードがクエリストリングで含まれます。この認可コードをもとにアクセストークンを取得するための、認可サーバーのエンドポイントです。
- ユーザーが認証を終えると GitHub から
user-info-uri
- 取得したアクセストークンをもとにユーザーの情報を取得するための、リソースサーバのエンドポイントです。確かに情報が取得できれば Sign On させます。
エンドポイント user-authorization-uri
で認証する際の画面例です。
localhost:18080 で動作する OAuth2 プロバイダで SSO
application.yml を localhost:18080
に向けます。その他のファイルは GitHub のサンプルと同じ内容のままです。
security:
oauth2:
client:
client-id: demo
client-secret: demo
access-token-uri: http://localhost:18080/myprovider/oauth/token
user-authorization-uri: http://localhost:18080/myprovider/oauth/authorize
client-authentication-scheme: form
resource:
user-info-uri: http://localhost:18080/myprovider/userinfo
server:
port: 8080
context-path: /myconsumer
Spring Security OAuth の Basic 認証です。上述のとおり、設定されたパスワードは mypassword
です。
一つ以上の scope を選択して認可します。
OAuth2 クライアントを直接利用
以下のサンプルにおいて build.gradle
および application.yml
の内容は、前述「Single Sign On によるログイン」と同じです。
Application.java
SSO では OAuth2 認可サーバから得たアクセストークンを内部的に利用してリソースサーバの user-info-uri
から取得した情報でログイン処理を行います。このアクセストークンはリソースサーバのその他のリソースを取得するために流用することができます。そのためのクライアント OAuth2RestTemplate
を利用するためには @EnableOAuth2Client
を設定して @Bean
を定義します。
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.context.annotation.Bean;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
@SpringBootApplication
@EnableOAuth2Sso
@EnableOAuth2Client
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext, OAuth2ProtectedResourceDetails details) {
return new OAuth2RestTemplate(details, oauth2ClientContext);
}
}
HelloController.java
定義した OAuth2RestTemplate Bean を @Autowired
して利用します。RestTemplate
と同様の手順で GitHub REST API を利用しています。返された JSON 文字列を後述の User クラスのオブジェクトに変換して返します。
package hello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Autowired
private OAuth2RestTemplate oauth2RestTemplate;
@RequestMapping("/")
Object userinfo(Authentication authentication) {
User user = oauth2RestTemplate.getForObject("https://api.github.com/user", User.class);
return user.toString();
}
}
User.java
前述の oauth2RestTemplate
で JSON 文字列を変換する対象となるクラスです。@JsonIgnoreProperties(ignoreUnknown=true)
は、JSON 文字列に存在して User クラスに存在しない項目を変換対象から除外するための設定です。
package hello;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown=true)
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User [name=" + name + "]";
}
}
レスポンス例
User [name=なつかしのねこ]
記事の執筆者にステッカーを贈る
有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。
さらに詳しく →Feedbacks
ログインするとコメントを投稿できます。
関連記事
- Spring Security フォームログインのサンプルコードSpring フレームワークによる Web アプリケーション開発で、ログイン処理を実装する際は Spring Security が便利です。ここでは特に Spring Boot で Web アプリケーションを開発する場合を対象とし、フォームによる ID/Password ログインを行うためのサンプルコードをまとめます。 公式ドキュメント [Spring Security チュートリアル](http...
- Java配列の宣言方法 (C/C++との違い)Javaの配列 Javaの配列宣言方法はC/C++と似ているようで若干異なる。 初期化しない場合 C/C++の int array[10]; はJavaでは int array[] = new int[10]; となる。同様にC/C++の int array[3][3]; はJavaでは int array[][] = new int[3][3]; となる。 初期化
- PlantUML による UML 図の描き方PlantUML はテキスト形式で表現されたシーケンス図やクラス図といった UML (Unified Modeling Language) 図の情報から画像を生成するためのツールです。簡単な使い方をまとめます。 インストール方法の選択 Atom や Eclipse のプラグインをインストールしてエディタから利用する方法、JAR をダウンロードして Java コマンドで実行する方法、Redmine ...
- Akka HTTP サンプルコード (Scala)Akka アクターを用いて実装された汎用 HTTP フレームワークです。Spray の後継です。コアモジュールである akka-http-core は 2016/2/17 に experimental が外れました。akka-http などのいくつかのサブモジュールは 2016/3/1 現在 experimental のままですが、基本的な
- Kestrel の使用例Kestrel は Message Queue (MQ) の実装のひとつです。一般に MQ はアプリケーション間やプロセス間、スレッド間で非同期に通信するために用いられます。メッセージの送信側は MQ に書き込めば受信側の応答を待たずに次の処理に非同期に進むことができます。Kestrel はわずか 2500 行程の Scala で実装されており JVM で動作します。MQ 自体はメモリ上に存在する...