Java 安全框架OAuth2.0

客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0定义了四种授权方式:

  • 密码模式(resource owner password credentials): 用户向客户端提供自己的用户名和密码。客户端使用这些信息,向服务商提供商索要授权。(一般不用)
  • 客户端模式(client credentials): 指客户端以自己的名义,而不是以用户的名义,向服务提供商进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求服务提供商提供服务,其实不存在授权问题。
  • 授权码模式(authorization code): 授权码模式,是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与服务提供商的认证服务器进行互动。
  • 简化模式(implicit): 不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了授权码这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。

资源拥有着(ResourceOwner): 资源的拥有人,想要分享某些资源给第三方应用
客户应用: 通常是一个Web或者无线应用,需要访问用户的受保护资源
资源服务器ResourceServer: Web站点或者Web service API,用户的受保护数据存在此处
授权服务器AuthenticationServer:客户应用成功认证并获得授权之后,向客户应用颁发访问令牌Access Token

客户凭证: 客户的clientId和密码用于认证客户,相当于给不同机构分配机构ID
令牌: 授权服务器在接收到客户请求后,颁发的访问令牌
作用域: 客户请求访问令牌时,由资源拥有者额外指定的细分权限(permission)

授权码(Authorization Code Token): 仅用于授权码授权类型,用于交换获取访问令牌和刷新令牌
刷新令牌(Refresh Token): 用于去授权服务器获取一个新的访问令牌
访问令牌(Access Token): 用于去代表一个用户或服务直接去访问受保护的资源.
Bearer Token: 不管谁拿到Token,都可以访问资源
Proof of Possession(PoP Token): 可以校验client是否对Token有名确的拥有权

spring-security-oauth三种存储策略:

  • InMemoryTokenStore 令牌存储在服务器内存中,因此存在授权服务器重新启动时丢失令牌的风险。
  • JwtTokenStore 所有授权和访问授权数据都被编码到令牌本身中,并且此类令牌不会在任何地方持久化。此类令牌使用解码器进行即时验证,并且依赖于JwtAccessTokenConverter 。
  • JdbcTokenStore 令牌数据存储在关系数据库中。使用此令牌存储,可以安全地重新启动授权服务器。令牌也可以在服务器之间轻松共享,并且可以被吊销。注意,要使用JdbcTokenStore,我们将在类路径中需要“spring-jdbc”依赖项。

虽然两者有时候可能存在于同一个应用程序中, 但 Spring Security OAuth 中你可以把它们各自放在不同应用上, 而且你可以有多个资源服务, 它们共享同一个中央授权服务.所有获取令牌的请求都将在 Spring MVC controller endpoints 中处理, 并且访问受保护的资源服务的处理流程将会放在标准的 Spring Security 请求过滤器中.

下面是配置一个授权服务必须要实现的 endpoints:

AuthorizationEndpoint: 用来作为请求者获得权限的服务, 默认 URL 是 /oauth/authorize
TokenEndpoint: 用来作为请求者获取令牌的服务, 默认 URL 是 /oauth/token
下面是配置一个资源服务必须要实现的过滤器:

OAuth2AuthenticationProcessingFilter: 用来作为认证令牌的一个处理流程过滤器. 只有当它放行后, 请求才能访问被保护的资源

流程

协议流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+--------+                               +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+

刷新Token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
+--------+                                           +---------------+
| |--(A)------- Authorization Grant --------->| |
| | | |
| |<-(B)----------- Access Token -------------| |
| | & Refresh Token | |
| | | |
| | +----------+ | |
| |--(C)---- Access Token ---->| | | |
| | | | | |
| |<-(D)- Protected Resource --| Resource | | Authorization |
| Client | | Server | | Server |
| |--(E)---- Access Token ---->| | | |
| | | | | |
| |<-(F)- Invalid Token Error -| | | |
| | +----------+ | |
| | | |
| |--(G)----------- Refresh Token ----------->| |
| | | |
| |<-(H)----------- Access Token -------------| |
+--------+ & Optional Refresh Token +---------------+

授权码

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
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Authorization Code ---------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)

隐式

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
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI --->| |
| User- | | Authorization |
| Agent -|----(B)-- User authenticates -->| Server |
| | | |
| |<---(C)--- Redirection URI ----<| |
| | with Access Token +---------------+
| | in Fragment
| | +---------------+
| |----(D)--- Redirection URI ---->| Web-Hosted |
| | without Fragment | Client |
| | | Resource |
| (F) |<---(E)------- Script ---------<| |
| | +---------------+
+-|--------+
| |
(A) (G) Access Token
| |
^ v
+---------+
| |
| Client |
| |
+---------+

密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+----------+
| Resource |
| Owner |
| |
+----------+
v
| Resource Owner
(A) Password Credentials
|
v
+---------+ +---------------+
| |>--(B)---- Resource Owner ------->| |
| | Password Credentials | Authorization |
| Client | | Server |
| |<--(C)---- Access Token ---------<| |
| | (w/ Optional Refresh Token) | |
+---------+ +---------------+

客户端

1
2
3
4
5
6
7
+---------+                                  +---------------+
| | | |
| |>--(A)- Client Authentication --->| Authorization |
| Client | | Server |
| |<--(B)---- Access Token ---------<| |
| | | |
+---------+ +---------------+

Authorization Server - 授权服务配置


用 @EnableAuthorizationServer 来配置 OAuth 2.0 授权服务.接下来介绍几个配置类, 它们是由 Spring 创建的独立的配置对象, 会被 Spring 传入 AuthorizationServerConfigurer 中:

  • ClientDetailsServiceConfigurer: 用来配置客户端详情服务, 客户端详情信息在这里初始化, 你能够把客户端详情硬编码在这里或是通过数据库来存取.
  • AuthorizationServerEndpointsConfigurer: 用来配置授权以及令牌的访问端点和令牌服务;
  • AuthorizationServerSecurityConfigurer: 用来配置令牌端点的安全约束.(以上配置可以选择继承 AuthorizationServerConfigurerAdapter 并且覆盖其中的三个configure 方法来配置)

ClientDetailsServiceConfigurer

void configure(ClientDetailsServiceConfigurer clients) throw Exception
ClientDetailsServiceConfigurer, AuthorizationServerConfigurer 的一个回调配置项. 能够使用内存或者 JDBC 来实现客户端详情服务 (ClientDetailsService).

AuthorizationServerEndpointsConfigurer

void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception
AuthorizationServerEndpointsConfigurer, AuthorizationServerConfigurer 的一个回调配置项. 用于配置授权以及令牌的访问端点和令牌服务.

AuthorizationServerSecurityConfigurer

void configure(AuthorizationServerSecurityConfigurer security) throws Exception
AuthorizationServerSecurityConfigurer, AuthorizationServerConfigurer 的一个回调配置项. “授权服务器” 安全配置, 实际上就是 /oauth/token 端点

AuthorizationServerTokenServices - 管理令牌

AuthorizationServerTokenServices 接口定义了一些操作使得你可以对令牌进行一些必要的管理, 需要注意:

  • 当一个令牌被创建了, 你必须保存, 这样当一个客户端使用这个令牌对资源服务请求的时候才可以应用到这个令牌
  • 当一个令牌是有效的时候, 它可以被用来加载身份信息, 里面包含了这个令牌的相关权限

InMemoryTokenStore JdbcTokenStore JwtTokenStore

公钥公布在 /oauth/token_key 这个 URL 连接中, 默认的访问安全规则是 denyAll()

Grant Type - 配置授权类型

授权是使用 AuthorizationEndpoint 和这个端点来控制的, 你能够使用 AuthorizationServerEndpointsConfigurer 这个对象的实例来配置

Endpoint URL - 配置授权端点

AuthorizationServerEndpointsConfigurer 这个配置对象 (AuthorizationServerConfigurer 的一个回调配置项) 有一个叫做 pathMapping() 的方法用来配置端点 URL 链接, 它有两个参数:第一个参数 String 类型的, 这个端点URL的默认链接; 第二个参数: String 类型的, 你要进行替代的URL链接.

以上的参数都将以 “/” 字符为开始的字符串, 框架的默认 URL 链接如下列表, 可以作为这个 pathMapping() 方法的第一个参数:

/oauth/authorize: 授权端点, 这个 URL 应该被 Spring Security 保护起来只供授权用户访问;也就是授权码模式认证授权接口
/oauth/token: 令牌端点,获取 token 的接口
/oauth/confirm_access: 用户确认授权提交端点;
/oauth/error: 授权服务错误信息端点;
/oauth/check_token: 用于资源服务访问的令牌解析端点; 当授权服务器和资源服务器分开部署的时候, 资源服务器需要访问这个地址验证令牌,也就是检查 token 合法性接口
/oauth/token_key: 提供公有密匙的端点, 如果使用 JWT 令牌的话;

