マイクロサービスアーキテクチャの普及に伴い、サービス間の認証・認可を一元管理する認可サーバーの重要性が増しています。本記事では、Spring Authorization Serverを使用して、自組織向けの認可サーバーを構築する方法を解説します。外部サービス(Auth0やKeycloakなど)に依存せず、完全に自前で認可基盤を構築できるようになります。
実行環境と前提条件#
本記事の内容を実践するにあたり、以下の環境を前提としています。
| 項目 |
バージョン・要件 |
| Java |
17以上 |
| Spring Boot |
3.4.x |
| Spring Authorization Server |
1.4.x |
| Spring Security |
6.4.x |
| ビルドツール |
Maven または Gradle |
| IDE |
VS Code または IntelliJ IDEA |
事前に以下の知識があると理解がスムーズです。
- Spring Securityの基本概念(認証・認可の違い)
- OAuth2の基礎知識(認可コードフロー、クライアントクレデンシャルフロー)
- JWTの構造と検証の仕組み
Spring Authorization Serverとは#
Spring Authorization Serverは、OAuth 2.1およびOpenID Connect 1.0の仕様に準拠した認可サーバーを構築するためのSpring公式プロジェクトです。2020年にSpring Securityプロジェクトから分離され、独立したプロジェクトとして開発が進められています。
主な機能#
Spring Authorization Serverが提供する主要な機能は以下のとおりです。
| 機能 |
説明 |
| OAuth2認可エンドポイント |
認可コード発行、PKCE対応 |
| トークンエンドポイント |
アクセストークン、リフレッシュトークンの発行 |
| JWK Setエンドポイント |
公開鍵の配布 |
| トークンイントロスペクション |
トークンの有効性検証 |
| トークン失効 |
トークンの明示的な無効化 |
| OpenID Connect 1.0 |
ID Token発行、UserInfoエンドポイント |
| Dynamic Client Registration |
クライアントの動的登録 |
アーキテクチャ概要#
Spring Authorization Serverを中心としたマイクロサービスアーキテクチャの全体像は以下のとおりです。
flowchart TB
subgraph "OAuth2 エコシステム"
U[ユーザー] --> C[クライアントアプリ<br/>SPA/モバイル/BFF]
C --> AS[Spring Authorization Server<br/>認可サーバー]
AS --> C
C --> RS1[リソースサーバーA<br/>ユーザーAPI]
C --> RS2[リソースサーバーB<br/>注文API]
RS1 --> AS
RS2 --> AS
end
AS -->|"JWK Set公開"| RS1
AS -->|"JWK Set公開"| RS2プロジェクトのセットアップ#
依存関係の追加#
Spring Authorization Serverを使用するには、以下の依存関係を追加します。
Mavenの場合(pom.xml):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
</dependency>
</dependencies>
|
Gradleの場合(build.gradle):
1
2
3
4
5
|
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-authorization-server'
}
|
spring-boot-starter-oauth2-authorization-serverは、Spring Authorization Serverの全機能を含んでいます。
プロジェクト構成#
本記事で作成するプロジェクトの構成は以下のとおりです。
src/main/java/com/example/authserver/
├── AuthServerApplication.java
├── config/
│ ├── AuthorizationServerConfig.java
│ └── DefaultSecurityConfig.java
└── entity/
└── User.java
最小構成での認可サーバー#
application.ymlによる設定#
Spring Boot 3.4以降では、application.ymlのみで基本的な認可サーバーを構成できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
server:
port: 9000
logging:
level:
org.springframework.security: DEBUG
spring:
security:
user:
name: user
password: password
oauth2:
authorizationserver:
client:
web-client:
registration:
client-id: "web-client"
client-secret: "{noop}web-secret"
client-authentication-methods:
- "client_secret_basic"
authorization-grant-types:
- "authorization_code"
- "refresh_token"
redirect-uris:
- "http://127.0.0.1:8080/login/oauth2/code/web-client"
post-logout-redirect-uris:
- "http://127.0.0.1:8080/"
scopes:
- "openid"
- "profile"
- "email"
require-authorization-consent: true
|
この設定により、以下のエンドポイントが自動的に構成されます。
| エンドポイント |
パス |
説明 |
| 認可エンドポイント |
/oauth2/authorize |
認可コード発行 |
| トークンエンドポイント |
/oauth2/token |
トークン発行 |
| JWK Setエンドポイント |
/oauth2/jwks |
公開鍵配布 |
| トークン失効 |
/oauth2/revoke |
トークン無効化 |
| トークンイントロスペクション |
/oauth2/introspect |
トークン検証 |
| OpenID構成 |
/.well-known/openid-configuration |
ディスカバリー |
カスタム設定による認可サーバー構築#
本番環境向けには、Javaコードで明示的に設定を行う方法が推奨されます。以下では、詳細なカスタマイズが可能な構成を解説します。
SecurityFilterChainの設定#
認可サーバー用とデフォルト認証用の2つのSecurityFilterChainを定義します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
package com.example.authserver.config;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
@Configuration
@EnableWebSecurity
public class AuthorizationServerConfig {
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
// 認可サーバーのデフォルト設定を適用
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
OAuth2AuthorizationServerConfigurer.authorizationServer();
http
.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
.with(authorizationServerConfigurer, authorizationServer ->
authorizationServer
// OpenID Connect 1.0を有効化
.oidc(Customizer.withDefaults())
)
.authorizeHttpRequests(authorize ->
authorize.anyRequest().authenticated()
)
// 未認証時はログインページへリダイレクト
.exceptionHandling(exceptions -> exceptions
.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
);
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
// フォームログインを有効化
.formLogin(Customizer.withDefaults());
return http.build();
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder()
.issuer("http://localhost:9000")
.build();
}
}
|
各コンポーネントの役割は以下のとおりです。
| コンポーネント |
役割 |
authorizationServerSecurityFilterChain |
OAuth2/OIDCプロトコルエンドポイント用のフィルターチェーン |
defaultSecurityFilterChain |
ログインページなど通常の認証用フィルターチェーン |
jwkSource |
JWTの署名・検証に使用するRSA鍵ペアを提供 |
jwtDecoder |
自己発行したJWTの検証用デコーダー |
authorizationServerSettings |
発行者(issuer)URLなどの設定 |
UserDetailsServiceの設定#
認証に使用するユーザー情報を提供するUserDetailsServiceを設定します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package com.example.authserver.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
public class DefaultSecurityConfig {
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
UserDetails admin = User.withDefaultPasswordEncoder()
.username("admin")
.password("admin")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
}
|
本番環境では、InMemoryUserDetailsManagerではなく、データベースと連携したUserDetailsServiceを実装してください。
クライアント登録と複数クライアント対応#
RegisteredClientRepositoryの設定#
OAuth2クライアントの登録と管理を行うRegisteredClientRepositoryを設定します。以下では、異なるユースケースに対応した複数のクライアントを登録する例を示します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
package com.example.authserver.config;
import java.util.UUID;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import java.time.Duration;
@Configuration
public class ClientConfig {
@Bean
public RegisteredClientRepository registeredClientRepository(PasswordEncoder passwordEncoder) {
// Webアプリケーション用クライアント(認可コードフロー)
RegisteredClient webClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("web-client")
.clientSecret(passwordEncoder.encode("web-secret"))
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/web-client")
.postLogoutRedirectUri("http://127.0.0.1:8080/")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.scope(OidcScopes.EMAIL)
.scope("read")
.scope("write")
.clientSettings(ClientSettings.builder()
.requireAuthorizationConsent(true)
.requireProofKey(false)
.build())
.tokenSettings(TokenSettings.builder()
.accessTokenTimeToLive(Duration.ofMinutes(30))
.refreshTokenTimeToLive(Duration.ofDays(7))
.reuseRefreshTokens(false)
.build())
.build();
// SPAアプリケーション用クライアント(PKCE必須)
RegisteredClient spaClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("spa-client")
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:3000/callback")
.postLogoutRedirectUri("http://127.0.0.1:3000/")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.scope("read")
.clientSettings(ClientSettings.builder()
.requireAuthorizationConsent(true)
.requireProofKey(true) // PKCE必須
.build())
.tokenSettings(TokenSettings.builder()
.accessTokenTimeToLive(Duration.ofMinutes(15))
.refreshTokenTimeToLive(Duration.ofHours(8))
.reuseRefreshTokens(false)
.build())
.build();
// マイクロサービス間通信用クライアント(クライアントクレデンシャルフロー)
RegisteredClient serviceClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("service-client")
.clientSecret(passwordEncoder.encode("service-secret"))
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope("internal:read")
.scope("internal:write")
.tokenSettings(TokenSettings.builder()
.accessTokenTimeToLive(Duration.ofMinutes(60))
.build())
.build();
return new InMemoryRegisteredClientRepository(webClient, spaClient, serviceClient);
}
@Bean
public PasswordEncoder passwordEncoder() {
return org.springframework.security.crypto.factory.PasswordEncoderFactories
.createDelegatingPasswordEncoder();
}
}
|
クライアント種別と設定の対応#
各クライアント種別に応じた設定のポイントを以下にまとめます。
| クライアント種別 |
認証方式 |
グラントタイプ |
PKCE |
主なユースケース |
| Webアプリ(BFF) |
CLIENT_SECRET_BASIC |
authorization_code |
任意 |
サーバーサイドレンダリング |
| SPA |
NONE |
authorization_code |
必須 |
フロントエンドアプリ |
| マイクロサービス |
CLIENT_SECRET_BASIC |
client_credentials |
不要 |
サービス間通信 |
| モバイルアプリ |
NONE |
authorization_code |
必須 |
ネイティブアプリ |
JWKSetエンドポイントの公開#
JWKSet(JSON Web Key Set)エンドポイントは、リソースサーバーがJWTの署名を検証するための公開鍵を提供します。Spring Authorization Serverでは、JWKSource Beanを登録すると自動的に/oauth2/jwksエンドポイントが有効になります。
本番環境向けの鍵管理#
本番環境では、起動ごとに鍵を生成するのではなく、永続化された鍵を使用する必要があります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@Bean
public JWKSource<SecurityContext> jwkSource() throws Exception {
// PEM形式の秘密鍵を読み込む場合
String privateKeyPem = Files.readString(Path.of("/etc/secrets/private-key.pem"));
String publicKeyPem = Files.readString(Path.of("/etc/secrets/public-key.pem"));
RSAPrivateKey privateKey = parsePrivateKey(privateKeyPem);
RSAPublicKey publicKey = parsePublicKey(publicKeyPem);
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID("prod-key-2026")
.build();
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
|
JWKSetレスポンスの例#
/oauth2/jwksエンドポイントにアクセスすると、以下のようなJWK Setが返却されます。
1
2
3
4
5
6
7
8
9
10
|
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"kid": "prod-key-2026",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4..."
}
]
}
|
リソースサーバーは、この公開鍵を使用してJWTの署名を検証します。
トークンのカスタマイズ#
アクセストークンへのカスタムクレーム追加#
アクセストークンにユーザー固有の情報を追加する場合は、OAuth2TokenCustomizerを使用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
package com.example.authserver.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import java.util.stream.Collectors;
@Configuration
public class TokenCustomizerConfig {
@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
return context -> {
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
Authentication principal = context.getPrincipal();
// ロール情報をクレームに追加
context.getClaims().claim("roles",
principal.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet()));
// カスタムクレームの追加
context.getClaims().claim("tenant_id", "default");
context.getClaims().claim("user_type", "internal");
}
};
}
}
|
カスタマイズ後のアクセストークンのペイロード例は以下のとおりです。
1
2
3
4
5
6
7
8
9
10
11
12
|
{
"sub": "user",
"aud": "web-client",
"nbf": 1736726400,
"scope": "openid profile email read",
"roles": ["ROLE_USER"],
"tenant_id": "default",
"user_type": "internal",
"iss": "http://localhost:9000",
"exp": 1736728200,
"iat": 1736726400
}
|
認可フローの動作確認#
認可コードフローのテスト#
認可コードフローを手動でテストする手順を示します。
1. 認可リクエストの送信:
ブラウザで以下のURLにアクセスします。
http://localhost:9000/oauth2/authorize?
response_type=code&
client_id=web-client&
scope=openid%20profile%20email%20read&
redirect_uri=http://127.0.0.1:8080/login/oauth2/code/web-client&
state=abc123
2. ユーザー認証:
ログインページが表示されるので、設定したユーザー(user/password)でログインします。
3. 同意画面:
requireAuthorizationConsentをtrueに設定している場合、スコープの同意画面が表示されます。
4. 認可コードの取得:
同意後、リダイレクトURIに認可コードが付与されて戻ります。
http://127.0.0.1:8080/login/oauth2/code/web-client?
code=AUTHORIZATION_CODE&
state=abc123
5. トークン取得リクエスト:
認可コードを使用してトークンを取得します。
1
2
3
4
5
6
|
curl -X POST http://localhost:9000/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "web-client:web-secret" \
-d "grant_type=authorization_code" \
-d "code=AUTHORIZATION_CODE" \
-d "redirect_uri=http://127.0.0.1:8080/login/oauth2/code/web-client"
|
クライアントクレデンシャルフローのテスト#
マイクロサービス間通信用のクライアントクレデンシャルフローをテストします。
1
2
3
4
5
|
curl -X POST http://localhost:9000/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "service-client:service-secret" \
-d "grant_type=client_credentials" \
-d "scope=internal:read internal:write"
|
成功すると以下のようなレスポンスが返却されます。
1
2
3
4
5
6
|
{
"access_token": "eyJraWQiOiJwcm9kLWtleS0yMDI2IiwiYWxnIjoiUlMyNTYifQ...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "internal:read internal:write"
}
|
データベース永続化の設定#
本番環境では、クライアント情報や認可情報をデータベースに永続化する必要があります。
JDBC実装の設定#
Spring Authorization Serverは、JDBC実装を標準で提供しています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
package com.example.authserver.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
@Configuration
public class JdbcConfig {
@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
return new JdbcRegisteredClientRepository(jdbcTemplate);
}
@Bean
public OAuth2AuthorizationService authorizationService(
JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
}
@Bean
public OAuth2AuthorizationConsentService authorizationConsentService(
JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
}
}
|
スキーマの作成#
Spring Authorization Serverが必要とするテーブルのスキーマは、公式リポジトリで提供されています。以下はPostgreSQL用のスキーマ例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
CREATE TABLE oauth2_registered_client (
id varchar(100) NOT NULL,
client_id varchar(100) NOT NULL,
client_id_issued_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
client_secret varchar(200) DEFAULT NULL,
client_secret_expires_at timestamp DEFAULT NULL,
client_name varchar(200) NOT NULL,
client_authentication_methods varchar(1000) NOT NULL,
authorization_grant_types varchar(1000) NOT NULL,
redirect_uris varchar(1000) DEFAULT NULL,
post_logout_redirect_uris varchar(1000) DEFAULT NULL,
scopes varchar(1000) NOT NULL,
client_settings varchar(2000) NOT NULL,
token_settings varchar(2000) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE oauth2_authorization (
id varchar(100) NOT NULL,
registered_client_id varchar(100) NOT NULL,
principal_name varchar(200) NOT NULL,
authorization_grant_type varchar(100) NOT NULL,
authorized_scopes varchar(1000) DEFAULT NULL,
attributes text DEFAULT NULL,
state varchar(500) DEFAULT NULL,
authorization_code_value text DEFAULT NULL,
authorization_code_issued_at timestamp DEFAULT NULL,
authorization_code_expires_at timestamp DEFAULT NULL,
authorization_code_metadata text DEFAULT NULL,
access_token_value text DEFAULT NULL,
access_token_issued_at timestamp DEFAULT NULL,
access_token_expires_at timestamp DEFAULT NULL,
access_token_metadata text DEFAULT NULL,
access_token_type varchar(100) DEFAULT NULL,
access_token_scopes varchar(1000) DEFAULT NULL,
oidc_id_token_value text DEFAULT NULL,
oidc_id_token_issued_at timestamp DEFAULT NULL,
oidc_id_token_expires_at timestamp DEFAULT NULL,
oidc_id_token_metadata text DEFAULT NULL,
refresh_token_value text DEFAULT NULL,
refresh_token_issued_at timestamp DEFAULT NULL,
refresh_token_expires_at timestamp DEFAULT NULL,
refresh_token_metadata text DEFAULT NULL,
user_code_value text DEFAULT NULL,
user_code_issued_at timestamp DEFAULT NULL,
user_code_expires_at timestamp DEFAULT NULL,
user_code_metadata text DEFAULT NULL,
device_code_value text DEFAULT NULL,
device_code_issued_at timestamp DEFAULT NULL,
device_code_expires_at timestamp DEFAULT NULL,
device_code_metadata text DEFAULT NULL,
PRIMARY KEY (id)
);
CREATE TABLE oauth2_authorization_consent (
registered_client_id varchar(100) NOT NULL,
principal_name varchar(200) NOT NULL,
authorities varchar(1000) NOT NULL,
PRIMARY KEY (registered_client_id, principal_name)
);
|
リソースサーバーとの連携#
作成した認可サーバーと連携するリソースサーバーの設定例を示します。
リソースサーバーの設定#
リソースサーバー側のapplication.ymlは以下のように設定します。
1
2
3
4
5
6
7
|
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:9000
jwk-set-uri: http://localhost:9000/oauth2/jwks
|
リソースサーバーは、jwk-set-uriから公開鍵を取得してJWTを検証します。issuer-uriを指定することで、起動時に自動的に.well-known/openid-configurationを取得し、各種エンドポイントを解決します。
構成図#
認可サーバーとリソースサーバーの連携を示す図は以下のとおりです。
sequenceDiagram
participant Client as クライアント
participant AS as 認可サーバー<br/>(localhost:9000)
participant RS as リソースサーバー<br/>(localhost:8080)
Client->>AS: 1. 認可リクエスト
AS->>Client: 2. 認可コード
Client->>AS: 3. トークンリクエスト
AS->>Client: 4. アクセストークン (JWT)
Client->>RS: 5. APIリクエスト + Bearer Token
RS->>AS: 6. JWK Set取得 (初回のみ)
AS->>RS: 7. 公開鍵
RS->>RS: 8. JWT署名検証
RS->>Client: 9. リソース返却本番環境に向けた考慮事項#
Spring Authorization Serverを本番環境で運用する際には、以下の点を考慮してください。
| 項目 |
推奨事項 |
| 鍵の管理 |
HSM(Hardware Security Module)やAWS KMS、HashiCorp Vaultなどを使用 |
| HTTPS |
本番環境では必ずHTTPSを使用 |
| クライアントシークレット |
環境変数やSecrets Managerで管理 |
| セッション管理 |
Redisなどの分散キャッシュでセッション共有 |
| ログ監査 |
認可イベントのログ記録と監視 |
| レート制限 |
トークンエンドポイントへのアクセス制限 |
| 高可用性 |
複数インスタンスでのクラスタ構成 |
まとめ#
本記事では、Spring Authorization Serverを使用して自組織向けの認可サーバーを構築する方法を解説しました。主なポイントを以下にまとめます。
- Spring Authorization Serverは、OAuth 2.1およびOpenID Connect 1.0に準拠した認可サーバーを構築するための公式プロジェクトです
- 最小構成では
application.ymlのみで動作しますが、本番環境ではJavaコードによる明示的な設定が推奨されます
- 複数のクライアント(Webアプリ、SPA、マイクロサービス)に対応した設定が可能です
- JWK Setエンドポイントにより、リソースサーバーへの公開鍵配布が自動化されます
- データベース永続化により、本番環境での運用に対応できます
Spring Authorization Serverを活用することで、KeycloakやAuth0などの外部サービスに依存せず、完全に自前の認可基盤を構築できます。マイクロサービスアーキテクチャにおける認証・認可の一元管理に、ぜひ活用してください。
参考リンク#