When we configure OAuth2, we will configure the resource server and authentication server. When our authorization service and authentication service are not in the same service, we can consider using RemoteTokenServices
.
If they are in the same service, you don’t need to configure tokenServices
, because when ResourceServerConfigurerAdapter
is configured, if tokenServices
is not configured, a default DefaultTokenServices
will be automatically configured. . The two tokenService
classes both implement the ResourceServerTokenServices
interface.
ResourceServerTokenServices
has a total of four implementation classes by default.
Actually, no matter RemoteTokenServices
or others, they all implement the ResourceServerTokenServices
interface to load access token credentials and retrieve token details.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public interface ResourceServerTokenServices {
/**
* Load the credentials for the specified access token.
*
* @param accessToken The access token value.
* @return The authentication for the access token.
* @throws AuthenticationException If the access token is expired
* @throws InvalidTokenException if the token isn't valid
*/
OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException;
/**
* Retrieve the full access token details from just the value.
*
* @param accessToken the token value
* @return the full access token with client id etc.
*/
OAuth2AccessToken readAccessToken(String accessToken);
}
|
So, I’ll take a step-by-step approach here, using RemoteTokenServices
as an example, to see how the process is done.
When you configure the OAuth2 resource server ResourceServerConfigurerAdapter
, you can configure tokenService
.
1
|
resources.tokenServices(xxx);
|
1
2
3
4
|
remoteTokenServices.setClientId(resourceServerProperties.getClientId());
remoteTokenServices.setClientSecret(resourceServerProperties.getClientSecret());
// 设置/oauth/check_token端点
remoteTokenServices.setCheckTokenEndpointUrl(resourceServerProperties.getTokenInfoUri());
|
Configure application.yml
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
# oauth2配置
security:
oauth2:
client:
# 客户端ID
client-id: ${OAUTH2_CLIENT_ID:lzhpo}
# 客户端秘钥(加密前)
client-secret: ${OAUTH2_CLIENT_SECRET:lzhpo1024}
# 授权类型
grant-type: ${OAUTH2_GRANT_TYPE:authorization_code,password,refresh_token,implicit,client_credentials}
# 权限范围
scope: ${OAUTH2_SCOPE:all}
# 用于密码模式,获取访问令牌的地址(org.springframework.security.oauth2.provider.endpoint.TokenEndpoint)
access-token-uri: ${OAUTH2_ACCESS_TOKEN_URI:http://localhost:9999/api/auth/oauth/token}
# 用于授权码模式,获取授权码的地址(org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint)
user-authorization-uri: ${OAUTH2_USER_AUTHORIZATION_URI:http://localhost:9999/api/auth/oauth/authorize}
resource:
# 资源服务器编号
id: ${spring.application.name}
# 校验访问令牌是否有效的地址(org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint.checkToken)
token-info-uri: ${OAUTH2_TOKEN_INFO_URI:http://localhost:9999/api/auth/oauth/check_token}
# 获取用户信息
user-info-uri: ${OAUTH2_USER_INFO_URI:http://localhost:9999/api/auth/oauth/check_user}
# 默认使用token-info-uri,可以设置为false以使用user-info-uri
prefer-token-info: true
|
Then configure on the authentication server (inherits the AuthorizationServerConfigurerAdapter
class).
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
|
/**
* Authorization server security
*
* <p>isAuthenticated()、permitAll() <br>
* If the endpoint /oauth/check_token is Authenticated, header Authorization is required. <br>
* e.g: {@code Authorization:Basic bHpocG86bHpocG8xMDI0}
*
* <pre>
* Reference:{@link AuthorizationServerProperties}
*
* Also can configure in application.properties or application.yml:
* {@code
* security.oauth2.authorization.token-key-access: isAuthenticated()
* security.oauth2.authorization.check-token-access: isAuthenticated()
* }
* </pre>
*
* @param security security
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
// Allow the client to send a form for permission authentication to obtain a token
.allowFormAuthenticationForClients()
// Endpoint: /oauth/token_key
// If you use jwt, the public key that can be obtained is used for token verification
.tokenKeyAccess("isAuthenticated()")
// Endpoint: /oauth/check_token
.checkTokenAccess("isAuthenticated()");
}
|
Configuring isAuthenticated()
means that authentication is required, set the value of Authorization
in the request header to the Base64 value of Basic
+ Client ID:Client Key
.
For example.
1
|
Authorization:Basic bHpocG86bHpocG8xMDI0
|
ResourceServerSecurityConfigurer
source code (org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer
)
The key code is here.
1
2
3
4
5
6
7
8
9
10
11
|
private ResourceServerTokenServices tokenServices(HttpSecurity http) {
if (resourceTokenServices != null) {
return resourceTokenServices;
}
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(clientDetails());
this.resourceTokenServices = tokenServices;
return tokenServices;
}
|
2. RemoteTokenServices
requests the /oauth/check_token
interface
RemoteTokenServices
source code (org.springframework.security.oauth2.provider.token.RemoteTokenServices
)
3. OAuth2ClientAuthenticationProcessingFilter
Get the result of the request and try to authenticate
Code location: org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter#attemptAuthentication
Key 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
|
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
OAuth2AccessToken accessToken;
try {
accessToken = restTemplate.getAccessToken();
} catch (OAuth2Exception e) {
BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e);
publish(new OAuth2AuthenticationFailureEvent(bad));
throw bad;
}
try {
// 这里就是前面`RemoteTokenServices`请求`/oauth/check_token`接口,拿到校验结果的
OAuth2Authentication result = tokenServices.loadAuthentication(accessToken.getValue());
if (authenticationDetailsSource!=null) {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue());
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());
result.setDetails(authenticationDetailsSource.buildDetails(request));
}
publish(new AuthenticationSuccessEvent(result));
return result;
}
catch (InvalidTokenException e) {
BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e);
publish(new OAuth2AuthenticationFailureEvent(bad));
throw bad;
}
}
|
It has 3 subclasses, namely org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
, org.springframework.security. ClientCredentialsTokenEndpointFilter
, org.springframework.security.oauth2.client.filter. OAuth2ClientAuthenticationProcessingFilter
UsernamePasswordAuthenticationFilter
: A filter to handle authentication form submissions.
ClientCredentialsTokenEndpointFilter
: Filter for OAuth2 token endpoints and authentication endpoints.
OAuth2ClientAuthenticationProcessingFilter
: Gets the OAuth2 access token from the authorization server and loads the authentication object into the SecurityContext
.
We are looking at how it is loaded into the SecurityContext
, so we focus on the OAuth2ClientAuthenticationProcessingFilter
filter.
org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter
is the client filter for OAuth2. RemoteServiceto load the identity verification information we got earlier into the
SecurityContext`.
It inherits from the org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter
abstract class.
attemptAuthentication
is an abstract method in the AbstractAuthenticationProcessingFilter
abstract class that is given to a subclass to attempt authentication.
Since AbstractAuthenticationProcessingFilter
is, after all, a filter that inherits from the GenericFilterBean
implementation, take a look at the filter
method.
I’ll post the code here to make it easier for me to write comments in the 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
|
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
try {
// 交给子类实现的尝试进行认证的抽象方法,就上面我说的OAuth2ClientAuthenticationProcessingFilter过滤器的attemptAuthentication
Authentication authenticationResult = attemptAuthentication(request, response);
if (authenticationResult == null) {
// return immediately as subclass has indicated that it hasn't completed
return;
}
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
// Authentication success
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
// 身份认证成功
successfulAuthentication(request, response, chain, authenticationResult);
}
catch (InternalAuthenticationServiceException failed) {
this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
unsuccessfulAuthentication(request, response, failed);
}
catch (AuthenticationException ex) {
// Authentication failed
unsuccessfulAuthentication(request, response, ex);
}
}
|
successfulAuthentication
Source code.
This is the purpose of this article, to load authentication information: SecurityContextHolder.getContext().setAuthentication(authResult);
1
2
3
4
5
6
7
8
9
10
11
12
13
|
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
// 装载身份验证信息
SecurityContextHolder.getContext().setAuthentication(authResult);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
}
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
|
Reference
http://www.lzhpo.com/article/170