参数说明
access_token : 就是之后请求需要带上的 token
token_type:为 bearer,这是 access token 最常用的一种形式
refresh_token:之后可以用这个值来换取新的 token,而不用输入账号密码
expires_in:token 的过期时间(秒)

Resource Server - 资源服务配置

一个资源服务 (可以和授权服务在同一个应用中, 当然也可以分离开成为两个不同的应用程序) 提供一些受token令牌保护的资源, Spring OAuth 提供者是通过 Spring Security authentication filter 即验证过滤器来实现的保护, 你可以通过 @EnableResourceServer 注解到一个 @Configuration 配置类上, 并且必须使用 ResourceServerConfigurer 这个配置对象来进行配置 (可以选择继承自 ResourceServerConfigurerAdapter 然后覆写其中的方法, 参数就是这个对象的实例). 下面是一些可以配置的属性:

tokenServices: ResourceServerTokenServices 类的实例, 用来实现令牌服务.
resourceId: 这个资源服务的ID, 这个属性是可选的, 但是推荐设置并在授权服务中进行验证.
其他的拓展属性例如 tokenExtractor 令牌提取器用来提取请求中的令牌.
请求匹配器, 用来设置需要进行保护的资源路径, 默认的情况下是受保护资源服务的全部路径.
受保护资源的访问规则, 默认的规则是简单的身份验证 (plain authenticated).
其他的自定义权限保护规则通过 HttpSecurity 来进行配置.
@EnableResourceServer 注解自动增加了一个类型为 OAuth2AuthenticationProcessingFilter 的过滤器链,

使用授权服务的 /oauth/check_token 端点你需要将这个端点暴露出去, 以便资源服务可以进行访问

ResourceServerSecurityConfigurer

configure(ResourceServerSecurityConfigurer resources): 为资源服务器配置特定属性, 如 resource id.
org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurer#configure(ResourceServerSecurityConfigurer)

ResourceServerTokenServices

ResourceServerTokenServices 是组成授权服务的另一半, 如果你的授权服务和资源服务在同一个应用程序上的话, 你可以使用 DefaultTokenServices, 这样你就不用考虑关于实现所有必要的接口的一致性问题, 这通常是很困难的. 如果你的资源服务器是分离开的, 那么你就必须要确保匹配授权服务提供的 ResourceServerTokenServices, 它知道如何对令牌进行解码

DefaultTokenServices: 在授权服务器上, 你通常可以使用 DefaultTokenServices 并且选择一些主要的表达式通过 TokenStore (后端存储或者本地编码).
RemoteTokenServices: 允许资源服务器通过 HTTP 请求来解码令牌 (也就是授权服务的 /oauth/check_token 端点). 如果你的资源服务没有太大的访问量的话, 那么使用 RemoteTokenServices 将会很方便 (所有受保护的资源请求都将请求一次授权服务用以检验 token 值), 或者你可以通过缓存来保存每一个 token 验证的结果

resource-id

可以为每一个资源服务器 (可能是一个微服务实例) 设置一个 resource id. 在给客户端授权的时候, 可以设置这个客户端可以访问哪些微服务实例. 如果没有设置就是对所有的 resource 都有访问权限.

UserDetailsService

UserDetailsService 接口从数据库中获取用户信息, 并通过实现 AuthenticationProvider 接口编写自己的校验逻辑, 从而完成 SpringSecurity 身份校验.

UserDetailsService
UserDetailsService 是加载用户指定数据的核心接口.

loadUserByUsername
UserDetailsService 只有 loadUserByUsername 一个接口方法, 用于通过用户名获取用户数据. 返回 UserDetails 对象, 表示用户的核心信息 (用户名, 用户密码, 权限等信息).

Authentication
一旦请求被 AuthenticationManager.authenticate(Authentication) 方法处理了, Authentication 就标示为一个认证请求的 Token. 当信息被认证了, Authentication 会被线程安全的 SecurityContext 持有, 后者可以通过 SecurityContextHolder 获取. 本文中我们会用到 Authentication 其中之一: UsernamePasswordAuthenticationToken (被设计成用于描述用户名和密码的简单实现).

AuthenticationProvider
定义了用于处理认证逻辑的接口标准, 我们可以实现这个类以便实现自己的认证逻辑.

SecurityConfig
在 WebSecurityConfigurerAdapter 的实现 - 配置类中指明启用我们自定义的 AuthenticationProvider

JWTAuthorizationFilter
这个过滤器用于对携带 access-token 的请求执行权限检查. 所以, 除了注册端点之外的所有请求都应该被它过滤.

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
public class JWTAuthorizationFilter extends OncePerRequestFilter {

private static final Set<String> WHITE_LIST = Stream.of("/auth/register").collect(Collectors.toSet());

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
log.debug("authorization filter doFilterInternal");
final String authorization = request.getHeader(JWTUtils.TOKEN_HEADER);
log.debug("raw-access-token: {}", authorization);

// Branch A: 如果请求头中没有 Authorization
if (StringUtils.isBlank(authorization)) {
// 白名单放行
if (WHITE_LIST.contains(request.getRequestURI())) {
chain.doFilter(request, response);
} else {
response.getWriter().write("未经授权的访问!");
}
return;
}

// Branch B: 如果请求头中有 Bear xxx, 设置认证信息
final String jsonWebToken = authorization.replace(JWTUtils.TOKEN_PREFIX, StringUtils.EMPTY);

// TODO 用 Redis 的过期控制 token, 而不用 jwt 的 Expiration
// if (JWTUtils.hasExpired(jsonWebToken)) {
// response.getWriter().write("access-token 已过期, 请重新登陆!");
// }
// TODO 每一次携带正确 token 的访问, 都刷新 Redis 的过期时间

CustomUserDetails customUserDetails = JWTUtils.userDetails(jsonWebToken);
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(
customUserDetails.getName(),
// TODO Json Web Token 中不能携带用户密码
customUserDetails.getPassword(),
customUserDetails.getAuthorities()
)
);
chain.doFilter(request, response);
}
}

微服务中一般使用流程

token可以通过存储在数据库中每次请求进行校验,或者用jwt程序直接交易(优点性能高,缺点没办直接法注销token)

OAuth2.0 jwt 通过tokenEnhancer增强jwttoken,增加额外信息,配置是否允许跨域请求等。

  1. 登录成功后生成token,把用户的权限信息存储到redis中(key=permission:账号:角色 value=权限内容)。
  2. 请求发送到网关(网关中配置了一些白名单,黑名单等策略),网关判断token是否存在等,如果存在网关获取请求头中Token调用Auth服务校验Token是否有效(token存储在redis中或者数据库中),或者直接使用jwt算法计算校验。(如果一个账号同时只允许一个用户登录,还可以把token放在redis里面(auth:token),在登录成功后把旧的auth:token删除,插入新的auth:token,每次请求都校验auth:token是否存在)
  3. 功能功能服务进行权限判断的时候,通过请求头中的token获取到用户信息,通过用户信息去redis中获取到具体的权限信息,然后通过自定义注解拦截器(基于:HandlerInterceptorAdapter,或者@Aspect切面)完成对接口的权限校验。

权限更简单的实现方案,直接使用uuid随机码生成token存储在redis中,通过切面进行权限校验,auth2都可以不需要,如果需要各种强大的功能SSO功能才需要考虑OAuth2.0。

SpringSecurity

自定义权限校验,开启@EnableGlobalMethodSecurity(prePostEnabled = true)注解,@PreAuthorize可以用了。 定义@Service("auth")定义鉴权方法 hasPermi等,定义异常类。可以在Controller层通过@PreAuthorize("@auth.hasPermi('sys:config:info')")
用户成功登陆处理,只需要实现AuthenticationSuccessHandler接口即可

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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
/**
* token格式
*/
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 520L;
private final Object principal;
private Object credentials;

public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}

public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}

public Object getCredentials() {
return this.credentials;
}

public Object getPrincipal() {
return this.principal;
}

public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
} else {
super.setAuthenticated(false);
}
}

public void eraseCredentials() {
super.eraseCredentials();
this.credentials = null;
}
}
public class OAuth2Authentication extends AbstractAuthenticationToken {

private static final long serialVersionUID = -4809832298438307309L;

private final OAuth2Request storedRequest;

private final Authentication userAuthentication; // 使用UsernamePasswordAuthenticationToken

}
/**
* 存储token
*/
public class RedisTokenStore implements TokenStore {
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { //OAuth2Authentication包含UsernamePasswordAuthenticationToken
byte[] serializedAccessToken = serialize(token);
byte[] serializedAuth = serialize(authentication);
byte[] accessKey = serializeKey(ACCESS + token.getValue());
byte[] authKey = serializeKey(AUTH + token.getValue());
byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication));
byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication));
byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());

