首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Spring如何在使用JWT和自定义声明时使用@PreAuthorize来测试@Service类方法

Spring如何在使用JWT和自定义声明时使用@PreAuthorize来测试@Service类方法
EN

Stack Overflow用户
提问于 2022-09-06 18:46:24
回答 1查看 329关注 0票数 0

我们有一个Spring后端应用程序,它使用使用Security和OAuth2资源服务器实现的JWT身份验证。我们通过使用antMatchers()限制对基于用户角色的URL模式的访问,以及使用全局方法安全性的服务层方法来保护我们的web层。

我们的JWT令牌是由Auth0提供的,在这里,我们已经配置为包括其他用户元数据作为自定义声明。这些声明值在@PreAuthorize注释中得到验证,例如:

代码语言:javascript
运行
复制
    @PreAuthorize("authentication.principal.claims[@environment.getProperty('jwt.organizationIdClaim')] == @environment.getProperty('current.organizationId')")
    public void deleteUser(String userId) {

在这里,属性jwt.organizationIdClaimcurrent.organizationId是从application.properties文件中获得的自定义属性。

当我尝试对上述方法进行单元测试时,会得到如下异常:

org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext

单元测试代码:

代码语言:javascript
运行
复制
    @Test
    void testDoNotAllowDeletingUsersWithRootRole() {
        auth0ManagementAPIService.deleteUser("auth0|REDACTED");
        assertTrue(true);
    }

我试图使用@WithMockUser运行此测试,但这会导致@PreAuthorize表达式无法计算:

java.lang.IllegalArgumentException: Failed to evaluate expression 'authentication.principal.claims[@environment.getProperty('jwt.organizationIdClaim')] == @environment.getProperty('current.organizationId')'

是否有任何方法将这些自定义声明数据添加到这些单元测试的SecurityContext中,以便@PreAuthorize表达式能够成功地进行计算?

通过使用SecurityMockMvcRequestPostProcessors.jwt(),我们绕过了web层中的JWT需求,也许我们可以为@Service类方法使用类似的东西?

我们使用的JWT的一个示例有效载荷如下:

代码语言:javascript
运行
复制
{
  "https://dev.api.REDACTED.com/roles": [
    "RESEARCHER",
    "ADMIN",
    "ROOT"
  ],
  "https://dev.api.REDACTED.com/organizationId": "REDACTED",
  "https://dev.api.REDACTED.com/userId": "REDACTED",
  "iss": "https://dev.login.REDACTED.com/",
  "sub": "auth0|REDACTED",
  "aud": [
    "https://dev.api.REDACTED.com",
    "https://REDACTED.us.auth0.com/userinfo"
  ],
  "iat": 1660626647,
  "exp": 1660713047,
  "azp": "REDACTED",
  "scope": "openid profile email offline_access",
  "permissions": [
    ...
  ]
}

在这里,https://dev.api.REDACTED.com/roleshttps://dev.api.REDACTED.com/organizationIdhttps://dev.api.REDACTED.com/userId是自定义声明,我们需要在@PreAuthorize条件下验证它们的值。

主体对象为Jwt类型,使用自定义转换器生成如下:

代码语言:javascript
运行
复制
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;

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

public class CustomJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {

    private final String rolesClaimName;

    public CustomJwtAuthenticationConverter(String rolesClaimName) {
        this.rolesClaimName = rolesClaimName;
    }

    @Override
    public AbstractAuthenticationToken convert(Jwt jwt) {
        // Get roles claim from the JWT token and add it to a granted authorities list.
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        List<String> rolesList = jwt.getClaimAsStringList(rolesClaimName);
        rolesList.forEach(r -> grantedAuthorities.add(new SimpleGrantedAuthority(r)));
        return new JwtAuthenticationToken(jwt, grantedAuthorities, jwt.getSubject());
    }
}

然后将其注入安全配置中的安全筛选链中:

代码语言:javascript
运行
复制
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(customJwtGrantedAuthoritiesConverter());
        http
                .cors().and()
                .authorizeRequests(
                        authorize -> authorize
                                .antMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
                                .antMatchers("/actuator", "/actuator/health").permitAll()
                                .antMatchers(HttpMethod.DELETE, "/users/**").hasAuthority(ROOT_ROLE)
                                .antMatchers("/users/**").hasAnyAuthority(ADMIN_ROLE, ROOT_ROLE)
                                .anyRequest().authenticated());
        return http.build();
    }

    @Bean
    public Converter<Jwt, AbstractAuthenticationToken> customJwtGrantedAuthoritiesConverter() {
        return new CustomJwtAuthenticationConverter(rolesClaimName);
    }
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-09-07 02:38:03

我有你想要的东西:https://github.com/ch4mpy/spring-addons

样本这里

代码语言:javascript
运行
复制
    @Test
    @WithMockJwtAuth(authorities = { "NICE", "AUTHOR" }, claims = @OpenIdClaims(preferredUsername = "Tonton Pirate"))
    void whenGrantedWithNiceRoleThenCanGreet() throws Exception {
        final var actual = mySecuredService.returnSomething();
        //test return falue
    }

@OpenIdClaims允许您配置标准的OpenID索赔(但没有一个是强制性的)和任何您喜欢的私有索赔。

与请求后处理器相比,测试注释的优点是它不需要MockMvc (当您想要单元测试安全的@Component而不是@Controller,比如@Service@Repository时)。不幸的是,spring安全小组当时对它不感兴趣,我贡献了OAuth2 MockMvc后处理器(和WebTestClient变异器)。启动上面链接的库的原因。

PS

您可能会在其他教程中找到有用的想法。resource-server_with_oauthentication可以为您节省相当多的配置代码,resource-server_with_specialized_oauthentication可以帮助您提高安全性表达式的可读性。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/73626562

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档