spring cloud升级到2020.x以后不再包含spring security 项目可以继续使用spring security oauth 2.x版本或者升级到spring security 5.x 官方迁移指引
差异
- 废弃@EnableResourceServer注解,改为使用oauth2ResourceServer方法
- 废弃ResourceServerConfigurerAdapter,改为在WebSecurityConfigurerAdapter暴露相同功能
- 鉴权表达式变更
spring security oauth 2.x | spring security 5.x |
---|---|
access("#oauth2.hasScope(‘scope’)") | hasAuthority(“SCOPE_scope”) |
依赖
代码语言:javascript复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>oauth2-oidc-sdk</artifactId>
</dependency>
注:使用 Opaque Token - 不透明令牌 模式,没有引入nimbusds依赖会报错java.lang.ClassNotFoundException: com.nimbusds.oauth2.sdk.http.HTTPResponse
配置
- 沿用2.x版本的配置文件的配置
public class ResourceServerConfig extends WebSecurityConfigurerAdapter {
final String clientId;
final String clientSecret;
final String introspectionUrl;
public ResourceServerConfig(
@Value("${security.oauth2.client.client-id}") final String clientId,
@Value("${security.oauth2.client.client-secret}") final String clientSecret,
@Value("${security.oauth2.resource.tokenInfoUri}") final String introspectionUrl
) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.introspectionUrl = introspectionUrl;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(
authorizeRequestsCustomizer ->
authorizeRequestsCustomizer
.antMatchers("/api/**").hasAnyAuthority("SCOPE_scope")
.anyRequest().permitAll()
).oauth2ResourceServer(
httpSecurityOAuth2ResourceServerConfigurer ->
httpSecurityOAuth2ResourceServerConfigurer
.opaqueToken()
.introspectionUri(introspectionUrl)
.introspectionClientCredentials(clientId, clientSecret)
);
}
}
- 使用5.x版本配置,java配置直接使用默认配置即可
spring:
security:
oauth2:
resourceserver:
opaque-token:
introspection-uri: http://loacalhost/oauth/check_token
client-id: client-id
client-secret: client-secret
代码语言:javascript复制http.oauth2ResourceServer().opaqueToken();
老授权服务器兼容性改造
这样配置后访问如果仍使用2.x搭建的授权服务器资源会报错403 ,
Bearer error=“insufficient_scope”,error_description=“The request requires higher privileges than provided by the access token.”,error_uri=“https://tools.ietf.org/html/rfc6750#section-3.1”
原因是2.x中scope的格式是数组,而nimbusds需要逗号分隔的字符串,从而导致check_token的响应中scope无法正常反序列化。
不想升级spring security的话解决办法是自定义AccessTokenConverter将scope拼接为逗号分隔的字符串
代码语言:javascript复制public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
...
endpoints.accessTokenConverter(new DefaultAccessTokenConverter() {
@Override
public Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
Map response = super.convertAccessToken(token, authentication);
if (token.getScope() != null) {
response.put(
AccessTokenConverter.SCOPE,
StringUtils.join(token.getScope(), ',')
);
}
return response;
}
});
...
}
源码分析
- 授权服务器令牌校验端点 org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint
private AccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
@RequestMapping(value = "/oauth/check_token")
@ResponseBody
public Map<String, ?> checkToken(@RequestParam("token") String value) {
...
Map<String, Object> response = (Map<String, Object>)accessTokenConverter.convertAccessToken(token, authentication);
...
return response;
}
- 授权服务器默认令牌转换器 org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter
public Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
// 此处scope属性是集合,序列化为json数组格式
if (token.getScope()!=null) {
response.put(scopeAttribute, token.getScope());
}
}
- 资源服务器令牌校验 com.nimbusds.oauth2.sdk.TokenIntrospectionSuccessResponse
public Scope getScope() {
try {
// 此处以字符串类型解析scope字段,实际上获得的是JSONArray对象
return Scope.parse(JSONObjectUtils.getString(this.params, "scope"));
} catch (ParseException var2) {
return null;
}
}
- 资源服务器 com.nimbusds.oauth2.sdk.Scope
public static Scope parse(String s) {
if (s == null) {
return null;
} else {
Scope scope = new Scope();
if (s.trim().isEmpty()) {
return scope;
} else {
StringTokenizer st = new StringTokenizer(s, " ,");
while(st.hasMoreTokens()) {
scope.add(new Scope.Value(st.nextToken()));
}
return scope;
}
}
}