进学阁

业精于勤荒于嬉,行成于思毁于随

0%

自定义密码模式

简介

在官方的说明中我们可以看到OAuth 2.1 废弃了 OAuth 2.0 中的密码模式,只提供了三种授权类型:授权码、刷新令牌、客户端凭证。

oauth2.1中授权码模式的流程为:

画板

由上图可知我们想要沿用密码模式会有两种方案

1.自定义密码模式

2.修改授权码模式,自定义授权页面,使用授权页面自动提交获取token。

自定义密码模式:

在官网中对自定义配置的描述

这里可以看到在授权断点是可以实现自定义的,于是就有了接下来的思路:

直接略过前面的密码验证、用户确认, 直接自定义授权端点。在自定义的端点中处理授权信息,打开

7 authorizationEndpoint

这里可以看到自定义一个oauth2的授权请求需要定义一个处理器 一个主处理器,但是官方并没有给具体的例子所以需要看下其他授权端点的做法来完成自定义,以code授权的源码为例分析

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
public final class OAuth2AuthorizationCodeAuthenticationConverter implements AuthenticationConverter {

@Nullable
@Override
public Authentication convert(HttpServletRequest request) {
// grant_type (REQUIRED)
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
if (!AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(grantType)) {
return null;
}

Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();

MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);

// code (REQUIRED)
String code = parameters.getFirst(OAuth2ParameterNames.CODE);
if (!StringUtils.hasText(code) ||
parameters.get(OAuth2ParameterNames.CODE).size() != 1) {
OAuth2EndpointUtils.throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
OAuth2ParameterNames.CODE,
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
}

// redirect_uri (REQUIRED)
// Required only if the "redirect_uri" parameter was included in the authorization request
String redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI);
if (StringUtils.hasText(redirectUri) &&
parameters.get(OAuth2ParameterNames.REDIRECT_URI).size() != 1) {
OAuth2EndpointUtils.throwError(
OAuth2ErrorCodes.INVALID_REQUEST,
OAuth2ParameterNames.REDIRECT_URI,
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
}

Map<String, Object> additionalParameters = new HashMap<>();
parameters.forEach((key, value) -> {
if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) &&
!key.equals(OAuth2ParameterNames.CLIENT_ID) &&
!key.equals(OAuth2ParameterNames.CODE) &&
!key.equals(OAuth2ParameterNames.REDIRECT_URI)) {
additionalParameters.put(key, value.get(0));
}
});

return new OAuth2AuthorizationCodeAuthenticationToken(
code, clientPrincipal, redirectUri, additionalParameters);
}

}

这里可以看出Converter的主要作用是对参数整理然后生成一个新得OAuth2AuthorizationCodeAuthenticationToken 对象,接着看下OAuth2AuthorizationCodeAuthenticationToken的代码

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
public class OAuth2AuthorizationCodeAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
private final String code;
private final String redirectUri;

/**
* Constructs an {@code OAuth2AuthorizationCodeAuthenticationToken} using the provided parameters.
*
* @param code the authorization code
* @param clientPrincipal the authenticated client principal
* @param redirectUri the redirect uri
* @param additionalParameters the additional parameters
*/
public OAuth2AuthorizationCodeAuthenticationToken(String code, Authentication clientPrincipal,
@Nullable String redirectUri, @Nullable Map<String, Object> additionalParameters) {
super(AuthorizationGrantType.AUTHORIZATION_CODE, clientPrincipal, additionalParameters);
Assert.hasText(code, "code cannot be empty");
this.code = code;
this.redirectUri = redirectUri;
}

/**
* Returns the authorization code.
*
* @return the authorization code
*/
public String getCode() {
return this.code;
}

/**
* Returns the redirect uri.
*
* @return the redirect uri
*/
@Nullable
public String getRedirectUri() {
return this.redirectUri;
}
}

这里主要是保存参数对象的。

