Spring boot Spring Boot Webflux安全性-编写测试时读取服务类中的主体

Spring boot Spring Boot Webflux安全性-编写测试时读取服务类中的主体,spring-boot,spring-security,spring-security-oauth2,spring-webflux,Spring Boot,Spring Security,Spring Security Oauth2,Spring Webflux,我对Spring生态系统和Webflux都很陌生。有两件事我正试图弄清楚,但找不到任何细节 我的设置: 我正在使用WebFlux(不使用控制器,而是使用处理函数)编写SpringBoot2RESTAPI。身份验证服务器是一个独立的服务,它发布JWT令牌,这些令牌作为身份验证头附加到每个请求。下面是一个简单的请求方法示例: public Mono<ServerResponse> all(ServerRequest serverRequest) { return prin

我对Spring生态系统和Webflux都很陌生。有两件事我正试图弄清楚,但找不到任何细节

我的设置:

我正在使用WebFlux(不使用控制器,而是使用处理函数)编写SpringBoot2RESTAPI。身份验证服务器是一个独立的服务,它发布JWT令牌,这些令牌作为身份验证头附加到每个请求。下面是一个简单的请求方法示例:

public Mono<ServerResponse> all(ServerRequest serverRequest) {
        return principal(serverRequest).flatMap(principal ->
                ReactiveResponses.listResponse(this.projectService.all(principal)));
    }

在使用Webflux时,应该使用
ReactiveSecurityContextHolder
检索主体,如下所示:
Object principal=ReactiveSecurityContextHolder.getContext().getAuthentication().getPrincipal()

正如您所看到的,使用非反应式将返回null


此答案中有更多与主题相关的信息-

当您使用Webflux时,您应该使用
ReactiveSecurityContextHolder
检索主体,如下所示:
对象主体=ReactiveSecurityContextHolder.getContext().getAuthentication().getPrincipal()

正如您所看到的,使用非反应式将返回null


这个答案中有更多与主题相关的信息-

根据另一个答案,我用以下方法解决了这个问题

我添加了以下方法来从声明中读取id,该id通常位于JWT令牌中

    public static Mono<String> currentUserId() {
        return jwt().map(jwt -> jwt.getClaimAsString(USER_ID_CLAIM_NAME));
    }


    public static Mono<Jwt> jwt() {
        return ReactiveSecurityContextHolder.getContext()
                .map(context -> context.getAuthentication().getPrincipal())
                .cast(Jwt.class);
    }
然后工厂:

String token = "....ANY_JWT_TOKEN_GOES_HERE";

    @Override
    public SecurityContext createSecurityContext(WithMockToken tokenAnnotation) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        HashMap<String, Object> headers = new HashMap<>();
        headers.put("kid", "SOME_ID");
        headers.put("typ", "JWT");
        headers.put("alg", "RS256");
        HashMap<String, Object> claims = new HashMap<>();
        claims.put("sub", tokenAnnotation.sub());
        claims.put("aud", new ArrayList<>() {{
            add("SOME_ID_HERE");
        }});
        claims.put("updated_at", "2019-06-24T12:16:17.384Z");
        claims.put("nickname", tokenAnnotation.email().substring(0, tokenAnnotation.email().indexOf("@")));
        claims.put("name", tokenAnnotation.name());
        claims.put("exp", new Date());
        claims.put("iat", new Date());
        claims.put("email", tokenAnnotation.email());
        Jwt jwt = new Jwt(token, Instant.now(), Instant.now().plus(1, ChronoUnit.HOURS), headers,
                claims);
        JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(jwt, AuthorityUtils.NO_AUTHORITIES); // Authorities are needed to pass authentication in the Integration tests
        context.setAuthentication(jwtAuthenticationToken);


        return context;
    }

