spring security oauth2.x迁移到spring security5.x - 资源服务器

spring cloud升级到2020.x以后不再包含spring security
项目可以继续使用spring security oauth 2.x版本或者升级到spring security 5.x
官方迁移指引

差异

  1. 废弃@EnableResourceServer注解,改为使用oauth2ResourceServer方法
  2. 废弃ResourceServerConfigurerAdapter,改为在WebSecurityConfigurerAdapter暴露相同功能
  3. 鉴权表达式变更
spring security oauth 2.xspring security 5.x
access("#oauth2.hasScope(‘scope’)")hasAuthority(“SCOPE_scope”)

依赖

<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
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拼接为逗号分隔的字符串

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;
        }
    }
}

posted on 2022-04-11 22:38  路过君  阅读(298)  评论(0编辑  收藏  举报

导航