接着再看下主处理的代码

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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
public final class OAuth2AuthorizationCodeAuthenticationProvider implements AuthenticationProvider {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE =
new OAuth2TokenType(OAuth2ParameterNames.CODE);
private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE =
new OAuth2TokenType(OidcParameterNames.ID_TOKEN);
private final OAuth2AuthorizationService authorizationService;
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;

/**
* Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the provided parameters.
*
* @param authorizationService the authorization service
* @param tokenGenerator the token generator
* @since 0.2.3
*/
public OAuth2AuthorizationCodeAuthenticationProvider(OAuth2AuthorizationService authorizationService,
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
Assert.notNull(authorizationService, "authorizationService cannot be null");
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
this.authorizationService = authorizationService;
this.tokenGenerator = tokenGenerator;
}

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
(OAuth2AuthorizationCodeAuthenticationToken) authentication;

OAuth2ClientAuthenticationToken clientPrincipal =
getAuthenticatedClientElseThrowInvalidClient(authorizationCodeAuthentication);
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();

OAuth2Authorization authorization = this.authorizationService.findByToken(
authorizationCodeAuthentication.getCode(), AUTHORIZATION_CODE_TOKEN_TYPE);
if (authorization == null) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
}
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode =
authorization.getToken(OAuth2AuthorizationCode.class);

OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
OAuth2AuthorizationRequest.class.getName());

if (!registeredClient.getClientId().equals(authorizationRequest.getClientId())) {
if (!authorizationCode.isInvalidated()) {
// Invalidate the authorization code given that a different client is attempting to use it
authorization = OAuth2AuthenticationProviderUtils.invalidate(authorization, authorizationCode.getToken());
this.authorizationService.save(authorization);
}
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
}

if (StringUtils.hasText(authorizationRequest.getRedirectUri()) &&
!authorizationRequest.getRedirectUri().equals(authorizationCodeAuthentication.getRedirectUri())) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
}

if (!authorizationCode.isActive()) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
}

// @formatter:off
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.principal(authorization.getAttribute(Principal.class.getName()))
.providerContext(ProviderContextHolder.getProviderContext())
.authorization(authorization)
.authorizedScopes(authorization.getAttribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME))
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrant(authorizationCodeAuthentication);
// @formatter:on

OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.from(authorization);

// ----- Access token -----
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
if (generatedAccessToken == null) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
"The token generator failed to generate the access token.", ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
if (generatedAccessToken instanceof ClaimAccessor) {
authorizationBuilder.token(accessToken, (metadata) ->
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims()));
} else {
authorizationBuilder.accessToken(accessToken);
}

// ----- Refresh token -----
OAuth2RefreshToken refreshToken = null;
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
// Do not issue refresh token to public client
!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {

tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
"The token generator failed to generate the refresh token.", ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
authorizationBuilder.refreshToken(refreshToken);
}

// ----- ID token -----
OidcIdToken idToken;
if (authorizationRequest.getScopes().contains(OidcScopes.OPENID)) {
tokenContext = tokenContextBuilder.tokenType(ID_TOKEN_TOKEN_TYPE).build();
OAuth2Token generatedIdToken = this.tokenGenerator.generate(tokenContext);
if (!(generatedIdToken instanceof Jwt)) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
"The token generator failed to generate the ID token.", ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
idToken = new OidcIdToken(generatedIdToken.getTokenValue(), generatedIdToken.getIssuedAt(),
generatedIdToken.getExpiresAt(), ((Jwt) generatedIdToken).getClaims());
authorizationBuilder.token(idToken, (metadata) ->
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims()));
} else {
idToken = null;
}

authorization = authorizationBuilder.build();

// Invalidate the authorization code as it can only be used once
authorization = OAuth2AuthenticationProviderUtils.invalidate(authorization, authorizationCode.getToken());

this.authorizationService.save(authorization);

Map<String, Object> additionalParameters = Collections.emptyMap();
if (idToken != null) {
additionalParameters = new HashMap<>();
additionalParameters.put(OidcParameterNames.ID_TOKEN, idToken.getTokenValue());
}

return new OAuth2AccessTokenAuthenticationToken(
registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);
}