RedisConnection conn = getConnection();
try {
conn.openPipeline();
if (springDataRedis_2_0) {
try {
this.redisConnectionSet_2_0.invoke(conn, accessKey, serializedAccessToken);
this.redisConnectionSet_2_0.invoke(conn, authKey, serializedAuth);
this.redisConnectionSet_2_0.invoke(conn, authToAccessKey, serializedAccessToken);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
} else {
conn.set(accessKey, serializedAccessToken);
conn.set(authKey, serializedAuth);
conn.set(authToAccessKey, serializedAccessToken);
}
if (!authentication.isClientOnly()) {
conn.sAdd(approvalKey, serializedAccessToken);
}
conn.sAdd(clientId, serializedAccessToken);
if (token.getExpiration() != null) {
int seconds = token.getExpiresIn();
conn.expire(accessKey, seconds);
conn.expire(authKey, seconds);
conn.expire(authToAccessKey, seconds);
conn.expire(clientId, seconds);
conn.expire(approvalKey, seconds);
}
OAuth2RefreshToken refreshToken = token.getRefreshToken();
if (refreshToken != null && refreshToken.getValue() != null) {
byte[] refresh = serialize(token.getRefreshToken().getValue());
byte[] auth = serialize(token.getValue());
byte[] refreshToAccessKey = serializeKey(REFRESH_TO_ACCESS + token.getRefreshToken().getValue());
byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + token.getValue());
if (springDataRedis_2_0) {
try {
this.redisConnectionSet_2_0.invoke(conn, refreshToAccessKey, auth);
this.redisConnectionSet_2_0.invoke(conn, accessToRefreshKey, refresh);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
} else {
conn.set(refreshToAccessKey, auth);
conn.set(accessToRefreshKey, refresh);
}
if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken;
Date expiration = expiringRefreshToken.getExpiration();
if (expiration != null) {
int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L)
.intValue();
conn.expire(refreshToAccessKey, seconds);
conn.expire(accessToRefreshKey, seconds);
}
}
}
conn.closePipeline();
} finally {
conn.close();
}
}
}
/**
* 配置授权服务器
*/
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter
{
/**
* 认证管理器
*/
@Autowired
private AuthenticationManager authenticationManager;

/**
* 数据源
*/
@Autowired
private DataSource dataSource;

// 实例化接口TokenStore为RedisTokenStore
@Autowired
private RedisConnectionFactory redisConnectionFactory;

/**
* 获取用户信息
*/
@Autowired
@Qualifier("userDetailsService") // 精确名字
private UserDetailsService userDetailsService; // 虽然说自带非常多实现类,但是都没被spring管理,所以可以自己再定义一个注入进来

@Autowired
private TokenEnhancer tokenEnhancer;
/**
* 定义授权和令牌端点以及令牌服务(authorization)(token)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
{
endpoints
// 请求方式
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
// 指定token存储位置
.tokenStore(tokenStore())
// 自定义生成令牌
.tokenEnhancer(tokenEnhancer)
// 用户账号密码认证
.userDetailsService(userDetailsService)
// 指定认证管理器 安全管理器,使用Spring定义的即可,但要声明为bean
.authenticationManager(authenticationManager)
// 是否重复使用 refresh_token
.reuseRefreshTokens(false)
// 自定义异常处理
.exceptionTranslator(new CustomWebResponseExceptionTranslator());
// 授权码模式 实现RandomValueAuthorizationCodeServices接口,public class RedisAuthorizationCodeServices extends RandomValueAuthorizationCodeServices
//.authorizationCodeServices(redisAuthorizationCodeServices);
}

/**
* 声明 ClientDetails实现
*/
public RedisClientDetailsService clientDetailsService()
{
RedisClientDetailsService clientDetailsService = new RedisClientDetailsService(dataSource);
return clientDetailsService;
}

/**
* 配置客户端详情
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception
{
// clients.inMemory().withClient("system").secret("system")
// .authorizedGrantTypes("password", "authorization_code", "refresh_token").scopes("app")
// .accessTokenValiditySeconds(3600); // 内存
// clients.jdbc(dataSource); // 数据库
clients.withClientDetails(clientDetailsService()); // 增加redis缓存
}

/**
* 基于 Redis 实现,令牌保存到缓存
*/
@Bean
public TokenStore tokenStore()
{
RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
tokenStore.setPrefix(CacheConstants.OAUTH_ACCESS);
return tokenStore;
}
}

/**
* 配置资源服务器
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter
{
@Autowired
private ResourceServerProperties resourceServerProperties;

@Autowired
private OAuth2ClientProperties oAuth2ClientProperties;

@Bean
public AuthIgnoreConfig authIgnoreConfig()
{
return new AuthIgnoreConfig();
}

@Bean
@LoadBalanced
public RestTemplate restTemplate()
{
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
return restTemplate;
}

@Bean
public ResourceServerTokenServices tokenServices()
{
RemoteTokenServices remoteTokenServices = new RemoteTokenServices(); // 是用于向远程认证服务器验证token,同时获取token对应的用户的信息
DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
UserAuthenticationConverter userTokenConverter = new CommonUserConverter(); // 用户权限转换成MAP
accessTokenConverter.setUserTokenConverter(userTokenConverter);
remoteTokenServices.setCheckTokenEndpointUrl(resourceServerProperties.getTokenInfoUri());
remoteTokenServices.setClientId(oAuth2ClientProperties.getClientId()); // clientId
remoteTokenServices.setClientSecret(oAuth2ClientProperties.getClientSecret()); // ClientSecret
remoteTokenServices.setRestTemplate(restTemplate());
remoteTokenServices.setAccessTokenConverter(accessTokenConverter);
return remoteTokenServices;
}

@Override
public void configure(HttpSecurity http) throws Exception
{
http.csrf().disable();
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
.authorizeRequests();
// 不登录可以访问
authIgnoreConfig().getUrls().forEach(url -> registry.antMatchers(url).permitAll());
registry.anyRequest().authenticated();
}

@Override
public void configure(ResourceServerSecurityConfigurer resources)
{
// 指定 ResourceServerTokenServices
resources.tokenServices(tokenServices());
}
}

/**
* 根据checktoken 的结果转化用户信息
*
*/
public class CommonUserConverter implements UserAuthenticationConverter
{
private static final String N_A = "N/A";

/**
* 将授权信息返回到资源服务
*/
@Override
public Map<String, ?> convertUserAuthentication(Authentication userAuthentication)
{
Map<String, Object> authMap = new LinkedHashMap<>();
authMap.put(USERNAME, userAuthentication.getName());
if (userAuthentication.getAuthorities() != null && !userAuthentication.getAuthorities().isEmpty())
{
authMap.put(AUTHORITIES, AuthorityUtils.authorityListToSet(userAuthentication.getAuthorities()));
}
return authMap;
}

/**
* 获取用户认证信息
*/
@Override
public Authentication extractAuthentication(Map<String, ?> map)
{
if (map.containsKey(USERNAME))
{
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);

Long userId = Convert.toLong(map.get(SecurityConstants.DETAILS_USER_ID));
String username = (String) map.get(SecurityConstants.DETAILS_USERNAME);
LoginUser user = new LoginUser(userId, username, N_A, true, true, true, true, authorities);
return new UsernamePasswordAuthenticationToken(user, N_A, authorities);
}
return null;
}

/**
* 获取权限资源信息
*/
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map)
{
Object authorities = map.get(AUTHORITIES);
if (authorities instanceof String)
{
return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
}
if (authorities instanceof Collection)
{
return AuthorityUtils.commaSeparatedStringToAuthorityList(
StringUtils.collectionToCommaDelimitedString((Collection<?>) authorities));
}
throw new IllegalArgumentException("Authorities must be either a String or a Collection");
}
}

/**
* 用户信息处理
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService
{
@Override
public UserDetails loadUserByUsername(String username)
{
R<UserInfo> userResult = remoteUserService.getUserInfo(username);
checkUser(userResult, username);
return getUserDetails(userResult);
}

public void checkUser(R<UserInfo> userResult, String username)
{
}
private UserDetails getUserDetails(R<UserInfo> result)
{
// 返回包含权限等完整的用户信息
}
}

/**
* 启用redis 缓存,通过DataSource从MYSQL数据库中获取信息
*/
@Service
public class RedisClientDetailsService extends JdbcClientDetailsService
{

}

/**
* 对整个web进行配置
* Oauth2依赖于Security 默认情况下WebSecurityConfig执行比ResourceServerConfig优先
*/
@Order(99)
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
}

