【问题标题】:Using user roles in resource server to restrict acces on path在资源服务器中使用用户角色来限制路径上的访问
【发布时间】:2021-10-12 01:04:36
【问题描述】:

我想使用新的 Spring Security Authorization Server 为我的 web 服务实现 OAuth2。

https://www.baeldung.com/spring-security-oauth-auth-server举个例子,分离

  • 授权服务器
  • 资源服务器
  • 客户

代码可以在https://github.com/Baeldung/spring-security-oauth/tree/master/oauth-authorization-server找到

这三个 Maven 项目在给定版本中运行,当客户端在 web 浏览器中访问 http://localhost:8080/articles 时输出正确

["Article 1","Article 2","Article 3"]

我需要根据用户定义的角色来限制对路径的访问,或者也许有权限也可以。

在这个例子中,它是在资源服务器中实现的

@EnableWebSecurity
public class ResourceServerConfig {
    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.mvcMatcher("/articles/**")
            .authorizeRequests()
            .mvcMatchers("/articles/**").access("hasAuthority('SCOPE_articles.read')")
            .and()
            .oauth2ResourceServer()
            .jwt();
            
         return http.build();
    } 

我改成

http
    .authorizeRequests()
    .antMatchers("/articles").hasRole("ADMIN")

并且还在授权服务器项目中定义了一个用户admin / admin123.roles("ADMIN")

但是,这并没有按预期运行,当客户端访问 http://localhost:8080/articles 我在客户端项目控制台中得到输出

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.reactive.function.client.WebClientResponseException$Forbidden: 403 Forbidden from GET http://localhost:8090/articles] with root cause

org.springframework.web.reactive.function.client.WebClientResponseException$Forbidden: 403 Forbidden from GET http://localhost:8090/articles
    at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:183) ~[spring-webflux-5.3.4.jar:5.3.4]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    |_ checkpoint ⇢ 403 from GET http://localhost:8090/articles [DefaultWebClient]

我找到了一个示例项目https://blog.jdriven.com/2019/10/spring-security-5-2-oauth-2-exploration-part1/,但不幸的是它使用 Keycloak 作为授权服务器,但实现了相同的想法,即使用用户角色限制对路径的访问。

它定义了一个KeycloakRealmRoleConverter,但是如何为给定的三个Baeldung项目定义一个?

Keycloak 的例子使用了jwt.getClaims().get("realm_access"),它显然访问了一个密钥realm_access,但这与Keycloak 有关。

打印出我在资源服务器 REST 控制器中更改的 JWT 令牌

@GetMapping("/articles")
public String[] getArticles(final @AuthenticationPrincipal Jwt 
    System.out.println("\n\njwt.getTokenValue():\n" + jwt.getTokenValue());
...
}

jwt.io 显示

HEADER:ALGORITHM & TOKEN TYPE
{
  "kid": "fce0c3e4-a9a6-4e6d-8fbd-c2b774b338f0",
  "typ": "JWT",
  "alg": "RS256"
}

PAYLOAD:DATA
{
  "sub": "admin",
  "aud": "articles-client",
  "nbf": 1628351323,
  "scope": [
    "articles.read"
  ],
  "iss": "http://127.0.0.1:9000",
  "exp": 1628351623,
  "iat": 1628351323,
  "jti": "c4bc5f35-e93f-483f-8952-a694a04f8f32"
}

那里没有定义角色。我本来希望在用户名 admin 旁边还有为该用户定义的角色 ADMIN

不确定如何限制资源服务器中具有用户角色的路径的访问,如果它不在 JWT 中。

【问题讨论】:

    标签: spring-boot spring-security spring-security-oauth2


    【解决方案1】:

    我们先来看看Spring Security中权限、角色和作用域的关系。

    角色只是一个以ROLE_为前缀的权限。
    配置.antMatchers("/articles/**").hasRole("ADMIN")等价于.antMatchers("/articles/**").hasAuthority("ROLE_ADMIN")

    默认情况下,资源服务器根据“范围”声明填充权限,方法是在每个值前面加上 SCOPE_
    在您提供的示例中,“范围”声明包含“articles.read”,这意味着唯一的权限是SCOPE_articles.read
    使用默认值时,资源服务器没有角色的概念,因为它没有以ROLE_为前缀的权限(它们都以SCOPE_为前缀)。

    授予“admin”用户的角色“ADMIN”在授权服务器中可用。
    它不是令牌的一部分,因为令牌不代表用户,而是客户端访问用户数据的特定部分的授权。

    如果资源服务器需要用户信息,我建议查看OpenID Connect Protocol

    Here 是一个类似的问题,它解释了为什么不应将作用域用于此目的。

    【讨论】:

    • 只是自发地想...可以修改 Authorizaton Server 项目,使 JWT 令牌包含一个额外的键/值对“roles”:[“ADMIN”,...]?
    • 可以,但我仍然建议您研究 OpenID Connect 而不是构建自定义的东西。
    • 好的,但在我看来这是一个基本的要求/功能,我认为 Spring Security Authorization Server 项目,现在在 0.1.2 中,应该集成它。
    【解决方案2】:

    以下可能是一个解决方案,灵感来自 Keycloak 示例 https://blog.jdriven.com/2019/10/spring-security-5-2-oauth-2-exploration-part1/

    资源服务器项目

    @EnableWebSecurity
    public class ResourceServerConfig {
        @Bean
        SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                .antMatchers("/articles/**").hasRole("ADMIN")
                .and()
                .oauth2ResourceServer()
                .jwt()
                .jwtAuthenticationConverter(jwtAuthenticationConverter())
                ;
    
            return http.build();
        }
        
        JwtAuthenticationConverter jwtAuthenticationConverter() {
            final JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
            jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new MyRoleConverter());
            
            return jwtAuthenticationConverter;
        }    
    
        public class MyRoleConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
            @Override
            public Collection<GrantedAuthority> convert(final Jwt jwt) {
                Map<String, String> userRoles = Map.of("admin", "ADMIN", "myuser", "MYUSER");
                
                List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
                
                String subject = jwt.getSubject();
                
                SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_" + userRoles.get(subject));
                simpleGrantedAuthorities.add(simpleGrantedAuthority);   
                
                return new ArrayList<>(simpleGrantedAuthorities);
            }
        }
    }
    

    出于测试目的,我将我的用户及其角色硬编码为

    Map<String, String> userRoles = Map.of("admin", "ADMIN", "myuser", "MYUSER");
    

    ResourceServerConfig 类中的某个位置。

    现在我必须在两个地方存储/创建用户和角色,在授权服务器项目和资源服务器项目中,当然,如果有一个数据库,它只会集中在一个地方。

    这是一个简单的例子,假设用户只有一个角色。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-11
      相关资源
      最近更新 更多