@Override
public boolean supports(Class<?> authentication) {
return OAuth2AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication);
}

}

这里可以看到token的生成详细信息。

自定义授权扩展

以此为参展自定义一个模式总共需要三个类

OAuth2PasswordAuthenticationConverter

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
public class OAuth2PasswordAuthenticationConverter  implements AuthenticationConverter {
@Override
public Authentication convert(HttpServletRequest request) {
// grant_type (REQUIRED)
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
if (!AuthorizationGrantType.PASSWORD.getValue().equals(grantType)) {
return null;
}

Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();

MultiValueMap<String, String> parameters = getParameters(request);

// username (REQUIRED)
String username = parameters.getFirst(OAuth2ParameterNames.USERNAME);
if (StrUtil.isEmpty(username) ||
parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) {
throw new LuckOAuth2AuthencticationException(ResponseStatusEnum.ACCOUNT_NOT_EXIST, "username cannot be null");
}

// password (REQUIRED)
String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);
if (StrUtil.isEmpty(password) ||
parameters.get(OAuth2ParameterNames.PASSWORD).size() != 1) {
throw new LuckOAuth2AuthencticationException(ResponseStatusEnum.PASSWORD_INCORRECT, "password cannot be null");
}

Map<String, Object> additionalParameters = new HashMap<>();
parameters.forEach((key, value) -> {
if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) &&
!key.equals(OAuth2ParameterNames.USERNAME) &&
!key.equals(OAuth2ParameterNames.PASSWORD)) {
additionalParameters.put(key, value.get(0));
}
});
return new OAuth2PasswordAuthenticationToken(username, password, clientPrincipal, additionalParameters);
}


public MultiValueMap<String, String> getParameters(HttpServletRequest request) {
Map<String, String[]> parameterMap = request.getParameterMap();
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
parameterMap.forEach((key, values) -> {
if (values.length > 0) {
for (String value : values) {
parameters.add(key, value);
}
}
});
return parameters;
}
}

OAuth2PasswordAuthenticationToken

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class OAuth2PasswordAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
@Getter
private final String username;
@Getter
private final String password;
/**
* Constructs an {@code OAuth2PasswordAuthenticationToken} using the provided parameters.
*
* @param username the username
* @param password the password
* @param clientPrincipal the authenticated client principal
* @param additionalParameters the additional parameters
*/
public OAuth2PasswordAuthenticationToken(String username, String password, Authentication clientPrincipal,
Map<String, Object> additionalParameters) {
super(AuthorizationGrantType.PASSWORD, clientPrincipal, additionalParameters);
Assert.hasText(username, "username cannot be empty");
Assert.hasText(password, "password cannot be empty");
this.username = username;
this.password = password;
}
}

OAuth2PasswordAuthenticationProvider

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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
public class OAuth2PasswordAuthenticationProvider implements AuthenticationProvider {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
private final AuthenticationManager authenticationManager;
private final OAuth2AuthorizationService authorizationService;
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
/**
* Constructs an {@code OAuth2PasswordAuthenticationProvider} using the provided parameters.
*
* @param authorizationService the authorization service
* @param tokenGenerator the token generator
*/
public OAuth2PasswordAuthenticationProvider(AuthenticationManager authenticationManager,
OAuth2AuthorizationService authorizationService,
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
Assert.notNull(authorizationService, "authorizationService cannot be null");
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
this.authenticationManager = authenticationManager;
this.authorizationService = authorizationService;
this.tokenGenerator = tokenGenerator;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2PasswordAuthenticationToken passwordAuthentication = (OAuth2PasswordAuthenticationToken) authentication;

OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(passwordAuthentication);
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
Assert.notNull(registeredClient, "registeredClient cannot be null");

if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.PASSWORD)) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
}
if (log.isTraceEnabled()) {
log.trace("Retrieved registered client");
}

// Attempts to authenticate the passwordAuthentication
Authentication authenticate = authenticate(passwordAuthentication);

DefaultOAuth2TokenContext.Builder tokenContextBuilder = getTokenContextBuilder(passwordAuthentication,
registeredClient, authenticate);

