操作方法:使用 Redis 实现核心服务
本指南介绍如何使用 Redis 实现 Spring Authorization Server 的核心服务。 本指南的目的是为自行实施这些服务提供一个起点,以便您可以进行修改以满足您的需要。
本指南中提供的代码示例位于 redis 子目录下的 documentation samples 目录中。 |
定义实体模型
下面定义了 和 domain 类的实体模型表示形式。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;
}
}
}
单击上面代码示例中的“Expand folded text”图标以显示完整示例。 |
授权授予基本实体
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)
以下清单显示了实体,该实体扩展了 ,并定义了 OAuth 2.0 授权类型的其他属性。OAuth2AuthorizationCodeGrantAuthorization
OAuth2AuthorizationGrantAuthorization
authorization_code
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)
以下清单显示了该实体,该实体扩展了 ,并定义了 OpenID Connect 1.0 授权类型的其他属性。OidcAuthorizationCodeGrantAuthorization
OAuth2AuthorizationCodeGrantAuthorization
authorization_code
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;
}
}
}
客户端凭据授予实体
以下清单显示了授权类型的实体,该实体扩展了 。OAuth2ClientCredentialsGrantAuthorization
OAuth2AuthorizationGrantAuthorization
client_credentials
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);
}
}
设备代码授予实体
以下清单显示了实体,该实体扩展了 ,并定义了授权类型的其他属性。OAuth2DeviceCodeGrantAuthorization
OAuth2AuthorizationGrantAuthorization
urn:ietf:params:oauth:grant-type:device_code
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);
}
}
}
代币交换赠款实体
以下清单显示了授权类型的实体,该实体扩展了 。OAuth2TokenExchangeGrantAuthorization
OAuth2AuthorizationGrantAuthorization
urn:ietf:params:oauth:grant-type:token-exchange
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 存储库
通过仔细检查每个核心服务的接口并审查实施,我们可以得出支持每个接口的 Redis 版本所需的最小查询集。Jdbc
已注册的客户端存储库
下面的清单显示了 ,它能够通过 and 字段找到 OAuth2RegisteredClient
。OAuth2RegisteredClientRepository
id
clientId
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);
}
授权授权存储库
下面的清单显示了 ,它能够通过字段以及 、 和 值找到 OAuth2AuthorizationGrantAuthorization
。OAuth2AuthorizationGrantAuthorizationRepository
id
state
authorizationCode
accessToken
refreshToken
idToken
deviceState
userCode
deviceCode
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);
}
授权同意存储库
下面的清单显示了 ,它能够通过构成复合主键的 and 字段来查找和删除 OAuth2UserConsent
。OAuth2UserConsentRepository
registeredClientId
principalName
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);
}
实施核心服务
核心服务使用 Utility 类与域对象(例如 )和实体模型表示形式(例如 )进行相互转换。ModelMapper RegisteredClient OAuth2RegisteredClient |
已注册的客户端存储库
下面的清单显示了 ,它使用 OAuth2RegisteredClientRepository
来持久化 OAuth2RegisteredClient
,并使用 Utility 类映射到 RegisteredClient
域对象或从 RegisteredClient 域对象映射。RedisRegisteredClientRepository
ModelMapper
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
,并使用实用程序类与 OAuth2Authorization
域对象相互映射。RedisOAuth2AuthorizationService
ModelMapper
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
,并使用实用程序类映射到 OAuth2AuthorizationConsent 域对象或从 OAuth2AuthorizationConsent
域对象映射。RedisOAuth2AuthorizationConsentService
ModelMapper
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 | 激活基础包下的 Spring Data Redis 存储库。sample.redis.repository |
2 | 使用 Jedis 连接器。 |
3 | 注册在持久保存到 Redis 之前执行 Object-to-Hash 转换的自定义 。Converter |
4 | 向已激活的 注册 。RedisRegisteredClientRepository OAuth2RegisteredClientRepository |
5 | 向已激活的 注册 。RedisOAuth2AuthorizationService OAuth2AuthorizationGrantAuthorizationRepository |
6 | 向已激活的 注册 。RedisOAuth2AuthorizationConsentService OAuth2UserConsentRepository |