OAuth2.0 对权限信息的存储

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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
/**
OAuth2AuthenticationProcessingFilter 核心过滤器,过滤请求
*/
public class OAuth2AuthenticationProcessingFilter implements Filter, InitializingBean {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
ServletException {
final boolean debug = logger.isDebugEnabled();
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;

try {

Authentication authentication = tokenExtractor.extract(request);

if (authentication == null) {
if (stateless && isAuthenticated()) {
if (debug) {
logger.debug("Clearing security context.");
}
SecurityContextHolder.clearContext();
}
if (debug) {
logger.debug("No token in request, will continue chain.");
}
}
else {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
if (authentication instanceof AbstractAuthenticationToken) {
AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
}
// 身份认证
Authentication authResult = authenticationManager.authenticate(authentication);

if (debug) {
logger.debug("Authentication success: " + authResult);
}
//成功事件通知
eventPublisher.publishAuthenticationSuccess(authResult);
//保存Security上下文
SecurityContextHolder.getContext().setAuthentication(authResult);

}
}
catch (OAuth2Exception failed) {
SecurityContextHolder.clearContext();

if (debug) {
logger.debug("Authentication request failed: " + failed);
}
eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
new PreAuthenticatedAuthenticationToken("access-token", "N/A"));

authenticationEntryPoint.commence(request, response,
new InsufficientAuthenticationException(failed.getMessage(), failed));

return;
}

chain.doFilter(request, response);
}
}
}

/**
* Expects the incoming authentication request to have a principal value that is an access token value (e.g. from an
* authorization header). Loads an authentication from the {@link ResourceServerTokenServices} and checks that the
* resource id is contained in the {@link AuthorizationRequest} (if one is specified). Also copies authentication
* details over from the input to the output (e.g. typically so that the access token value and request details can
* be reported later).
* 认证管理器
* @param authentication an authentication request containing an access token value as the principal
* @return an {@link OAuth2Authentication}
*
* @see org.springframework.security.authentication.AuthenticationManager#authenticate(org.springframework.security.core.Authentication)
*/
public class OAuth2AuthenticationManager implements AuthenticationManager, InitializingBean {
public Authentication authenticate(Authentication authentication) throws AuthenticationException {

if (authentication == null) {
throw new InvalidTokenException("Invalid token (token not found)");
}
String token = (String) authentication.getPrincipal();
OAuth2Authentication auth = tokenServices.loadAuthentication(token); // 获取权限
if (auth == null) {
throw new InvalidTokenException("Invalid token: " + token);
}

Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();
if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {
throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");
}

checkClientDetails(auth);

if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
// Guard against a cached copy of the same details
if (!details.equals(auth.getDetails())) {
// Preserve the authentication details from the one loaded by token services
details.setDecodedDetails(auth.getDetails());
}
}
auth.setDetails(authentication.getDetails());
auth.setAuthenticated(true);
return auth;

}
}

/**
* demo 自定义ResourceServerTokenServices
*/
public class UserInfoTokenServices implements ResourceServerTokenServices {
/**
根据access_token获取用户认证信息(根据access_token调用认证服务器)
*/
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
//this.userInfoEndpointUrl application.peoperties中配置的
//获取用户信息的URL security.oauth2.resource.userInfoUri
//accessToken 回去的access_token
//以下为根据access_token和获取用户信息的URL(需要则认证服务器写专门的controller处理)获取用户信息
Map<String, Object> map = this.getMap(this.userInfoEndpointUrl, accessToken);
if (map.containsKey("error")) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("userinfo returned error: " + map.get("error"));
}

throw new InvalidTokenException(accessToken);
} else {
return this.extractAuthentication(map);
}
}


/**
提取用户认证信息
*/
private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
Object principal = this.getPrincipal(map);
List<GrantedAuthority> authorities = this.authoritiesExtractor.extractAuthorities(map);
OAuth2Request request = new OAuth2Request((Map)null, this.clientId, (Collection)null, true, (Set)null, (Set)null, (String)null, (Set)null, (Map)null);
//将提取的值principal作为构造函数参数
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
token.setDetails(map);
return new OAuth2Authentication(request, token);
}
/**
从map中提取最终在UsernamePasswordAuthenticationToken构造函数中封装的值
*/
protected Object getPrincipal(Map<String, Object> map) {
Object principal = this.principalExtractor.extractPrincipal(map);
return principal == null ? "unknown" : principal;
}

public OAuth2AccessToken readAccessToken(String accessToken) {
throw new UnsupportedOperationException("Not supported: read access token");
}

/**
调用认证服务器,获取用户信息
*/
private Map<String, Object> getMap(String path, String accessToken) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Getting user info from: " + path);
}

try {
OAuth2RestOperations restTemplate = this.restTemplate;
if (restTemplate == null) {
BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();
resource.setClientId(this.clientId);
restTemplate = new OAuth2RestTemplate(resource);
}

OAuth2AccessToken existingToken = ((OAuth2RestOperations)restTemplate).getOAuth2ClientContext().getAccessToken();
if (existingToken == null || !accessToken.equals(existingToken.getValue())) {
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(accessToken);
token.setTokenType(this.tokenType);
((OAuth2RestOperations)restTemplate).getOAuth2ClientContext().setAccessToken(token);
}
//通过restTemplate返回用户信息
return (Map)((OAuth2RestOperations)restTemplate).getForEntity(path, Map.class, new Object[0]).getBody();
} catch (Exception var6) {
this.logger.warn("Could not fetch user details: " + var6.getClass() + ", " + var6.getMessage());
return Collections.singletonMap("error", "Could not fetch user details");
}
}
}

/**
* 调用远程ResourceServerTokenServices服务
*/
public class RemoteTokenServices implements ResourceServerTokenServices {
@Override
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {

MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
formData.add(tokenName, accessToken);
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", getAuthorizationHeader(clientId, clientSecret));
Map<String, Object> map = postForMap(checkTokenEndpointUrl, formData, headers); // 远程调用授权服务器的解析令牌端点

if (map.containsKey("error")) {
if (logger.isDebugEnabled()) {
logger.debug("check_token returned error: " + map.get("error"));
}
throw new InvalidTokenException(accessToken);
}

// gh-838
if (!Boolean.TRUE.equals(map.get("active"))) {
logger.debug("check_token returned active attribute: " + map.get("active"));
throw new InvalidTokenException(accessToken);
}

// DefaultAccessTokenConverter.extractAuthentication(Map<String, ?> map) DefaultUserAuthenticationConverter 如果UserDetailsService不为空,那么会调用它的loadUserByUsername方法来获取用户信息
return tokenConverter.extractAuthentication(map);
}
}


/**
最终在map中提取的是什么信息,封装在principal中
注意:以下只会返回一个合适的值,即使你认证服务返回在多的信息,最终都无法在客户端显示
*/
public class FixedPrincipalExtractor implements PrincipalExtractor {

//
private static final String[] PRINCIPAL_KEYS = new String[] { "user", "username",
"userid", "user_id", "login", "id", "name" };

@Override
public Object extractPrincipal(Map<String, Object> map) {
//提取只会返回一个合适的值,最终封装在principal中
//主要还是看认证服务器返回用户信息的接口是否返回如上数组定义的字段信息,如果没有可能会返回null
for (String key : PRINCIPAL_KEYS) {
if (map.containsKey(key)) {
return map.get(key);
}
}
return null;
}

}
/**
* 认证服务器
*/
public interface AuthorizationServerTokenServices {
// 创建token
OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException;
// 刷新token
OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)
throws AuthenticationException;
// 获取token
OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);
}
/**
* 资源服务器
*/
public interface ResourceServerTokenServices {

//根据accessToken加载客户端信息
OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException;

//根据accessToken获取完整的访问令牌详细信息。
OAuth2AccessToken readAccessToken(String accessToken);

}

/**
其他
*/
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{

}

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

}

/**
* 在认证服务的 Endpoints 中, 使用的正是 DefaultTokenServices,
* 被RemoteTokenServices调用
*/
@FrameworkEndpoint
public class CheckTokenEndpoint {
@RequestMapping(value = "/oauth/check_token")
@ResponseBody
public Map<String, ?> checkToken(@RequestParam("token") String value) {

OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value);
if (token == null) {
throw new InvalidTokenException("Token was not recognised");
}

if (token.isExpired()) {
throw new InvalidTokenException("Token has expired");
}

// 调用DefaultTokenServices 调用 RedisStore获取权限信息
OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(token.getValue());

// 调用TokenCoverter
Map<String, Object> response = (Map<String, Object>)accessTokenConverter.convertAccessToken(token, authentication);

// gh-1070
response.put("active", true); // Always true if token exists and not expired

return response;
}
}

/**
* 默认ResourceServerTokenServices
*/
public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
ConsumerTokenServices, InitializingBean {
public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException,
InvalidTokenException {
OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
if (accessToken == null) {
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
}
else if (accessToken.isExpired()) {
tokenStore.removeAccessToken(accessToken);
throw new InvalidTokenException("Access token expired: " + accessTokenValue);
}

OAuth2Authentication result = tokenStore.readAuthentication(accessToken); // 调用配置好的redisStore
if (result == null) {
// in case of race condition
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
}
if (clientDetailsService != null) {
String clientId = result.getOAuth2Request().getClientId();
try {
clientDetailsService.loadClientByClientId(clientId);
}
catch (ClientRegistrationException e) {
throw new InvalidTokenException("Client not valid: " + clientId, e);
}
}
return result;
}
}