OAuth2Authorization.Builder authorizationBuilder = getAuthorizationBuilder(passwordAuthentication,
registeredClient);

OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();

// ----- Access token -----
OAuth2AccessToken accessToken = getAccessToken(authorizationBuilder, tokenContext);

// ----- Refresh token -----
OAuth2RefreshToken refreshToken = getRefreshToken(clientPrincipal, registeredClient, tokenContextBuilder, authorizationBuilder);

return new OAuth2AccessTokenAuthenticationToken(
registeredClient, clientPrincipal, accessToken, refreshToken, passwordAuthentication.getAdditionalParameters());
}

private Authentication authenticate(OAuth2PasswordAuthenticationToken passwordAuthentication) {
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(passwordAuthentication.getUsername(),
passwordAuthentication.getPassword());
Authentication authenticate;
try {
authenticate = authenticationManager.authenticate(token);
} catch (Exception e) {
throw oAuth2AuthenticationException(e);
}

return authenticate;
}

private static DefaultOAuth2TokenContext.Builder getTokenContextBuilder(
OAuth2PasswordAuthenticationToken passwordAuthentication,
RegisteredClient registeredClient,
Authentication authenticate) {
return DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.principal(authenticate)
.providerContext(ProviderContextHolder.getProviderContext())
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.authorizationGrant(passwordAuthentication);
}

private static OAuth2Authorization.Builder getAuthorizationBuilder(
OAuth2PasswordAuthenticationToken passwordAuthentication,
RegisteredClient registeredClient) {
return OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(passwordAuthentication.getName())
.authorizationGrantType(AuthorizationGrantType.PASSWORD);
}

private OAuth2AccessToken getAccessToken(OAuth2Authorization.Builder authorizationBuilder,
OAuth2TokenContext tokenContext) {
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
if (generatedAccessToken == null) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
"The token generator failed to generate the access token.", ERROR_URI);
throw new OAuth2AuthenticationException(error);
}

if (log.isTraceEnabled()) {
log.trace("Generated access token");
}

OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
if (generatedAccessToken instanceof ClaimAccessor) {
authorizationBuilder.token(accessToken, (metadata) ->
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME,
((ClaimAccessor) generatedAccessToken).getClaims()));
} else {
authorizationBuilder.accessToken(accessToken);
}
return accessToken;
}

private OAuth2RefreshToken getRefreshToken(OAuth2ClientAuthenticationToken clientPrincipal,
RegisteredClient registeredClient,
DefaultOAuth2TokenContext.Builder tokenContextBuilder,
OAuth2Authorization.Builder authorizationBuilder) {
OAuth2TokenContext tokenContext;
OAuth2RefreshToken refreshToken = null;
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
// Do not issue refresh token to public client
!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {

tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
"The token generator failed to generate the refresh token.",ERROR_URI);
throw new OAuth2AuthenticationException(error);
}

if (log.isTraceEnabled()) {
log.trace("Generated refresh token");
}

refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
authorizationBuilder.refreshToken(refreshToken);

this.authorizationService.save(authorizationBuilder.build());

if (log.isTraceEnabled()) {
log.trace("Saved authorization");
}
}
return refreshToken;
}

@Override
public boolean supports(Class<?> authentication) {
return OAuth2PasswordAuthenticationToken.class.isAssignableFrom(authentication);
}

