spring security 5 oauth2 资源服务器无法正确处理用户授权 报错insufficient_scope

现象

客户端通过授权码模式获取不透明令牌(opaque token),使用令牌访问资源服务器
资源服务器安全配置只能处理客户端scope授权,如果添加用户授权的判定规则,则报错

www-authenticate: 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”

原因

spring security 5 默认的令牌校验逻辑只处理scope,没有处理用户授权

源码

  • 资源服务器不透明令牌默认配置
    org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerOpaqueTokenConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(OpaqueTokenIntrospector.class)
static class OpaqueTokenIntrospectionClientConfiguration {

	@Bean
	@ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.opaquetoken.introspection-uri")
	NimbusOpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2ResourceServerProperties properties) {
		OAuth2ResourceServerProperties.Opaquetoken opaqueToken = properties.getOpaquetoken();
		return new NimbusOpaqueTokenIntrospector(opaqueToken.getIntrospectionUri(), opaqueToken.getClientId(),
				opaqueToken.getClientSecret());
	}

}
  • 内省和验证不透明令牌
    org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector
private OAuth2AuthenticatedPrincipal convertClaimsSet(TokenIntrospectionSuccessResponse response) {
	Collection<GrantedAuthority> authorities = new ArrayList<>();
	Map<String, Object> claims = response.toJSONObject();
	...
	// 处理scope授权,加上前缀"SCOPE_"
	if (response.getScope() != null) {
		List<String> scopes = Collections.unmodifiableList(response.getScope().toStringList());
		claims.put(OAuth2IntrospectionClaimNames.SCOPE, scopes);
		for (String scope : scopes) {
			authorities.add(new SimpleGrantedAuthority(this.authorityPrefix + scope));
		}
	}
	// 授权中只包含scope,用户授权直接作为一般属性传递了
	return new OAuth2IntrospectionAuthenticatedPrincipal(claims, authorities);
}

解决

  • 自定义内省器
import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.util.JSONUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal;
import org.springframework.web.client.RestOperations;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Slf4j
public class DefaultOpaqueTokenIntrospector extends NimbusOpaqueTokenIntrospector {
    public DefaultOpaqueTokenIntrospector(String introspectionUri, String clientId, String clientSecret) {
        super(introspectionUri, clientId, clientSecret);
    }

    public DefaultOpaqueTokenIntrospector(String introspectionUri, RestOperations restOperations) {
        super(introspectionUri, restOperations);
    }

    @Override
    public OAuth2AuthenticatedPrincipal introspect(String token) {
        OAuth2AuthenticatedPrincipal principal = super.introspect(token);
        try {
            List<String> userAuthorities = JSONUtils.to(principal.getAttribute("authorities"), List.class);
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            authorities.addAll(principal.getAuthorities());
            for (String userAuthority : userAuthorities) {
                authorities.add(new SimpleGrantedAuthority(userAuthority));
            }
            return new OAuth2IntrospectionAuthenticatedPrincipal(principal.getAttributes(), authorities);
        } catch (ParseException e) {
            log.warn("令牌用户授权信息解析失败", e);
        }
        return principal;
    }
}
  • 配置
@Bean
@ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.opaquetoken.introspection-uri")
NimbusOpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2ResourceServerProperties properties) {
    OAuth2ResourceServerProperties.Opaquetoken opaqueToken = properties.getOpaquetoken();
    return new DefaultOpaqueTokenIntrospector(opaqueToken.getIntrospectionUri(), opaqueToken.getClientId(),
            opaqueToken.getClientSecret());
}
```

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

导航