public class DefaultAccessTokenConverter implements AccessTokenConverter {
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
Map<String, String> parameters = new HashMap<String, String>();
Set<String> scope = extractScope(map);
// 调用DefaultUserAuthenticationConverter.extractAuthentication
Authentication user = userTokenConverter.extractAuthentication(map);
String clientId = (String) map.get(clientIdAttribute);
parameters.put(clientIdAttribute, clientId);
if (includeGrantType && map.containsKey(GRANT_TYPE)) {
parameters.put(GRANT_TYPE, (String) map.get(GRANT_TYPE));
}
Set<String> resourceIds = new LinkedHashSet<String>(map.containsKey(AUD) ? getAudience(map)
: Collections.<String>emptySet());

Collection<? extends GrantedAuthority> authorities = null;
if (user==null && map.containsKey(AUTHORITIES)) {
@SuppressWarnings("unchecked")
String[] roles = ((Collection<String>)map.get(AUTHORITIES)).toArray(new String[0]);
authorities = AuthorityUtils.createAuthorityList(roles);
}
OAuth2Request request = new OAuth2Request(parameters, clientId, authorities, true, scope, resourceIds, null, null,
null);
return new OAuth2Authentication(request, user);
}
}

/**
* 权限转换器
*/
public class DefaultUserAuthenticationConverter implements UserAuthenticationConverter {

public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey("user_name")) {
Object principal = map.get("user_name");
Collection<? extends GrantedAuthority> authorities = this.getAuthorities(map);
if (this.userDetailsService != null) {
UserDetails user = this.userDetailsService.loadUserByUsername((String)map.get("user_name"));
authorities = user.getAuthorities();
principal = user;
}

return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
} else {
return null;
}
}
}