String-token=“…任何JWT-token都在这里”;
@凌驾
public SecurityContext createSecurityContext(带MockToken标记注释){
SecurityContext上下文=SecurityContextHolder.createEmptyContext();
HashMap headers=新的HashMap();
标题。放置(“孩子”,“一些ID”);
标题。put(“典型”、“JWT”);
标题。放置(“alg”、“RS256”);
HashMap声明=新的HashMap();
claims.put(“sub”,tokenAnnotation.sub());
索赔。出售(“澳元”,新ArrayList(){{
添加(“此处的某些ID”);
}});
权利要求书(“更新日期”,“2019-06-24T12:16:17.384Z”);
claims.put(“昵称”,tokenAnnotation.email().substring(0,tokenAnnotation.email().indexOf(@));
claims.put(“name”,tokenAnnotation.name());
索赔。卖出(“exp”,新日期());
索赔。出售(“iat”,新日期());
claims.put(“email”,tokenAnnotation.email());
Jwt Jwt=新的Jwt(令牌,Instant.now(),Instant.now()。加上(1,ChronoUnit.HOURS),头,
索赔);
JwtAuthenticationToken JwtAuthenticationToken=新的JwtAuthenticationToken(jwt,AuthorityUtils.NO_AUTHORITIES);//需要权限才能通过集成测试中的身份验证
setAuthentication(jwtAuthenticationToken);
返回上下文;
}
然后,一个简单的测试将如下所示:

    @Test
    @WithMockToken(sub = "uuid2")
    public void delete_whenNotOwner() {
        Mono<Void> deleted = this.projectService.create(projectDTO)
                .flatMap(saved -> this.projectService.delete(saved.getId()));

        StepVerifier
                .create(deleted)
                .verifyError(ProjectDeleteNotAllowedException.class);
    }

@测试
@WithMockToken(sub=“uuid2”)
public void delete_whenNotOwner(){
Mono deleted=this.projectService.create(projectDTO)
.flatMap(已保存->此.projectService.delete(已保存.getId());
步进验证器
.创建(已删除)
.verifyError(ProjectDeleteNotAllowedException.class);
}

根据另一个答案,我用以下方法解决了这个问题

我添加了以下方法来从声明中读取id,该id通常位于JWT令牌中

    public static Mono<String> currentUserId() {
        return jwt().map(jwt -> jwt.getClaimAsString(USER_ID_CLAIM_NAME));
    }


    public static Mono<Jwt> jwt() {
        return ReactiveSecurityContextHolder.getContext()
                .map(context -> context.getAuthentication().getPrincipal())
                .cast(Jwt.class);
    }
然后工厂:

String token = "....ANY_JWT_TOKEN_GOES_HERE";

    @Override
    public SecurityContext createSecurityContext(WithMockToken tokenAnnotation) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        HashMap<String, Object> headers = new HashMap<>();
        headers.put("kid", "SOME_ID");
        headers.put("typ", "JWT");
        headers.put("alg", "RS256");
        HashMap<String, Object> claims = new HashMap<>();
        claims.put("sub", tokenAnnotation.sub());
        claims.put("aud", new ArrayList<>() {{
            add("SOME_ID_HERE");
        }});
        claims.put("updated_at", "2019-06-24T12:16:17.384Z");
        claims.put("nickname", tokenAnnotation.email().substring(0, tokenAnnotation.email().indexOf("@")));
        claims.put("name", tokenAnnotation.name());
        claims.put("exp", new Date());
        claims.put("iat", new Date());
        claims.put("email", tokenAnnotation.email());
        Jwt jwt = new Jwt(token, Instant.now(), Instant.now().plus(1, ChronoUnit.HOURS), headers,
                claims);
        JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(jwt, AuthorityUtils.NO_AUTHORITIES); // Authorities are needed to pass authentication in the Integration tests
        context.setAuthentication(jwtAuthenticationToken);


        return context;
    }

String-token=“…任何JWT-token都在这里”;
@凌驾
public SecurityContext createSecurityContext(带MockToken标记注释){
SecurityContext上下文=SecurityContextHolder.createEmptyContext();
HashMap headers=新的HashMap();
标题。放置(“孩子”,“一些ID”);
标题。put(“典型”、“JWT”);
标题。放置(“alg”、“RS256”);
HashMap声明=新的HashMap();
claims.put(“sub”,tokenAnnotation.sub());
索赔。出售(“澳元”,新ArrayList(){{
添加(“此处的某些ID”);
}});
权利要求书(“更新日期”,“2019-06-24T12:16:17.384Z”);
claims.put(“昵称”,tokenAnnotation.email().substring(0,tokenAnnotation.email().indexOf(@));
claims.put(“name”,tokenAnnotation.name());
索赔。卖出(“exp”,新日期());
索赔。出售(“iat”,新日期());
claims.put(“email”,tokenAnnotation.email());
Jwt Jwt=新的Jwt(令牌,Instant.now(),Instant.now()。加上(1,ChronoUnit.HOURS),头,
索赔);
JwtAuthenticationToken JwtAuthenticationToken=新的JwtAuthenticationToken(jwt,AuthorityUtils.NO_AUTHORITIES);//需要权限才能通过集成测试中的身份验证
setAuthentication(jwtAuthenticationToken);
返回上下文;
}
然后,一个简单的测试将如下所示:

    @Test
    @WithMockToken(sub = "uuid2")
    public void delete_whenNotOwner() {
        Mono<Void> deleted = this.projectService.create(projectDTO)
                .flatMap(saved -> this.projectService.delete(saved.getId()));

        StepVerifier
                .create(deleted)
                .verifyError(ProjectDeleteNotAllowedException.class);
    }

@测试
@WithMockToken(sub=“uuid2”)
public void delete_whenNotOwner(){
Mono deleted=this.projectService.create(projectDTO)
.flatMap(已保存->此.projectService.delete(已保存.getId());
步进验证器
.创建(已删除)
.verifyError(ProjectDeleteNotAllowedException.class);
}

非常感谢。我不知道我怎么会错过这个。它确实让我找到了文档,并帮助我定制了模拟令牌。我将在下面发布我的最终解决方案作为参考。非常感谢。我不知道我怎么会错过这个。它确实让我找到了文档,并帮助我定制了模拟令牌。我将在下面发布我的最终解决方案作为参考。