使い方: Redis でコアサービスを実装する
This guide shows how to implement the core services of Spring Authorization Server with Redis (英語) . The purpose of this guide is to provide a starting point for implementing these services yourself, with the intention that you can make modifications to suit your needs.
The code samples provided in this guide are located in the documentation samples [GitHub] (英語) directory under the redis subdirectory. |
エンティティモデルを定義する
以下は、RegisteredClient
、OAuth2Authorization
、OAuth2AuthorizationConsent
ドメインクラスのエンティティモデル表現を定義します。
登録クライアントエンティティ
次のリストは、RegisteredClient
ドメインクラスからマップされた情報を保持するために使用される OAuth2RegisteredClient
エンティティを示しています。
import java.time.Duration;
import java.time.Instant;
import java.util.Set;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
@RedisHash("oauth2_registered_client")
public class OAuth2RegisteredClient {
@Id
private final String id;
@Indexed
private final String clientId;
private final Instant clientIdIssuedAt;
private final String clientSecret;
private final Instant clientSecretExpiresAt;
private final String clientName;
private final Set<ClientAuthenticationMethod> clientAuthenticationMethods;
private final Set<AuthorizationGrantType> authorizationGrantTypes;
private final Set<String> redirectUris;
private final Set<String> postLogoutRedirectUris;
private final Set<String> scopes;
private final ClientSettings clientSettings;
private final TokenSettings tokenSettings;
public OAuth2RegisteredClient(String id, String clientId, Instant clientIdIssuedAt, String clientSecret,
Instant clientSecretExpiresAt, String clientName,
Set<ClientAuthenticationMethod> clientAuthenticationMethods,
Set<AuthorizationGrantType> authorizationGrantTypes, Set<String> redirectUris,
Set<String> postLogoutRedirectUris, Set<String> scopes, ClientSettings clientSettings,
TokenSettings tokenSettings) {
this.id = id;
this.clientId = clientId;
this.clientIdIssuedAt = clientIdIssuedAt;
this.clientSecret = clientSecret;
this.clientSecretExpiresAt = clientSecretExpiresAt;
this.clientName = clientName;
this.clientAuthenticationMethods = clientAuthenticationMethods;
this.authorizationGrantTypes = authorizationGrantTypes;
this.redirectUris = redirectUris;
this.postLogoutRedirectUris = postLogoutRedirectUris;
this.scopes = scopes;
this.clientSettings = clientSettings;
this.tokenSettings = tokenSettings;
}
public String getId() {
return this.id;
}
public String getClientId() {
return this.clientId;
}
public Instant getClientIdIssuedAt() {
return this.clientIdIssuedAt;
}
public String getClientSecret() {
return this.clientSecret;
}
public Instant getClientSecretExpiresAt() {
return this.clientSecretExpiresAt;
}
public String getClientName() {
return this.clientName;
}
public Set<ClientAuthenticationMethod> getClientAuthenticationMethods() {
return this.clientAuthenticationMethods;
}
public Set<AuthorizationGrantType> getAuthorizationGrantTypes() {
return this.authorizationGrantTypes;
}
public Set<String> getRedirectUris() {
return this.redirectUris;
}
public Set<String> getPostLogoutRedirectUris() {
return this.postLogoutRedirectUris;
}
public Set<String> getScopes() {
return this.scopes;
}
public ClientSettings getClientSettings() {
return this.clientSettings;
}
public TokenSettings getTokenSettings() {
return this.tokenSettings;
}
public static class ClientSettings {
private final boolean requireProofKey;
private final boolean requireAuthorizationConsent;
private final String jwkSetUrl;
private final JwsAlgorithm tokenEndpointAuthenticationSigningAlgorithm;
private final String x509CertificateSubjectDN;
public ClientSettings(boolean requireProofKey, boolean requireAuthorizationConsent, String jwkSetUrl,
JwsAlgorithm tokenEndpointAuthenticationSigningAlgorithm, String x509CertificateSubjectDN) {
this.requireProofKey = requireProofKey;
this.requireAuthorizationConsent = requireAuthorizationConsent;
this.jwkSetUrl = jwkSetUrl;
this.tokenEndpointAuthenticationSigningAlgorithm = tokenEndpointAuthenticationSigningAlgorithm;
this.x509CertificateSubjectDN = x509CertificateSubjectDN;
}
public boolean isRequireProofKey() {
return this.requireProofKey;
}
public boolean isRequireAuthorizationConsent() {
return this.requireAuthorizationConsent;
}
public String getJwkSetUrl() {
return this.jwkSetUrl;
}
public JwsAlgorithm getTokenEndpointAuthenticationSigningAlgorithm() {
return this.tokenEndpointAuthenticationSigningAlgorithm;
}
public String getX509CertificateSubjectDN() {
return this.x509CertificateSubjectDN;
}
}
public static class TokenSettings {
private final Duration authorizationCodeTimeToLive;
private final Duration accessTokenTimeToLive;
private final OAuth2TokenFormat accessTokenFormat;
private final Duration deviceCodeTimeToLive;
private final boolean reuseRefreshTokens;
private final Duration refreshTokenTimeToLive;
private final SignatureAlgorithm idTokenSignatureAlgorithm;
private final boolean x509CertificateBoundAccessTokens;
public TokenSettings(Duration authorizationCodeTimeToLive, Duration accessTokenTimeToLive,
OAuth2TokenFormat accessTokenFormat, Duration deviceCodeTimeToLive, boolean reuseRefreshTokens,
Duration refreshTokenTimeToLive, SignatureAlgorithm idTokenSignatureAlgorithm,
boolean x509CertificateBoundAccessTokens) {
this.authorizationCodeTimeToLive = authorizationCodeTimeToLive;
this.accessTokenTimeToLive = accessTokenTimeToLive;
this.accessTokenFormat = accessTokenFormat;
this.deviceCodeTimeToLive = deviceCodeTimeToLive;
this.reuseRefreshTokens = reuseRefreshTokens;
this.refreshTokenTimeToLive = refreshTokenTimeToLive;
this.idTokenSignatureAlgorithm = idTokenSignatureAlgorithm;
this.x509CertificateBoundAccessTokens = x509CertificateBoundAccessTokens;
}
public Duration getAuthorizationCodeTimeToLive() {
return this.authorizationCodeTimeToLive;
}
public Duration getAccessTokenTimeToLive() {
return this.accessTokenTimeToLive;
}
public OAuth2TokenFormat getAccessTokenFormat() {
return this.accessTokenFormat;
}
public Duration getDeviceCodeTimeToLive() {
return this.deviceCodeTimeToLive;
}
public boolean isReuseRefreshTokens() {
return this.reuseRefreshTokens;
}
public Duration getRefreshTokenTimeToLive() {
return this.refreshTokenTimeToLive;
}
public SignatureAlgorithm getIdTokenSignatureAlgorithm() {
return this.idTokenSignatureAlgorithm;
}
public boolean isX509CertificateBoundAccessTokens() {
return this.x509CertificateBoundAccessTokens;
}
}
}
完全な例を表示するには、上のコードサンプルの 折りたたまれたテキストを展開する アイコンをクリックします。 |
認可付与ベースエンティティ
OAuth2Authorization
ドメインクラスのエンティティモデルは、認可付与型に基づくクラス階層を使用して設計されています。
次のリストは、各認可付与型に共通する属性を定義する OAuth2AuthorizationGrantAuthorization
基本エンティティを示しています。
import java.time.Instant;
import java.util.Map;
import java.util.Set;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
@RedisHash("oauth2_authorization")
public abstract class OAuth2AuthorizationGrantAuthorization {
@Id
private final String id;
private final String registeredClientId;
private final String principalName;
private final Set<String> authorizedScopes;
private final AccessToken accessToken;
private final RefreshToken refreshToken;
protected OAuth2AuthorizationGrantAuthorization(String id, String registeredClientId, String principalName,
Set<String> authorizedScopes, AccessToken accessToken, RefreshToken refreshToken) {
this.id = id;
this.registeredClientId = registeredClientId;
this.principalName = principalName;
this.authorizedScopes = authorizedScopes;
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
public String getId() {
return this.id;
}
public String getRegisteredClientId() {
return this.registeredClientId;
}
public String getPrincipalName() {
return this.principalName;
}
public Set<String> getAuthorizedScopes() {
return this.authorizedScopes;
}
public AccessToken getAccessToken() {
return this.accessToken;
}
public RefreshToken getRefreshToken() {
return this.refreshToken;
}
protected abstract static class AbstractToken {
@Indexed
private final String tokenValue;
private final Instant issuedAt;
private final Instant expiresAt;
private final boolean invalidated;
protected AbstractToken(String tokenValue, Instant issuedAt, Instant expiresAt, boolean invalidated) {
this.tokenValue = tokenValue;
this.issuedAt = issuedAt;
this.expiresAt = expiresAt;
this.invalidated = invalidated;
}
public String getTokenValue() {
return this.tokenValue;
}
public Instant getIssuedAt() {
return this.issuedAt;
}
public Instant getExpiresAt() {
return this.expiresAt;
}
public boolean isInvalidated() {
return this.invalidated;
}
}
public static class ClaimsHolder {
private final Map<String, Object> claims;
public ClaimsHolder(Map<String, Object> claims) {
this.claims = claims;
}
public Map<String, Object> getClaims() {
return this.claims;
}
}
public static class AccessToken extends AbstractToken {
private final OAuth2AccessToken.TokenType tokenType;
private final Set<String> scopes;
private final OAuth2TokenFormat tokenFormat;
private final ClaimsHolder claims;
public AccessToken(String tokenValue, Instant issuedAt, Instant expiresAt, boolean invalidated,
OAuth2AccessToken.TokenType tokenType, Set<String> scopes, OAuth2TokenFormat tokenFormat,
ClaimsHolder claims) {
super(tokenValue, issuedAt, expiresAt, invalidated);
this.tokenType = tokenType;
this.scopes = scopes;
this.tokenFormat = tokenFormat;
this.claims = claims;
}
public OAuth2AccessToken.TokenType getTokenType() {
return this.tokenType;
}
public Set<String> getScopes() {
return this.scopes;
}
public OAuth2TokenFormat getTokenFormat() {
return this.tokenFormat;
}
public ClaimsHolder getClaims() {
return this.claims;
}
}
public static class RefreshToken extends AbstractToken {
public RefreshToken(String tokenValue, Instant issuedAt, Instant expiresAt, boolean invalidated) {
super(tokenValue, issuedAt, expiresAt, invalidated);
}
}
}
認可コード付与エンティティ (OAuth 2.0)
次のリストは、OAuth2AuthorizationGrantAuthorization
を継承し、OAuth 2.0 authorization_code
付与型の追加属性を定義する OAuth2AuthorizationCodeGrantAuthorization
エンティティを示しています。
import java.security.Principal;
import java.time.Instant;
import java.util.Set;
import org.springframework.data.redis.core.index.Indexed;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
public class OAuth2AuthorizationCodeGrantAuthorization extends OAuth2AuthorizationGrantAuthorization {
private final Principal principal;
private final OAuth2AuthorizationRequest authorizationRequest;
private final AuthorizationCode authorizationCode;
@Indexed
private final String state; // Used to correlate the request during the authorization
// consent flow
public OAuth2AuthorizationCodeGrantAuthorization(String id, String registeredClientId, String principalName,
Set<String> authorizedScopes, AccessToken accessToken, RefreshToken refreshToken, Principal principal,
OAuth2AuthorizationRequest authorizationRequest, AuthorizationCode authorizationCode, String state) {
super(id, registeredClientId, principalName, authorizedScopes, accessToken, refreshToken);
this.principal = principal;
this.authorizationRequest = authorizationRequest;
this.authorizationCode = authorizationCode;
this.state = state;
}
public Principal getPrincipal() {
return this.principal;
}
public OAuth2AuthorizationRequest getAuthorizationRequest() {
return this.authorizationRequest;
}
public AuthorizationCode getAuthorizationCode() {
return this.authorizationCode;
}
public String getState() {
return this.state;
}
public static class AuthorizationCode extends AbstractToken {
public AuthorizationCode(String tokenValue, Instant issuedAt, Instant expiresAt, boolean invalidated) {
super(tokenValue, issuedAt, expiresAt, invalidated);
}
}
}
認可コード付与エンティティ (OpenID Connect 1.0)
次のリストは、OAuth2AuthorizationCodeGrantAuthorization
を継承し、OpenID Connect 1.0 authorization_code
付与型の追加属性を定義する OidcAuthorizationCodeGrantAuthorization
エンティティを示しています。
import java.security.Principal;
import java.time.Instant;
import java.util.Set;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
public class OidcAuthorizationCodeGrantAuthorization extends OAuth2AuthorizationCodeGrantAuthorization {
private final IdToken idToken;
public OidcAuthorizationCodeGrantAuthorization(String id, String registeredClientId, String principalName,
Set<String> authorizedScopes, AccessToken accessToken, RefreshToken refreshToken, Principal principal,
OAuth2AuthorizationRequest authorizationRequest, AuthorizationCode authorizationCode, String state,
IdToken idToken) {
super(id, registeredClientId, principalName, authorizedScopes, accessToken, refreshToken, principal,
authorizationRequest, authorizationCode, state);
this.idToken = idToken;
}
public IdToken getIdToken() {
return this.idToken;
}
public static class IdToken extends AbstractToken {
private final ClaimsHolder claims;
public IdToken(String tokenValue, Instant issuedAt, Instant expiresAt, boolean invalidated,
ClaimsHolder claims) {
super(tokenValue, issuedAt, expiresAt, invalidated);
this.claims = claims;
}
public ClaimsHolder getClaims() {
return this.claims;
}
}
}
クライアント資格情報付与エンティティ
次のリストは、client_credentials
付与型の OAuth2AuthorizationGrantAuthorization
を継承する OAuth2ClientCredentialsGrantAuthorization
エンティティを示しています。
import java.util.Set;
public class OAuth2ClientCredentialsGrantAuthorization extends OAuth2AuthorizationGrantAuthorization {
public OAuth2ClientCredentialsGrantAuthorization(String id, String registeredClientId, String principalName,
Set<String> authorizedScopes, AccessToken accessToken) {
super(id, registeredClientId, principalName, authorizedScopes, accessToken, null);
}
}
デバイスコード付与エンティティ
次のリストは、OAuth2AuthorizationGrantAuthorization
を継承し、urn:ietf:params:oauth:grant-type:device_code
付与型の追加属性を定義する OAuth2DeviceCodeGrantAuthorization
エンティティを示しています。
import java.security.Principal;
import java.time.Instant;
import java.util.Set;
import org.springframework.data.redis.core.index.Indexed;
public class OAuth2DeviceCodeGrantAuthorization extends OAuth2AuthorizationGrantAuthorization {
private final Principal principal;
private final DeviceCode deviceCode;
private final UserCode userCode;
private final Set<String> requestedScopes;
@Indexed
private final String deviceState; // Used to correlate the request during the
// authorization consent flow
public OAuth2DeviceCodeGrantAuthorization(String id, String registeredClientId, String principalName,
Set<String> authorizedScopes, AccessToken accessToken, RefreshToken refreshToken, Principal principal,
DeviceCode deviceCode, UserCode userCode, Set<String> requestedScopes, String deviceState) {
super(id, registeredClientId, principalName, authorizedScopes, accessToken, refreshToken);
this.principal = principal;
this.deviceCode = deviceCode;
this.userCode = userCode;
this.requestedScopes = requestedScopes;
this.deviceState = deviceState;
}
public Principal getPrincipal() {
return this.principal;
}
public DeviceCode getDeviceCode() {
return this.deviceCode;
}
public UserCode getUserCode() {
return this.userCode;
}
public Set<String> getRequestedScopes() {
return this.requestedScopes;
}
public String getDeviceState() {
return this.deviceState;
}
public static class DeviceCode extends AbstractToken {
public DeviceCode(String tokenValue, Instant issuedAt, Instant expiresAt, boolean invalidated) {
super(tokenValue, issuedAt, expiresAt, invalidated);
}
}
public static class UserCode extends AbstractToken {
public UserCode(String tokenValue, Instant issuedAt, Instant expiresAt, boolean invalidated) {
super(tokenValue, issuedAt, expiresAt, invalidated);
}
}
}
トークン交換助成団体
次のリストは、urn:ietf:params:oauth:grant-type:token-exchange
付与型の OAuth2AuthorizationGrantAuthorization
を継承する OAuth2TokenExchangeGrantAuthorization
エンティティを示しています。
import java.util.Set;
public class OAuth2TokenExchangeGrantAuthorization extends OAuth2AuthorizationGrantAuthorization {
public OAuth2TokenExchangeGrantAuthorization(String id, String registeredClientId, String principalName,
Set<String> authorizedScopes, AccessToken accessToken) {
super(id, registeredClientId, principalName, authorizedScopes, accessToken, null);
}
}
認可同意エンティティ
次のリストは、OAuth2AuthorizationConsent
ドメインクラスからマップされた情報を保持するために使用される OAuth2UserConsent
エンティティを示しています。
import java.util.Set;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;
import org.springframework.security.core.GrantedAuthority;
@RedisHash("oauth2_authorization_consent")
public class OAuth2UserConsent {
@Id
private final String id;
@Indexed
private final String registeredClientId;
@Indexed
private final String principalName;
private final Set<GrantedAuthority> authorities;
public OAuth2UserConsent(String id, String registeredClientId, String principalName,
Set<GrantedAuthority> authorities) {
this.id = id;
this.registeredClientId = registeredClientId;
this.principalName = principalName;
this.authorities = authorities;
}
public String getId() {
return this.id;
}
public String getRegisteredClientId() {
return this.registeredClientId;
}
public String getPrincipalName() {
return this.principalName;
}
public Set<GrantedAuthority> getAuthorities() {
return this.authorities;
}
}
Spring Data リポジトリを作成する
各コアサービスのインターフェースを詳しく調べ、Jdbc
実装を確認することで、各インターフェースの Redis バージョンをサポートするために必要な最小限のクエリセットを導き出すことができます。
登録済みクライアントリポジトリ
次のリストは、id
および clientId
フィールドによって OAuth2RegisteredClient
を見つけることができる OAuth2RegisteredClientRepository
を示しています。
import sample.redis.entity.OAuth2RegisteredClient;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2RegisteredClientRepository extends CrudRepository<OAuth2RegisteredClient, String> {
OAuth2RegisteredClient findByClientId(String clientId);
}
認可付与リポジトリ
次のリストは、id
フィールドと state
、authorizationCode
、accessToken
、refreshToken
、idToken
、deviceState
、userCode
、deviceCode
値によって OAuth2AuthorizationGrantAuthorization
を見つけることができる OAuth2AuthorizationGrantAuthorizationRepository
を示しています。
import sample.redis.entity.OAuth2AuthorizationCodeGrantAuthorization;
import sample.redis.entity.OAuth2AuthorizationGrantAuthorization;
import sample.redis.entity.OAuth2DeviceCodeGrantAuthorization;
import sample.redis.entity.OidcAuthorizationCodeGrantAuthorization;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2AuthorizationGrantAuthorizationRepository
extends CrudRepository<OAuth2AuthorizationGrantAuthorization, String> {
<T extends OAuth2AuthorizationCodeGrantAuthorization> T findByState(String state);
<T extends OAuth2AuthorizationCodeGrantAuthorization> T findByAuthorizationCode_TokenValue(String authorizationCode);
<T extends OAuth2AuthorizationCodeGrantAuthorization> T findByStateOrAuthorizationCode_TokenValue(String state, String authorizationCode);
<T extends OAuth2AuthorizationGrantAuthorization> T findByAccessToken_TokenValue(String accessToken);
<T extends OAuth2AuthorizationGrantAuthorization> T findByRefreshToken_TokenValue(String refreshToken);
<T extends OAuth2AuthorizationGrantAuthorization> T findByAccessToken_TokenValueOrRefreshToken_TokenValue(String accessToken, String refreshToken);
<T extends OidcAuthorizationCodeGrantAuthorization> T findByIdToken_TokenValue(String idToken);
<T extends OAuth2DeviceCodeGrantAuthorization> T findByDeviceState(String deviceState);
<T extends OAuth2DeviceCodeGrantAuthorization> T findByDeviceCode_TokenValue(String deviceCode);
<T extends OAuth2DeviceCodeGrantAuthorization> T findByUserCode_TokenValue(String userCode);
<T extends OAuth2DeviceCodeGrantAuthorization> T findByDeviceStateOrDeviceCode_TokenValueOrUserCode_TokenValue(String deviceState, String deviceCode, String userCode);
}
認可同意リポジトリ
次のリストは、複合主キーを形成する registeredClientId
および principalName
フィールドによって OAuth2UserConsent
を検索および削除できる OAuth2UserConsentRepository
を示しています。
import sample.redis.entity.OAuth2UserConsent;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2UserConsentRepository extends CrudRepository<OAuth2UserConsent, String> {
OAuth2UserConsent findByRegisteredClientIdAndPrincipalName(String registeredClientId, String principalName);
void deleteByRegisteredClientIdAndPrincipalName(String registeredClientId, String principalName);
}
コアサービスの実装
With the above entities and repositories, we can begin implementing the core services.
コアサービスは、ドメインオブジェクト (例: RegisteredClient ) とエンティティモデル表現 (例: OAuth2RegisteredClient ) 間の変換に ModelMapper ユーティリティクラスを使用します。 |
登録済みクライアントリポジトリ
次のリストは、OAuth2RegisteredClientRepository
を使用して OAuth2RegisteredClient
を永続化し、ModelMapper
ユーティリティクラスを使用して RegisteredClient
ドメインオブジェクトとの間でマップする RedisRegisteredClientRepository
を示しています。
import sample.redis.entity.OAuth2RegisteredClient;
import sample.redis.repository.OAuth2RegisteredClientRepository;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.util.Assert;
public class RedisRegisteredClientRepository implements RegisteredClientRepository {
private final OAuth2RegisteredClientRepository registeredClientRepository;
public RedisRegisteredClientRepository(OAuth2RegisteredClientRepository registeredClientRepository) {
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
this.registeredClientRepository = registeredClientRepository;
}
@Override
public void save(RegisteredClient registeredClient) {
Assert.notNull(registeredClient, "registeredClient cannot be null");
OAuth2RegisteredClient oauth2RegisteredClient = ModelMapper.convertOAuth2RegisteredClient(registeredClient);
this.registeredClientRepository.save(oauth2RegisteredClient);
}
@Nullable
@Override
public RegisteredClient findById(String id) {
Assert.hasText(id, "id cannot be empty");
return this.registeredClientRepository.findById(id).map(ModelMapper::convertRegisteredClient).orElse(null);
}
@Nullable
@Override
public RegisteredClient findByClientId(String clientId) {
Assert.hasText(clientId, "clientId cannot be empty");
OAuth2RegisteredClient oauth2RegisteredClient = this.registeredClientRepository.findByClientId(clientId);
return oauth2RegisteredClient != null ? ModelMapper.convertRegisteredClient(oauth2RegisteredClient) : null;
}
}
認可サービス
次のリストは、OAuth2AuthorizationGrantAuthorizationRepository
を使用して OAuth2AuthorizationGrantAuthorization
を永続化し、ModelMapper
ユーティリティクラスを使用して OAuth2Authorization
ドメインオブジェクトとの間でマップする RedisOAuth2AuthorizationService
を示しています。
import sample.redis.entity.OAuth2AuthorizationGrantAuthorization;
import sample.redis.repository.OAuth2AuthorizationGrantAuthorizationRepository;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.util.Assert;
public class RedisOAuth2AuthorizationService implements OAuth2AuthorizationService {
private final RegisteredClientRepository registeredClientRepository;
private final OAuth2AuthorizationGrantAuthorizationRepository authorizationGrantAuthorizationRepository;
public RedisOAuth2AuthorizationService(RegisteredClientRepository registeredClientRepository,
OAuth2AuthorizationGrantAuthorizationRepository authorizationGrantAuthorizationRepository) {
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
Assert.notNull(authorizationGrantAuthorizationRepository,
"authorizationGrantAuthorizationRepository cannot be null");
this.registeredClientRepository = registeredClientRepository;
this.authorizationGrantAuthorizationRepository = authorizationGrantAuthorizationRepository;
}
@Override
public void save(OAuth2Authorization authorization) {
Assert.notNull(authorization, "authorization cannot be null");
OAuth2AuthorizationGrantAuthorization authorizationGrantAuthorization = ModelMapper
.convertOAuth2AuthorizationGrantAuthorization(authorization);
this.authorizationGrantAuthorizationRepository.save(authorizationGrantAuthorization);
}
@Override
public void remove(OAuth2Authorization authorization) {
Assert.notNull(authorization, "authorization cannot be null");
this.authorizationGrantAuthorizationRepository.deleteById(authorization.getId());
}
@Nullable
@Override
public OAuth2Authorization findById(String id) {
Assert.hasText(id, "id cannot be empty");
return this.authorizationGrantAuthorizationRepository.findById(id)
.map(this::toOAuth2Authorization)
.orElse(null);
}
@Nullable
@Override
public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) {
Assert.hasText(token, "token cannot be empty");
OAuth2AuthorizationGrantAuthorization authorizationGrantAuthorization = null;
if (tokenType == null) {
authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository
.findByStateOrAuthorizationCode_TokenValue(token, token);
if (authorizationGrantAuthorization == null) {
authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository
.findByAccessToken_TokenValueOrRefreshToken_TokenValue(token, token);
}
if (authorizationGrantAuthorization == null) {
authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository
.findByIdToken_TokenValue(token);
}
if (authorizationGrantAuthorization == null) {
authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository
.findByDeviceStateOrDeviceCode_TokenValueOrUserCode_TokenValue(token, token, token);
}
}
else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) {
authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository.findByState(token);
if (authorizationGrantAuthorization == null) {
authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository
.findByDeviceState(token);
}
}
else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) {
authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository
.findByAuthorizationCode_TokenValue(token);
}
else if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenType)) {
authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository
.findByAccessToken_TokenValue(token);
}
else if (OidcParameterNames.ID_TOKEN.equals(tokenType.getValue())) {
authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository
.findByIdToken_TokenValue(token);
}
else if (OAuth2TokenType.REFRESH_TOKEN.equals(tokenType)) {
authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository
.findByRefreshToken_TokenValue(token);
}
else if (OAuth2ParameterNames.USER_CODE.equals(tokenType.getValue())) {
authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository
.findByUserCode_TokenValue(token);
}
else if (OAuth2ParameterNames.DEVICE_CODE.equals(tokenType.getValue())) {
authorizationGrantAuthorization = this.authorizationGrantAuthorizationRepository
.findByDeviceCode_TokenValue(token);
}
return authorizationGrantAuthorization != null ? toOAuth2Authorization(authorizationGrantAuthorization) : null;
}
private OAuth2Authorization toOAuth2Authorization(
OAuth2AuthorizationGrantAuthorization authorizationGrantAuthorization) {
RegisteredClient registeredClient = this.registeredClientRepository
.findById(authorizationGrantAuthorization.getRegisteredClientId());
OAuth2Authorization.Builder builder = OAuth2Authorization.withRegisteredClient(registeredClient);
ModelMapper.mapOAuth2AuthorizationGrantAuthorization(authorizationGrantAuthorization, builder);
return builder.build();
}
}
認可同意サービス
次のリストは、OAuth2UserConsentRepository
を使用して OAuth2UserConsent
を永続化し、ModelMapper
ユーティリティクラスを使用して OAuth2AuthorizationConsent
ドメインオブジェクトとの間でマップする RedisOAuth2AuthorizationConsentService
を示しています。
import sample.redis.entity.OAuth2UserConsent;
import sample.redis.repository.OAuth2UserConsentRepository;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.util.Assert;
public class RedisOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService {
private final OAuth2UserConsentRepository userConsentRepository;
public RedisOAuth2AuthorizationConsentService(OAuth2UserConsentRepository userConsentRepository) {
Assert.notNull(userConsentRepository, "userConsentRepository cannot be null");
this.userConsentRepository = userConsentRepository;
}
@Override
public void save(OAuth2AuthorizationConsent authorizationConsent) {
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
OAuth2UserConsent oauth2UserConsent = ModelMapper.convertOAuth2UserConsent(authorizationConsent);
this.userConsentRepository.save(oauth2UserConsent);
}
@Override
public void remove(OAuth2AuthorizationConsent authorizationConsent) {
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
this.userConsentRepository.deleteByRegisteredClientIdAndPrincipalName(
authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName());
}
@Nullable
@Override
public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) {
Assert.hasText(registeredClientId, "registeredClientId cannot be empty");
Assert.hasText(principalName, "principalName cannot be empty");
OAuth2UserConsent oauth2UserConsent = this.userConsentRepository
.findByRegisteredClientIdAndPrincipalName(registeredClientId, principalName);
return oauth2UserConsent != null ? ModelMapper.convertOAuth2AuthorizationConsent(oauth2UserConsent) : null;
}
}
コアサービスを構成する
次の例は、コアサービスを構成する方法を示しています。
import java.util.Arrays;
import sample.redis.convert.BytesToClaimsHolderConverter;
import sample.redis.convert.BytesToOAuth2AuthorizationRequestConverter;
import sample.redis.convert.BytesToUsernamePasswordAuthenticationTokenConverter;
import sample.redis.convert.ClaimsHolderToBytesConverter;
import sample.redis.convert.OAuth2AuthorizationRequestToBytesConverter;
import sample.redis.convert.UsernamePasswordAuthenticationTokenToBytesConverter;
import sample.redis.repository.OAuth2AuthorizationGrantAuthorizationRepository;
import sample.redis.repository.OAuth2RegisteredClientRepository;
import sample.redis.repository.OAuth2UserConsentRepository;
import sample.redis.service.RedisOAuth2AuthorizationConsentService;
import sample.redis.service.RedisOAuth2AuthorizationService;
import sample.redis.service.RedisRegisteredClientRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.convert.RedisCustomConversions;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
@EnableRedisRepositories("sample.redis.repository") (1)
@Configuration(proxyBeanMethods = false)
public class RedisConfig {
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new JedisConnectionFactory(); (2)
}
@Bean
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<byte[], byte[]> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
@Bean
public RedisCustomConversions redisCustomConversions() { (3)
return new RedisCustomConversions(Arrays.asList(new UsernamePasswordAuthenticationTokenToBytesConverter(),
new BytesToUsernamePasswordAuthenticationTokenConverter(),
new OAuth2AuthorizationRequestToBytesConverter(), new BytesToOAuth2AuthorizationRequestConverter(),
new ClaimsHolderToBytesConverter(), new BytesToClaimsHolderConverter()));
}
@Bean
public RedisRegisteredClientRepository registeredClientRepository(
OAuth2RegisteredClientRepository registeredClientRepository) {
return new RedisRegisteredClientRepository(registeredClientRepository); (4)
}
@Bean
public RedisOAuth2AuthorizationService authorizationService(RegisteredClientRepository registeredClientRepository,
OAuth2AuthorizationGrantAuthorizationRepository authorizationGrantAuthorizationRepository) {
return new RedisOAuth2AuthorizationService(registeredClientRepository,
authorizationGrantAuthorizationRepository); (5)
}
@Bean
public RedisOAuth2AuthorizationConsentService authorizationConsentService(
OAuth2UserConsentRepository userConsentRepository) {
return new RedisOAuth2AuthorizationConsentService(userConsentRepository); (6)
}
}
1 | sample.redis.repository ベースパッケージにある Spring Data Redis リポジトリをアクティブ化します。 |
2 | Jedis コネクターを使用します。 |
3 | Redis に永続化する前に、オブジェクトからハッシュへの変換を実行するカスタム Converter を登録します。 |
4 | アクティブ化された OAuth2RegisteredClientRepository に RedisRegisteredClientRepository を登録します。 |
5 | アクティブ化された OAuth2AuthorizationGrantAuthorizationRepository に RedisOAuth2AuthorizationService を登録します。 |
6 | アクティブ化された OAuth2UserConsentRepository に RedisOAuth2AuthorizationConsentService を登録します。 |