/**
oAuth2RequestFactory 工厂模式
ResourceServerConfigurerAdapter 适配器模式
AbstractConfiguredSecurityBuilder 建造者模式
TokenStore 模板方法模式
AuthenticationEventPublisher 发布订阅模式
/*

流程大概是:
OAuth2AuthenticationProcessingFilter拦截请求调用 OAuth2AuthenticationManager 进行权限校验 调用RemoteTokenServices获取用户详细权限信息(调用this.postForMap(this.checkTokenEndpointUrl, formData, headers) restTemplate /oauth/check_token,资源服务loadAuthentication(调用DefaultTokenServices如果使用的是redisStore里面已经存储了权限信息),调用DefaultAccessTokenConverter再调用DefaultUserAuthenticationConverter调用UserDetailsService.loadUserByUsername()然后Convert转换),然后OAuth2AuthenticationProcessingFilter调用SecurityContextHolder.getContext().setAuthentication(authResult);方法保存到上下文中。
进行权限校验的时候通过注解获取方法权限名称,再通过上下文获取权限列表进行对比判断是否有权限。

1
Token→OAuthAuthenticationProcessingFilter→OAuth2AuthnticationManager→RemoteTOkenServices→CheckToken→DefaultAccessTokenConverter→DefaultUserAuthenticationConverter→userDetailsService→UserDetails
  1. OAuth2AuthenticationManager.authenticate(),filter执行判断的入口
  2. 当用户携带token 去请求微服务模块,被资源服务器拦截调用RemoteTokenServices.loadAuthentication ,执行所谓的check-token过程
  3. CheckToken 处理逻辑很简单,就是调用redisTokenStore 查询token的合法性,及其返回用户的部分信息
  4. 返回给 RemoteTokenServices.loadAuthentication 最后一句tokenConverter.extractAuthentication 解析组装服务端返回的信息 userTokenConverter.extractAuthentication(map);
  5. 最重要的一步,是否判断是否有userDetailsService实现,如果有 的话去查根据 返回的 username 查询一次全部的用户信息,没有实现直接返回username。
  6. UerDetailsServiceImpl.loadUserByUsername 根据用户名去换取用户全部信息

RemoteTokenServices远程调用流程图:
RemoteTokenServices

认证会用到的请求

  • 获取access_token(/oauth/token) 请求所需参数:client_id、client_secret、grant_type、username、password
    1
    http://127.0.0.1:9000/auth/oauth/token?client_id=web&client_secret=123456&grant_type=password&username=admin&password=admin123
  • 检查请求是否有效(/oauth/check_token) 请求所需参数:token
    1
    http://127.0.0.1:9000/auth/oauth/check_token?token=fsdfsdf-223d-4b3d7-221-fffssdf
  • 刷新token请求(/oauth/token) 请求所需参数:grant_type、refresh_token、client_id、client_secret 其中grant_type为固定值:grant_type=refresh_token
    1
    http://127.0.0.1:9000/oauth/token?grant_type=refresh_token&refresh_token=fbde821e-f419-4221-1224-91121232e9&client_id=web&client_secret=123456

认证流程

获取token流程

  • 用户发起获取token的请求。
  • 过滤器会验证path是否是认证的请求/oauth/token,如果为false,则直接返回没有后续操作。
  • 过滤器通过clientId查询生成一个Authentication对象。
  • 然后会通过username和生成的Authentication对象生成一个UserDetails对象,并检查用户是否存在。
  • 以上全部通过会进入地址/oauth/token,即TokenEndpoint的postAccessToken方法中。
  • postAccessToken方法中会验证Scope,然后验证是否是refreshToken请求等。
  • 之后调用AbstractTokenGranter中的grant方法。
  • grant方法中调用AbstractUserDetailsAuthenticationProvider的authenticate方法,通过username和Authentication对象来检索用户是否存在。
  • 然后通过DefaultTokenServices类从tokenStore中获取OAuth2AccessToken对象。
  • 然后将OAuth2AccessToken对象包装进响应流返回。

刷新token(refresh token)的流程

获取token调用的是AbstractTokenGranter中的getAccessToken方法,然后调用tokenStore中的getAccessToken方法获取token。
刷新token调用的是RefreshTokenGranter中的getAccessToken方法,然后使用tokenStore中的refreshAccessToken方法获取token。

tokenStore的特点

tokenStore通常情况为自定义实现,一般放置在缓存或者数据库中。此处可以利用自定义tokenStore来实现多种需求,如:

同已用户每次获取token,获取到的都是同一个token,只有token失效后才会获取新token。
同一用户每次获取token都生成一个完成周期的token并且保证每次生成的token都能够使用(多点登录)。
同一用户每次获取token都保证只有最后一个token能够使用,之前的token都设为无效(单点token)。

获取token详细流程

  1. 重要的过滤器
    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
    public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
    implements ApplicationEventPublisherAware, MessageSourceAware {
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
    throws IOException, ServletException {

    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;

    if (!requiresAuthentication(request, response)) { // 验证Path
    chain.doFilter(request, response);

    return;
    }

    if (logger.isDebugEnabled()) {
    logger.debug("Request is to process authentication");
    }

    Authentication authResult;

    try {
    authResult = attemptAuthentication(request, response); // 调用ClientCredentialsTokenEndpointFilter类的方法,进行clientid校验
    if (authResult == null) {
    // return immediately as subclass has indicated that it hasn't completed
    // authentication
    return;
    }
    sessionStrategy.onAuthentication(authResult, request, response);
    }
    catch (InternalAuthenticationServiceException failed) {
    logger.error(
    "An internal error occurred while trying to authenticate the user.",
    failed);
    unsuccessfulAuthentication(request, response, failed);

    return;
    }
    catch (AuthenticationException failed) {
    // Authentication failed
    unsuccessfulAuthentication(request, response, failed);

    return;
    }

    // Authentication success
    if (continueChainBeforeSuccessfulAuthentication) {
    chain.doFilter(request, response);
    }

    successfulAuthentication(request, response, chain, authResult); // clientid校验通过了,通过各种filter,最后调用TokenEndpoint中真正的/oauth/token
    }

    protected void successfulAuthentication(HttpServletRequest request,
    HttpServletResponse response, FilterChain chain, Authentication authResult)
    throws IOException, ServletException {

    if (logger.isDebugEnabled()) {
    logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
    + authResult);
    }

    SecurityContextHolder.getContext().setAuthentication(authResult);

    rememberMeServices.loginSuccess(request, response, authResult);

    // Fire event
    if (this.eventPublisher != null) {
    eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
    authResult, this.getClass()));
    }

    successHandler.onAuthenticationSuccess(request, response, authResult); //调用TokenEndpoint中真正的/oauth/token,进行后续username,password验证
    }
    }
  2. 1中的attemptAuthentication方法
    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
    public class ClientCredentialsTokenEndpointFilter extends AbstractAuthenticationProcessingFilter {
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
    throws AuthenticationException, IOException, ServletException {

    if (allowOnlyPost && !"POST".equalsIgnoreCase(request.getMethod())) {
    throw new HttpRequestMethodNotSupportedException(request.getMethod(), new String[] { "POST" });
    }

    String clientId = request.getParameter("client_id");
    String clientSecret = request.getParameter("client_secret");

    // If the request is already authenticated we can assume that this
    // filter is not needed
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication != null && authentication.isAuthenticated()) {
    return authentication;
    }

    if (clientId == null) {
    throw new BadCredentialsException("No client credentials presented");
    }

    if (clientSecret == null) {
    clientSecret = "";
    }

    clientId = clientId.trim();
    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId,
    clientSecret);

    return this.getAuthenticationManager().authenticate(authRequest); // ** 重要 调用ProviderManager类中方法,校验username与password

    }
    }
  3. 2中调用的authenticate方法
    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
    public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    AuthenticationException parentException = null;
    Authentication result = null;
    Authentication parentResult = null;
    boolean debug = logger.isDebugEnabled();
    Iterator var8 = this.getProviders().iterator();

    while(var8.hasNext()) {
    AuthenticationProvider provider = (AuthenticationProvider)var8.next();
    if (provider.supports(toTest)) {
    if (debug) {
    logger.debug("Authentication attempt using " + provider.getClass().getName());
    }

    try {
    result = provider.authenticate(authentication); // 遍历所有Provider,只要有一个结果就返回,这里是AbstractUserDetailsAuthenticationProvider类返回结果
    if (result != null) {
    this.copyDetails(authentication, result);
    break;
    }
    } catch (InternalAuthenticationServiceException | AccountStatusException var13) {
    this.prepareException(var13, authentication);
    throw var13;
    } catch (AuthenticationException var14) {
    lastException = var14;
    }
    }
    }

    if (result == null && this.parent != null) {
    try {
    result = parentResult = this.parent.authenticate(authentication);
    } catch (ProviderNotFoundException var11) {
    } catch (AuthenticationException var12) {
    parentException = var12;
    lastException = var12;
    }
    }

    if (result != null) {
    if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
    ((CredentialsContainer)result).eraseCredentials();
    }

    if (parentResult == null) {
    this.eventPublisher.publishAuthenticationSuccess(result);
    }

    return result;
    } else {
    if (lastException == null) {
    lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
    }

    if (parentException == null) {
    this.prepareException((AuthenticationException)lastException, authentication);
    }

    throw lastException;
    }
    }
    }
  4. 3中调用的AbstractUserDetailsAuthenticationProvider的authenticate方法
    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
    public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
    return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
    });
    String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
    boolean cacheWasUsed = true;
    UserDetails user = this.userCache.getUserFromCache(username);
    if (user == null) {
    cacheWasUsed = false;

    try {
    user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); // 检查用户是否存在,调用DaoAuthenticationProvider类
    } catch (UsernameNotFoundException var6) {
    this.logger.debug("User '" + username + "' not found");
    if (this.hideUserNotFoundExceptions) {
    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
    }

    throw var6;
    }

    Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
    }

    try {
    this.preAuthenticationChecks.check(user);
    this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); // 验证client_id,client_secret是否正确,调用DaoAuthenticationProvider类
    } catch (AuthenticationException var7) {
    if (!cacheWasUsed) {
    throw var7;
    }

    cacheWasUsed = false;
    user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
    this.preAuthenticationChecks.check(user);
    this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
    }

    this.postAuthenticationChecks.check(user);
    if (!cacheWasUsed) {
    this.userCache.putUserInCache(user);
    }

    Object principalToReturn = user;
    if (this.forcePrincipalAsString) {
    principalToReturn = user.getUsername();
    }

    return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }
    }
  5. 4中调用的DaoAuthenticationProvider的retrieveUser方法
    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
    public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    protected final UserDetails retrieveUser(String username,
    UsernamePasswordAuthenticationToken authentication)
    throws AuthenticationException {
    prepareTimingAttackProtection();
    try {
    UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); // 调用ClientDetailsUserDetailsService类
    if (loadedUser == null) {
    throw new InternalAuthenticationServiceException(
    "UserDetailsService returned null, which is an interface contract violation");
    }
    return loadedUser;
    }
    catch (UsernameNotFoundException ex) {
    mitigateAgainstTimingAttack(authentication);
    throw ex;
    }
    catch (InternalAuthenticationServiceException ex) {
    throw ex;
    }
    catch (Exception ex) {
    throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
    }
    }
    }
  6. 5中调用的ClientDetailsUserDetailsService类的loadUserByUsername方法,执行完后接着返回执行4之后的方法
    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
    public class ClientDetailsUserDetailsService implements UserDetailsService {
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    ClientDetails clientDetails;
    try {
    clientDetails = clientDetailsService.loadClientByClientId(username); // 这边clientDetailsService一般是自己注入的service,RedisClientDetailsService根据clienid获取Client信息
    } catch (NoSuchClientException e) {
    throw new UsernameNotFoundException(e.getMessage(), e);
    }
    String clientSecret = clientDetails.getClientSecret();
    if (clientSecret== null || clientSecret.trim().length()==0) {
    clientSecret = emptyPassword;
    }
    return new User(username, clientSecret, clientDetails.getAuthorities());
    }
    }
    public class JdbcClientDetailsService implements ClientDetailsService, ClientRegistrationService {

    }
    public class RedisClientDetailsService extends JdbcClientDetailsService
    {
    public RedisClientDetailsService(DataSource dataSource)
    {
    super(dataSource);
    super.setSelectClientDetailsSql(SecurityConstants.DEFAULT_SELECT_STATEMENT);
    super.setFindClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT);
    }

    @Override
    @Cacheable(value = CacheConstants.CLIENT_DETAILS_KEY, key = "#clientId", unless = "#result == null")
    public ClientDetails loadClientByClientId(String clientId)
    {
    return super.loadClientByClientId(clientId);
    }
    }
    public class UserDetailsServiceImpl implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 自己注入的实现类,根据username获取用户信息
    Result result = remoteUserService.getUserInfo(username);
    checkUser(result, username);
    return getUserDetails(result);
    }
    }
  7. 4中调用的DaoAuthenticationProvider类的additionalAuthenticationChecks方法,此处执行完则主要过滤器执行完毕,后续会进入/oauth/token映射的方法,进行真正的username password校验
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    @SuppressWarnings("deprecation")
    protected void additionalAuthenticationChecks(UserDetails userDetails,
    UsernamePasswordAuthenticationToken authentication)
    throws AuthenticationException {
    if (authentication.getCredentials() == null) {
    logger.debug("Authentication failed: no credentials provided");

    throw new BadCredentialsException(messages.getMessage(
    "AbstractUserDetailsAuthenticationProvider.badCredentials",
    "Bad credentials"));
    }

    String presentedPassword = authentication.getCredentials().toString();

    if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { // presentedPassword为client_secret。后面真正用户passowrd也是调用该方法
    logger.debug("Authentication failed: password does not match stored value");

    throw new BadCredentialsException(messages.getMessage(
    "AbstractUserDetailsAuthenticationProvider.badCredentials",
    "Bad credentials"));
    }
    }
    }
  8. 此处进入/oauth/token映射的TokenEndpoint类的postAccessToken方法
    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
    public class TokenEndpoint extends AbstractEndpoint {
    @RequestMapping(value = "/oauth/token", method=RequestMethod.GET)
    public ResponseEntity<OAuth2AccessToken> getAccessToken(Principal principal, @RequestParam
    Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
    if (!allowedRequestMethods.contains(HttpMethod.GET)) {
    throw new HttpRequestMethodNotSupportedException("GET");
    }
    return postAccessToken(principal, parameters);
    }

    @RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
    public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
    Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {

    if (!(principal instanceof Authentication)) {
    throw new InsufficientAuthenticationException(
    "There is no client authentication. Try adding an appropriate authentication filter.");
    }

    String clientId = getClientId(principal);
    ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId); // 获取ClientDetails

    TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);

    if (clientId != null && !clientId.equals("")) {
    // Only validate the client details if a client authenticated during this
    // request.
    if (!clientId.equals(tokenRequest.getClientId())) {
    // double check to make sure that the client ID in the token request is the same as that in the
    // authenticated client
    throw new InvalidClientException("Given client ID does not match authenticated client");
    }
    }
    if (authenticatedClient != null) {
    oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
    }
    if (!StringUtils.hasText(tokenRequest.getGrantType())) {
    throw new InvalidRequestException("Missing grant type");
    }
    if (tokenRequest.getGrantType().equals("implicit")) {
    throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
    }

    if (isAuthCodeRequest(parameters)) { // 验证scope
    // The scope was requested or determined during the authorization step
    if (!tokenRequest.getScope().isEmpty()) {
    logger.debug("Clearing scope of incoming token request");
    tokenRequest.setScope(Collections.<String> emptySet());
    }
    }

    if (isRefreshTokenRequest(parameters)) {
    // A refresh token has its own default scopes, so we should ignore any added by the factory here.
    tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
    }

    OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest); // 调用AbstractTokenGranter类,生成OAuth2AccessToken对象
    if (token == null) {
    throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
    }

    return getResponse(token); // 包装OAuth2AccessToken对象返回

    }
    }
  9. 此处是8中的AbstractTokenGranter类的grant方法
    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
    public abstract class AbstractTokenGranter implements TokenGranter {
    public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {

    if (!this.grantType.equals(grantType)) {
    return null;
    }

    String clientId = tokenRequest.getClientId();
    ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
    validateGrantType(grantType, client);

    if (logger.isDebugEnabled()) {
    logger.debug("Getting access token for: " + clientId);
    }

    return getAccessToken(client, tokenRequest);// 调用下面方法

    }
    protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
    return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest)); // 调用ResourceOwnerPasswordTokenGranter类返回OAuth2Authentication对象
    }
    }

    // 可以忽略
    public class CompositeTokenGranter implements TokenGranter {

    private final List<TokenGranter> tokenGranters;

    public CompositeTokenGranter(List<TokenGranter> tokenGranters) {
    this.tokenGranters = new ArrayList<TokenGranter>(tokenGranters);
    }

    public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
    for (TokenGranter granter : tokenGranters) { // 授权模式,我们用的是密码模式
    OAuth2AccessToken grant = granter.grant(grantType, tokenRequest); // 调用 ResourceOwnerPasswordTokenGranter,执行AbstractTokenGranter grant方法
    if (grant!=null) {
    return grant;
    }
    }
    return null;
    }

    public void addTokenGranter(TokenGranter tokenGranter) {
    if (tokenGranter == null) {
    throw new IllegalArgumentException("Token granter is null");
    }
    tokenGranters.add(tokenGranter);
    }

    }
  10. 9中ResourceOwnerPasswordTokenGranter类中的getOAuth2Authentication方法
    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
    public class ResourceOwnerPasswordTokenGranter extends AbstractTokenGranter {
    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

    Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
    String username = parameters.get("username");
    String password = parameters.get("password");
    // Protect from downstream leaks of password
    parameters.remove("password");

    Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password); // ** 重要 准备验证用户密码 ,username password
    ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
    try {
    userAuth = authenticationManager.authenticate(userAuth); // provider通常为用户自定义,** 验证username password
    }
    catch (AccountStatusException ase) {
    //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
    throw new InvalidGrantException(ase.getMessage());
    }
    catch (BadCredentialsException e) {
    // If the username/password are wrong the spec says we should send 400/invalid grant
    throw new InvalidGrantException(e.getMessage());
    }
    if (userAuth == null || !userAuth.isAuthenticated()) {
    throw new InvalidGrantException("Could not authenticate user: " + username);
    }

    OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
    return new OAuth2Authentication(storedOAuth2Request, userAuth);
    }
    }
  11. 9中调用的DefaultTokenServices中的createAccessToken方法
    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 DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
    ConsumerTokenServices, InitializingBean {
    @Transactional
    public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {

    OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication); // 通过tokenStore获取token
    OAuth2RefreshToken refreshToken = null;
    if (existingAccessToken != null) {
    if (existingAccessToken.isExpired()) {
    if (existingAccessToken.getRefreshToken() != null) {
    refreshToken = existingAccessToken.getRefreshToken();
    // The token store could remove the refresh token when the
    // access token is removed, but we want to
    // be sure...
    tokenStore.removeRefreshToken(refreshToken);
    }
    tokenStore.removeAccessToken(existingAccessToken);
    }
    else {
    // Re-store the access token in case the authentication has changed
    tokenStore.storeAccessToken(existingAccessToken, authentication);
    return existingAccessToken;
    }
    }

    // Only create a new refresh token if there wasn't an existing one
    // associated with an expired access token.
    // Clients might be holding existing refresh tokens, so we re-use it in
    // the case that the old access token
    // expired.
    if (refreshToken == null) {
    refreshToken = createRefreshToken(authentication); // 不存在就创建
    }
    // But the refresh token itself might need to be re-issued if it has
    // expired.
    else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
    ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
    if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
    refreshToken = createRefreshToken(authentication);
    }
    }

    OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
    tokenStore.storeAccessToken(accessToken, authentication);
    // In case it was modified
    refreshToken = accessToken.getRefreshToken();
    if (refreshToken != null) {
    tokenStore.storeRefreshToken(refreshToken, authentication);
    }
    return accessToken; // 返回token

    }
    }
  12. 11中RedisTokenStore中的getAccessToken方法等,此处执行完,则一直向上返回8执行后续方法
    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
    public class RedisTokenStore implements TokenStore {
    @Override
    public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
    String key = authenticationKeyGenerator.extractKey(authentication);
    byte[] serializedKey = serializeKey(AUTH_TO_ACCESS + key);
    byte[] bytes = null;
    RedisConnection conn = getConnection();
    try {
    bytes = conn.get(serializedKey);
    } finally {
    conn.close();
    }
    OAuth2AccessToken accessToken = deserializeAccessToken(bytes);
    if (accessToken != null) {
    OAuth2Authentication storedAuthentication = readAuthentication(accessToken.getValue());
    if ((storedAuthentication == null || !key.equals(authenticationKeyGenerator.extractKey(storedAuthentication)))) {
    // Keep the stores consistent (maybe the same user is
    // represented by this authentication but the details have
    // changed)
    storeAccessToken(accessToken, authentication);
    }

    }
    return accessToken;
    }
    }
  13. 8中获取到token后需要包装返回流操作
    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
    public class TokenEndpoint extends AbstractEndpoint {
    @RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
    public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
    Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {

    if (!(principal instanceof Authentication)) {
    throw new InsufficientAuthenticationException(
    "There is no client authentication. Try adding an appropriate authentication filter.");
    }

    String clientId = getClientId(principal);
    ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);

    TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);

    if (clientId != null && !clientId.equals("")) {
    // Only validate the client details if a client authenticated during this
    // request.
    if (!clientId.equals(tokenRequest.getClientId())) {
    // double check to make sure that the client ID in the token request is the same as that in the
    // authenticated client
    throw new InvalidClientException("Given client ID does not match authenticated client");
    }
    }
    if (authenticatedClient != null) {
    oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
    }
    if (!StringUtils.hasText(tokenRequest.getGrantType())) {
    throw new InvalidRequestException("Missing grant type");
    }
    if (tokenRequest.getGrantType().equals("implicit")) {
    throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
    }

    if (isAuthCodeRequest(parameters)) {
    // The scope was requested or determined during the authorization step
    if (!tokenRequest.getScope().isEmpty()) {
    logger.debug("Clearing scope of incoming token request");
    tokenRequest.setScope(Collections.<String> emptySet());
    }
    }

    if (isRefreshTokenRequest(parameters)) {
    // A refresh token has its own default scopes, so we should ignore any added by the factory here.
    tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
    }

    OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
    if (token == null) {
    throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
    }

    return getResponse(token); // token返回响应

    }
    }

POSTMAN访问时传递的ClientID会再DaoAuthenticationProvider方法中进行校验通过JdbcClientDetailsService实现。然后调用真正/oauth/token方法进行username校验,也是调用DaoAuthenticationProvider.retrieveUser进行校验,getUserDetailsService对象不一样,校验username的时候是remoteUserService。UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);

扩展

请求拦截

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
---Auth授权服务器,鉴权模块的信息---
"http-nio-9001-exec-6@10505" daemon prio=5 tid=0x40 nid=NA runnable
java.lang.Thread.State: RUNNABLE
at org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint.checkToken(CheckTokenEndpoint.java:74)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:652)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:126)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:118)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:158)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilterInternal(BasicAuthenticationFilter.java:204)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:888)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1597)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
- locked <0x3894> (a org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)

---被调用模块,也就是资源服务器的信息---
"http-nio-8156-exec-2@9364" daemon prio=5 tid=0x40 nid=NA runnable
java.lang.Thread.State: RUNNABLE
at org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter.doFilter(OAuth2AuthenticationProcessingFilter.java:162)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:888)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1597)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
- locked <0x3016> (a org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)

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
public class FilterChainProxy extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (currentPosition == size) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " reached end of additional filter chain; proceeding with original chain");
}

// Deactivate path stripping as we exit the security filter chain
this.firewalledRequest.reset();

originalChain.doFilter(request, response);
}
else {
currentPosition++;

Filter nextFilter = additionalFilters.get(currentPosition - 1);// 有15个过滤器

if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " at position " + currentPosition + " of " + size
+ " in additional filter chain; firing Filter: '"
+ nextFilter.getClass().getSimpleName() + "'");
}

nextFilter.doFilter(request, response, this);
}
}
}
}
public class BasicAuthenticationFilter extends OncePerRequestFilter {
// 获取权限进行校验
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
final boolean debug = this.logger.isDebugEnabled();
try {
UsernamePasswordAuthenticationToken authRequest = authenticationConverter.convert(request);
if (authRequest == null) {
chain.doFilter(request, response);
return;
}

String username = authRequest.getName();

if (debug) {
this.logger
.debug("Basic Authentication Authorization header found for user '"
+ username + "'");
}

if (authenticationIsRequired(username)) {
Authentication authResult = this.authenticationManager
.authenticate(authRequest);

if (debug) {
this.logger.debug("Authentication success: " + authResult);
}

SecurityContextHolder.getContext().setAuthentication(authResult);

this.rememberMeServices.loginSuccess(request, response, authResult);

onSuccessfulAuthentication(request, response, authResult);
}

}
catch (AuthenticationException failed) {
SecurityContextHolder.clearContext();

if (debug) {
this.logger.debug("Authentication request for failed!", failed);
}

this.rememberMeServices.loginFail(request, response);

onUnsuccessfulAuthentication(request, response, failed);

if (this.ignoreFailure) {
chain.doFilter(request, response);
}
else {
this.authenticationEntryPoint.commence(request, response, failed);
}

return;
}

chain.doFilter(request, response);
}
}
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

processRequest(request, response);
}

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

long startTime = System.currentTimeMillis();
Throwable failureCause = null;

LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);

RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

initContextHolders(request, localeContext, requestAttributes);

try {
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}

finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
}

public class DispatcherServlet extends FrameworkServlet {
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);

// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}

// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}

try {
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
}

被调用模块记得引入Auth2.0 Security模块

引入模块并且通过注解启用

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
/**
* SecurityImportBeanDefinitionRegistrar 安全模块注册
* OAuth2FeignConfig 内部调用增加请求头
* ApplicationConfig 其他配置
*/
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
@Import({ SecurityImportBeanDefinitionRegistrar.class, OAuth2FeignConfig.class, ApplicationConfig.class })
public class WebSocketApplication {

public static void main(String[] args) {
SpringApplication.run(WebSocketApplication.class, args);
}

}