/**
* OAuth2 认证失败处理器只能处理 OAuth2AuthenticationException,故转换
*
* @param authenticationException 身份验证异常
* @return {@link OAuth2AuthenticationException}
*/
private OAuth2AuthenticationException oAuth2AuthenticationException(Exception authenticationException) {
if (authenticationException instanceof UsernameNotFoundException) {
return new LuckOAuth2AuthencticationException(ResponseStatusEnum.ACCOUNT_NOT_EXIST);
}
if (authenticationException instanceof BadCredentialsException) {
return new LuckOAuth2AuthencticationException(ResponseStatusEnum.USERNAME_OR_PASSWORD_INCORRECT);
}
if (authenticationException instanceof LockedException) {
return new LuckOAuth2AuthencticationException(ResponseStatusEnum.ACCOUNT_FROZEN);
}
if (authenticationException instanceof AccountExpiredException) {
return new LuckOAuth2AuthencticationException(ResponseStatusEnum.ACCOUNT_EXPIRED);
}
if (authenticationException instanceof CredentialsExpiredException) {
return new LuckOAuth2AuthencticationException(ResponseStatusEnum.PASSWORD_EXPIRED);
}
if (authenticationException instanceof InternalAuthenticationServiceException) {
if (authenticationException.getCause() instanceof DisabledException) {
return new LuckOAuth2AuthencticationException(ResponseStatusEnum.ACCOUNT_DISABLE);
}
}
return new LuckOAuth2AuthencticationException(ResponseStatusEnum.IDENTITY_VERIFICATION_FAILED);
}
public OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
OAuth2ClientAuthenticationToken clientPrincipal = null;
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
}
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
return clientPrincipal;
}
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
}
}

当然如果需要自定义claims可以添加一个自定义OAuth2TokenCustomizer

这里以OAuth2TokenCustomizerImpl为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class OAuth2TokenCustomizerImpl implements OAuth2TokenCustomizer<JwtEncodingContext> {
@Override
public void customize(JwtEncodingContext context) {
JwtClaimsSet.Builder builder = context.getClaims();

// 客户端模式不返回具体用户信息
if (SecurityConstants.CLIENT_CREDENTIALS.equals(context.getAuthorizationGrantType().getValue())) {
return;
}

User user = (User) context.getPrincipal().getPrincipal();
builder.claims((claims) -> {
claims.put("username", user.getUsername());
claims.put("authorities", user.getAuthorities().stream().map(GrantedAuthority::getAuthority).toArray());
});
}
}

自定义鉴权成功或者鉴权失败代码可以参照

DefaultAuthenticationFailureHandler

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
public class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandler {

private static final String AUTHENTICATION_METHOD = "authentication_method";
private static final String CREDENTIALS = "credentials";
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
AjaxResult<?> error = createError(exception);

ServletServerHttpResponse servletServerHttpResponse = new ServletServerHttpResponse(response);
servletServerHttpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
HttpEndpointUtils.writeWithMessageConverters(error, servletServerHttpResponse);
}

/**
* 创建 error
*
* @param exception /
* @return /
*/
private AjaxResult<?> createError(AuthenticationException exception) {
AjaxResult<?> error = null;

if (exception instanceof LuckOAuth2AuthencticationException) {
error = AjaxResult.error(((LuckOAuth2AuthencticationException) exception).getCode(),
exception.getMessage());
} else if (exception instanceof OAuth2AuthenticationException) {
OAuth2AuthenticationException oAuth2AuthenticationException= (OAuth2AuthenticationException) exception;
String errorCode = oAuth2AuthenticationException.getError().getErrorCode();
String description = oAuth2AuthenticationException.getError().getDescription();
if (OAuth2ErrorCodes.INVALID_CLIENT.equals(errorCode)){
if (StrUtil.isEmpty(description)) {
return AjaxResult.error(ResponseStatusEnum.INVALID_GRANT, "无效的客户端");
} else if (description.contains(OAuth2ParameterNames.CLIENT_ID)) {
return AjaxResult.error(ResponseStatusEnum.CLIENT_NOT_EXIST);
} else if (description.contains(AUTHENTICATION_METHOD)) {
return AjaxResult.error(ResponseStatusEnum.AUTHORIZATION_DENIED);
} else if (description.contains(CREDENTIALS)) {
return AjaxResult.error(ResponseStatusEnum.CLIENT_PASSWORD_EMPTY);
} else if (description.contains(OAuth2ParameterNames.CLIENT_SECRET)) {
return AjaxResult.error(ResponseStatusEnum.CLIENT_PASSWORD_INCORRECT);
}
}
switch (errorCode) {
case OAuth2ErrorCodes.UNSUPPORTED_GRANT_TYPE :
return AjaxResult.error(ResponseStatusEnum.UNSUPPORTED_GRANT_TYPE);
case OAuth2ErrorCodes.INVALID_REQUEST : {
if (StrUtil.isEmpty(description)) {
return AjaxResult.error(ResponseStatusEnum.INVALID_GRANT, "无效的客户端");
} else if (description.contains(OAuth2ParameterNames.GRANT_TYPE)) {
return AjaxResult.error(ResponseStatusEnum.GRANT_TYPE_EMPTY);
}
}
case OAuth2ErrorCodes.INVALID_GRANT : return AjaxResult.error(ResponseStatusEnum.INVALID_GRANT);
case OAuth2ErrorCodes.INVALID_SCOPE : return AjaxResult.error(ResponseStatusEnum.INVALID_SCOPE);
case OAuth2ErrorCodes.UNAUTHORIZED_CLIENT :
return AjaxResult.error(ResponseStatusEnum.UNAUTHORIZED_CLIENT);
default : error = AjaxResult.error(ResponseStatusEnum.USER_LOGIN_ABNORMAL.getCode(),
oAuth2AuthenticationException.getError().getErrorCode());
}
} else {
error = AjaxResult.error(ResponseStatusEnum.USER_LOGIN_ABNORMAL, exception.getLocalizedMessage());
}

return error;
}

}

