spring-security-oauth2 授权问题?

Spring Boot + Spring Security + Spring Authorization Server 来做RABC权限管理,一个资源服务器随便也有几百个接口吧

资源服务器配置:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
             .authorizeHttpRequests((authorize) -> authorize
                     .requestMatchers(HttpMethod.GET, "/api/get1").hasAuthority("SCOPE_api.get1")
                     .requestMatchers(HttpMethod.POST, "/api/post1").hasAuthority("SCOPE_api.post1")
                     .requestMatchers(HttpMethod.GET, "/api/get2").hasAuthority("SCOPE_api.get2")
                     .requestMatchers(HttpMethod.POST, "/api/post2").hasAuthority("SCOPE_api.post2")
                     .requestMatchers(HttpMethod.GET, "/api/get3").hasAuthority("SCOPE_api.get3")
                     .requestMatchers(HttpMethod.POST, "/api/post3").hasAuthority("SCOPE_api.post3")
                     // ...
                     .requestMatchers(HttpMethod.GET, "/api/get100").hasAuthority("SCOPE_api.get100")
                     .requestMatchers(HttpMethod.POST, "/api/post100").hasAuthority("SCOPE_api.post100")
                     .anyRequest().authenticated()
             )
             .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);

        return http.build();
    }

}

授权服务器:

@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig {

    // ...

    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("messaging-client")
                .clientSecret("{noop}secret")
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
                .redirectUri("http://127.0.0.1:8080/authorized")
                .redirectUri("https://www.baidu.com")
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .scope("api.get1")
                .scope("api.post1")
                .scope("api.get2")
                .scope("api.post2")
                .scope("api.get3")
                .scope("api.post3")
                // ...
                .scope("api.get100")
                .scope("api.post100")
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(false).build())
                .build();

        JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
        registeredClientRepository.save(registeredClient);

        return registeredClientRepository;
    }


    // ...

}

这样一来获取 access_token 会非常恐怖呀,再说,这还只是一个资源服务器,要是多个,这个 scope 不会更多, access_token 会很大很大呀,用户一多那这个 registeredClient 表也非常大呀,这样正常吗?是怎么做的吗?

阅读 1.5k
1 个回答

看了你的代码,你这种配置方法确实会导致access_token变得非常大,特别是当有许多资源服务器和API接口时。你可以考虑通过下面式优化:

将scope简化:将scope粒度减小,将它们分为更通用的权限组。例如,可以将多个API接口组合成一个功能或模块,并为其分配一个通用的scope。这样可以减少scope的数量,并减小access_token的大小。

使用角色而不是scope:在资源服务器的权限配置中,可以使用用户的角色(如ROLE_ADMIN,ROLE_USER等)而不是scope。这样,可以避免在RegisteredClient配置中添加大量scope。但是,请确保在认证服务器中将用户角色映射到对应的权限和scope。

使用数据库动态管理权限:可以使用数据库来存储和管理API接口的权限规则,这样可以避免在代码中硬编码大量的权限规则。在资源服务器的权限配置中,可以使用自定义的权限表达式处理器来从数据库中加载权限规则。

将access_token的存储方式改为JWT的引用令牌(Reference Tokens):这样,access_token只包含一个唯一的ID,资源服务器可以使用这个ID从认证服务器获取完整的用户信息和权限。这可以显著减小access_token的大小,但会增加资源服务器与认证服务器之间的通信。

确实,当有大量权限时,将所有权限放入请求URL会导致请求长度超出限制。这种情况下,你可以考虑以下两种方法来解决这个问题:
第一个方法是精简scope是一个有效的解决方法。将多个权限归类到一个更通用的权限组,从而减少scope的数量。例如,如果您有一组与用户管理相关的API,可以创建一个名为"user_management"的scope,而不是为每个API分配单独的scope。
第二是使用动态scope:
可以在授权服务器中实现一个动态scope策略,这样,客户端在请求时仅需要传递一个基本scope(例如"dynamic_scope")。然后,在授权服务器端为用户生成JWT token时,动态地添加用户实际具有的权限。这样,可以减少请求URL中scope的数量,同时仍然允许用户访问他们实际具有的权限。

我这里这里有一个关于如何在Spring Security中使用动态scope的示例你可以参考一下:

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    converter.setKeyPair(new KeyStoreKeyFactory(ksResource, ksPassword.toCharArray())
        .getKeyPair(ksAlias, ksKeyPassword.toCharArray()));
    
    // 自定义token增强器
    TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
    tokenEnhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer(), accessTokenConverter()));
    endpoints.tokenEnhancer(tokenEnhancerChain);
    
    return converter;
}

public TokenEnhancer customTokenEnhancer() {
    return (accessToken, authentication) -> {
        // 获取用户实际权限
        Collection<GrantedAuthority> authorities = (Collection<GrantedAuthority>) authentication.getUserAuthentication().getAuthorities();

        // 将权限添加到额外信息中
        Map<String, Object> additionalInfo = new HashMap<>();
        additionalInfo.put("authorities", authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()));

        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
        return accessToken;
    };
}

在这个示例中,我使用了一个自定义的TokenEnhancer,它会在生成JWT token时将用户实际权限添加到额外信息中。这样,客户端只需要请求一个"dynamic_scope",而实际权限仍然可以在生成的JWT token中找到。

你在使用这两种方法可以就解决在第一步获取授权码时,权限太多导致GET请求超出限制的问题了。下班了,还有问题可以留言,我周末有时间帮你看看😄

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进