/**
* 导入自动加载,资源服务
*/
public class SecurityImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar
{
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry)
{
// 资源服务配置
Class<ResourceServerConfig> aClass = ResourceServerConfig.class;
String beanName = StringUtils.uncapitalize(aClass.getSimpleName());
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ResourceServerConfig.class);
registry.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
}
}

SecurityImportBeanDefinitionRegistrar调用链

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
java.lang.Thread.State: RUNNABLE
at org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition(DefaultListableBeanFactory.java:929) // this.beanDefinitionMap 有价值的所有Bean
at vip.infotech.test.common.security.config.SecurityImportBeanDefinitionRegistrar.registerBeanDefinitions(SecurityImportBeanDefinitionRegistrar.java:22)
at org.springframework.context.annotation.ImportBeanDefinitionRegistrar.registerBeanDefinitions(ImportBeanDefinitionRegistrar.java:86)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.lambda$loadBeanDefinitionsFromRegistrars$1(ConfigurationClassBeanDefinitionReader.java:384)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$$Lambda$181.1374982240.accept(Unknown Source:-1)
at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars(ConfigurationClassBeanDefinitionReader.java:383)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:148)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:120)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:331) // Build and validate a configuration model based on the registry of
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:236)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:280)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:96)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:707)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:533)
- locked <0x13f5> (a java.lang.Object)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:405)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at vip.infotech.test.module.WebSocketApplication.main(WebSocketApplication.java:34)