DefaultAuthenticationSuccessHandler

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
public class DefaultAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

/**
* 指定响应数据不使用 ResponseData 封装的参数名称
* Swagger 登录时可用
*/
private static final String NO_RESPONSE_DATA_PARAM_NAME = "no_response_data";

private final Converter<OAuth2AccessTokenResponse, Map<String, Object>> accessTokenResponseParametersConverter =
new DefaultOAuth2AccessTokenResponseMapConverter();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication;
OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken();
OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken();
Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();
OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
.tokenType(accessToken.getTokenType()).scopes(accessToken.getScopes());
if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
}
if (refreshToken != null) {
builder.refreshToken(refreshToken.getTokenValue());
}
if (!CollectionUtils.isEmpty(additionalParameters)) {
builder.additionalParameters(additionalParameters);
}
OAuth2AccessTokenResponse accessTokenResponse = builder.build();
Map<String, Object> tokenResponseParameters = this.accessTokenResponseParametersConverter.convert(accessTokenResponse);
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);

boolean noResponseData = Boolean.parseBoolean(request.getParameter(NO_RESPONSE_DATA_PARAM_NAME));
if (noResponseData) {
HttpEndpointUtils.writeWithMessageConverters(tokenResponseParameters, httpResponse);
} else {
AjaxResult<Map<String, Object>> responseData = AjaxResult.ok(tokenResponseParameters);
HttpEndpointUtils.writeWithMessageConverters(responseData, httpResponse);
}
}
}

当然如果你需要自定义获取用户信息重写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
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
  @Autowired
JdbcTemplate jdbcTemplate;
@Autowired
RedisService redisService;

/**
* 这是个Spring security 的过滤器链,默认会配置
* <p>
* OAuth2 Authorization endpoint
* <p>
* OAuth2 Token endpoint
* <p>
* OAuth2 Token Introspection endpoint
* <p>
* OAuth2 Token Revocation endpoint
* <p>
* OAuth2 Authorization Server Metadata endpoint
* <p>
* JWK Set endpoint
* <p>
* OpenID Connect 1.0 Provider Configuration endpoint
* <p>
* OpenID Connect 1.0 UserInfo endpoint
* 这些协议端点,只有配置了他才能够访问的到接口地址(类似mvc的controller)。
*
* @param http
* @return
* @throws Exception
*/
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.authorizationService(authorizationService())
.tokenEndpoint(tokenEndpoint->tokenEndpoint
.accessTokenRequestConverter(new OAuth2PasswordAuthenticationConverter())
.accessTokenResponseHandler(responseDataAuthenticationSuccessHandler())
.errorResponseHandler(authenticationFailureHandler()));
DefaultSecurityFilterChain chain = http
.requestMatcher(authorizationServerConfigurer.getEndpointsMatcher())
.authorizeHttpRequests(authorize ->
authorize.anyRequest().authenticated()
)
.csrf(CsrfConfigurer::disable)
.build();
addingAdditionalAuthenticationProvider(http);
return chain;
}