java.lang.Thread.State: RUNNABLE
at vip.infotech.test.common.security.config.ResourceServerConfig.tokenServices(ResourceServerConfig.java:54)
at vip.infotech.test.common.security.config.ResourceServerConfig$$EnhancerBySpringCGLIB$$d0a1096b.CGLIB$tokenServices$3(<generated>:-1)
at vip.infotech.test.common.security.config.ResourceServerConfig$$EnhancerBySpringCGLIB$$d0a1096b$$FastClassBySpringCGLIB$$ffde92b9.invoke(<generated>:-1)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331)
at vip.infotech.test.common.security.config.ResourceServerConfig$$EnhancerBySpringCGLIB$$d0a1096b.tokenServices(<generated>:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:652)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:485)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1336)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1176)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:556)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$145.636959006.getObject(Unknown Source:-1)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
- locked <0x21d3> (a java.util.concurrent.ConcurrentHashMap)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.addCandidateEntry(DefaultListableBeanFactory.java:1525)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1489)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveMultipleBeans(DefaultListableBeanFactory.java:1408)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1265) // resolveMultipleBeans
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1227)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1420)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$145.636959006.getObject(Unknown Source:-1)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:624)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:612)
at org.springframework.security.config.annotation.web.configuration.AutowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers(AutowiredWebSecurityConfigurersIgnoreParents.java:53)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.expression.spel.support.ReflectiveMethodExecutor.execute(ReflectiveMethodExecutor.java:129)
at org.springframework.expression.spel.ast.MethodReference.getValueInternal(MethodReference.java:139)
at org.springframework.expression.spel.ast.MethodReference.access$000(MethodReference.java:55)
at org.springframework.expression.spel.ast.MethodReference$MethodValueRef.getValue(MethodReference.java:387)
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:92)
at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:112)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:272)
at org.springframework.context.expression.StandardBeanExpressionResolver.evaluate(StandardBeanExpressionResolver.java:166)
at org.springframework.beans.factory.support.AbstractBeanFactory.evaluateBeanDefinitionString(AbstractBeanFactory.java:1575)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1251) // evaluateBeanDefinitionString
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1227)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:714)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1420)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$145.636959006.getObject(Unknown Source:-1)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:897)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)
- locked <0x2226> (a java.lang.Object)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:405)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at vip.infotech.test.module.WebSocketApplication.main(WebSocketApplication.java:34)

OAuth2FeignConfig初始调用链

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
java.lang.Thread.State: RUNNABLE
at vip.infotech.test.common.security.feign.OAuth2FeignConfig.requestInterceptor(OAuth2FeignConfig.java:17)
at vip.infotech.test.common.security.feign.OAuth2FeignConfig$$EnhancerBySpringCGLIB$$9120d89e.CGLIB$requestInterceptor$0(<generated>:-1)
at vip.infotech.test.common.security.feign.OAuth2FeignConfig$$EnhancerBySpringCGLIB$$9120d89e$$FastClassBySpringCGLIB$$215a06c2.invoke(<generated>:-1)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331)
at vip.infotech.test.common.security.feign.OAuth2FeignConfig$$EnhancerBySpringCGLIB$$9120d89e.requestInterceptor(<generated>:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:652)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:485)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1336)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1176)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:556)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$145.1482748887.getObject(Unknown Source:-1)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
- locked <0x146e> (a java.util.concurrent.ConcurrentHashMap)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:897)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)
- locked <0x13f5> (a java.lang.Object)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:405)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at vip.infotech.test.module.WebSocketApplication.main(WebSocketApplication.java:34)

微服务权限应用

权限控制可以分为三个部分:用户认证,服务权限,用户权限。
在微服务上校验用户token的话,还要考虑到要与服务调用服务的场景区分,增加了校验的复杂程度,所以选择将用户认证统一放在gateway网关校验。
服务权限认证,放在在被调用具体服务上实现。微服务间的权限校验。
用户权限认证,放在在被调用具体服务上实现(调用鉴权服务),功能权限、数据权限。主要是因为权限注解在controller层,如果把权限与API接口信息配置到数据库中也是可以直接在gateway网关中获取权限信息直接校验过滤的。

使用Feign可以实现RequestInterceptor在请求头中增加TOKEN。服务间权限校验除了使用token还可以使用私钥。

其他方案:
比如直接用redis
登录:登录成功后把token与token对应用户信息存储在redis中
请求:在gateway中验证token是否有效(redis中获取token信息),更新redis中token过期时间,把token对应的用户信息(用户id、用户名等)存储到请求头中转发给后端微服务,微服务根据token从redis中获取用户详细信息校验是否有访问接口的权限

demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
public class OAuth2FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes(); // ThreadLocal中获取
HttpServletRequest request = attributes.getRequest();
// Enumeration<String> headerNames = request.getHeaderNames();
String values = request.getHeader(HttpHeaders.AUTHORIZATION);
requestTemplate.header(HttpHeaders.AUTHORIZATION,
String.format("%s %s", SecurityConstants.BEARER_TOKEN_TYPE, values));
}
}

protected String getRequestTokenTest() {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
return request.getHeader(HttpHeaders.AUTHORIZATION);
}

问题

返回BUG

if (!Boolean.TRUE.equals(map.get("active"))) { 返回的是String类型,需要的是boolean类型。可能是引入的包导致的。解决方法就行重写org.springframework.security.oauth2.provider.token.RemoteTokenServices类。下面这个依赖导致的问题。这个json转xml

1
2
3
4
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>

参考