/**
* 这个也是个Spring Security的过滤器链,用于Spring Security的身份认证。
* @param http
* @return
* @throws Exception
*/
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
// Form login handles the redirect to the login page from the
// authorization server filter chain
.formLogin(Customizer.withDefaults());

return http.build();
}
@Bean
public AuthenticationSuccessHandler responseDataAuthenticationSuccessHandler() {
return new DefaultAuthenticationSuccessHandler();
}

@Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
return new DefaultAuthenticationFailureHandler();
}

/**
* 配置用户信息,或者配置用户数据来源,主要用于用户的检索。
* @return
*/
@Bean
public UserDetailsService userDetailsService() {
// UserDetails userDetails = User.withDefaultPasswordEncoder()
// .username("user")
// .password("password")
// .roles("USER")
// .build();
// return new InMemoryUserDetailsManager();

return new OAuth2UserDetailsManager();
}


/**
* oauth2 用于第三方认证,RegisteredClientRepository 主要用于管理第三方(每个第三方就是一个客户端)
* @return
*/
@Bean
public RegisteredClientRepository registeredClientRepository() {
// RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
// .clientId("messaging-client")
// .clientSecret("{noop}secret")
// .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
// .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
// .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
// .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
// .redirectUri("http://127.0.0.1:1067/login/oauth2/code/messaging-client-oidc")
// .redirectUri("http://127.0.0.1:1067/authorized")
// .scope(OidcScopes.OPENID)
// .scope("message.read")
// .scope("message.write")
// .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
// .build();
//
// return new InMemoryRegisteredClientRepository(registeredClient);
return new JdbcRegisteredClientRepository(jdbcTemplate);
}

@Bean
public OAuth2AuthorizationService authorizationService(){
return new OAuth2RedisAuthorizationService(redisService);
}
/**
* 用于给access_token签名使用。
* @return
*/
@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);
}

/**
* 生成秘钥对,为jwkSource提供服务。
* @return
*/
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;
}

/**
* 配置Authorization Server实例
* @return
*/
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder().build();
}

private static void addingAdditionalAuthenticationProvider(HttpSecurity http) {
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);
OAuth2TokenGenerator<?> tokenGenerator = http.getSharedObject(OAuth2TokenGenerator.class);

OAuth2PasswordAuthenticationProvider passwordAuthenticationProvider =
new OAuth2PasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator);
http.authenticationProvider(passwordAuthenticationProvider);
}

备注:

registeredClientRepository 客户端信息

authorizationService token新增和处理

providerSettings 访问端点配置 默认为:

addingAdditionalAuthenticationProvider

OAuth2TokenEndpointConfigurer 并没有提供批量设置 AuthenticationProvider 的方法,即,如果你要同时配置 authorization_code、refresh_token、client_credentials、password 的 AuthenticationProvider 的话,你需要调用多次 accessTokenResponseHandler() 但是HttpSecurity提供了一个 authenticationProvider() 可以动态添加 AuthenticationProvider

总结:

新版本的spring-authorization-server不同于原来的oauth2的版本,去除了密码模式,让很多简单的自定义登陆变的很麻烦,但是其中可自定义的地方有很多

Converter :从访问令牌中获取参数,并格式化

Provider :主处理器用于生成token以及保存token

AuthorizationService :保存token信息,获取token信息

ResponseHandler :返回信息处理

Token:格式化访问信息为Provider提供数据

Customizer :自定义claims信息数据可以从context.getPrincipal().getPrincipal()中获取

Manager :自定义获取用户信息逻辑

当然如果你不希望这么麻烦想要更简单的实现登陆,可以自定义consentPage页面然后在自定义的consentPage中增加脚本自动提交表单获取令牌