如何在webflux安全的webclient的HTTP.POST和HTTP.PUT上添加HTTP基本身份验证?

时间:2019-07-08 04:06:44

标签: spring-security spring-webflux

我尝试制作如下所示的一些webflux安全代码,

@Configuration
@EnableWebFluxSecurity
public class BlogWebFluxSecurityConfig {

    @Bean
    public MapReactiveUserDetailsService userDetailsService() {

        UserDetails userWebFlux = User.withUsername("joseph").password(passwordEncoder().encode("password")).roles("USER").build();
        return new MapReactiveUserDetailsService(userWebFlux);
    }

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http
        .authorizeExchange()
        .pathMatchers("/route/user/all", "/route/post/all").permitAll()
        .pathMatchers(HttpMethod.GET, "/route/user/**", "/route/post/**").hasRole("USER")
        .anyExchange().authenticated()
        .and()
        .httpBasic();

        return http.build();
    } 

    @Bean 
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

并使用WebClient类制作webflux客户端代码。我使用http基本身份验证。在HTTP GET方法中,http基本身份验证成功运行。这些是webclient代码,效果很好。

client.get().uri("/route/post/id/{id}", 10).accept(MediaType.APPLICATION_JSON)
                .header(HttpHeaders.AUTHORIZATION, basicAuthHeader).exchange()
                .flatMap(response -> response.bodyToMono(Post.class))
                .subscribe(p -> System.out.println("GET by Id : " + p.getUser().getUsername() + ":" + p.getTitle()));

但是在HTTP.POST和HTTP.PUT方法上,http基本身份验证不起作用,甚至会引发异常。

public class WebFluxBlogClient {

    private WebClient client = WebClient.create("http://localhost:8080");  

    String basicAuthHeader = "basic " + Base64Utils.encodeToString(("joseph" + ":" + "password").getBytes());

    public void functionOnSecurityDocument() {

        Map<String, String> mapUser = new HashMap<String, String>();
        mapUser.put("username", "joseph");
        mapUser.put("password", "password");

        client.post().uri("/route/user/login").accept(MediaType.APPLICATION_JSON).body(Mono.just(mapUser), Map.class)
                .header(HttpHeaders.AUTHORIZATION, basicAuthHeader).exchange()
                .map(ClientResponse::statusCode).subscribe(response -> System.out.println("Login : " + response.getReasonPhrase()));

        User user = new User("0005", 4L, "jane", "password", "aaa@bbb.com", "누나", "USER");

        client.post().uri("/route/user/create").accept(MediaType.APPLICATION_JSON).body(Mono.just(user), User.class)
                .header(HttpHeaders.AUTHORIZATION, basicAuthHeader).exchange() 
                .map(ClientResponse::statusCode).subscribe(response -> System.out.println("User Creation: " + response.getReasonPhrase()));


        client.put().uri("/route/post/{id}/{content}", 7, "test sentences....")
                .accept(MediaType.APPLICATION_JSON).header(HttpHeaders.AUTHORIZATION, basicAuthHeader).exchange().flatMap(response -> response.bodyToMono(Post.class))
                .subscribe(p -> System.out.println("EDIT by Id : " + p.getUser().getUsername() + ":" + p.getBody()));
    }
}

响应值为

Login : Forbidden
User Creation: Forbidden
2019-07-08 12:46:16.443  WARN 4800 --- [ctor-http-nio-3] io.netty.util.ReferenceCountUtil         : Failed to release a message: DefaultLastHttpContent(data: PooledSlicedByteBuf(freed), decoderResult: success)

io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1
    at io.netty.util.internal.ReferenceCountUpdater.toLiveRealRefCnt(ReferenceCountUpdater.java:74) ~[netty-common-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.util.internal.ReferenceCountUpdater.release(ReferenceCountUpdater.java:138) ~[netty-common-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.buffer.AbstractReferenceCountedByteBuf.release(AbstractReferenceCountedByteBuf.java:100) ~[netty-buffer-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.handler.codec.http.DefaultHttpContent.release(DefaultHttpContent.java:94) ~[netty-codec-http-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.util.ReferenceCountUtil.release(ReferenceCountUtil.java:88) ~[netty-common-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.util.ReferenceCountUtil.safeRelease(ReferenceCountUtil.java:113) ~[netty-common-4.1.36.Final.jar:4.1.36.Final]
    at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:120) ~[reactor-netty-0.8.9.RELEASE.jar:0.8.9.RELEASE]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:102) ~[netty-codec-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:438) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:323) ~[netty-codec-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:297) ~[netty-codec-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:253) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1408) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:930) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:682) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:617) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:534) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:496) ~[netty-transport-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:906) ~[netty-common-4.1.36.Final.jar:4.1.36.Final]
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.36.Final.jar:4.1.36.Final]
    at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]

2019-07-08 12:46:16.443  WARN 4800 --- [ctor-http-nio-3] reactor.netty.channel.FluxReceive        : [id: 0xdfda1db5, L:0.0.0.0/0.0.0.0:54009] An exception has been observed post termination, use DEBUG level to see the full stack: reactor.core.Exceptions$ErrorCallbackNotImplemented: org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'text/plain' not supported for bodyType=com.aaa.blog.wf.model.Post

我不知道异常的含义。我想知道如何在webflux webclient的HTTP.POST和HTTP.PUT方法上应用http基本身份验证。任何答复将不胜感激。谢谢

==更新的部件==

我如下更改安全配置,但失败。

@Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http
        .authorizeExchange()
        .pathMatchers("/route/user/all", "/route/post/all").permitAll()
        .pathMatchers(HttpMethod.GET, "/route/user/id/**", "/route/user/username/**", "/route/user/email/**").hasRole("USER")
        .pathMatchers(HttpMethod.POST, "/route/user/login", "/route/user/create", "/route/post/create").hasRole("USER")
        .anyExchange().authenticated()
        .and()
        .httpBasic();

        return http.build();
    }

为便于参考,我附上了路由器类。

@Bean
public RouterFunction<ServerResponse> routesUser(UserHandler handler) {

        return RouterFunctions.route(RequestPredicates.GET("/route/user/all"), handler::findAll)
                    .andRoute(RequestPredicates.GET("/route/user/id/{id}"), handler::findById)
                    .andRoute(RequestPredicates.GET("/route/user/username/{username}"), handler::findByUsername)
                    .andRoute(RequestPredicates.GET("/route/user/email/{email}"), handler::findByEmail)
                    .andRoute(RequestPredicates.POST("/route/user/create"), handler::register)
                    .andRoute(RequestPredicates.POST("/route/user/login"), handler::authenticate);
    }

但是会引发如下相同的错误消息,

Login : Forbidden
User Creation: Forbidden

我的SecurityWebFilterChain配置有一些问题?请给我您的答复。

2 个答案:

答案 0 :(得分:2)

您明确将身份验证限制为仅GET请求。如果您在SecurityWebFilterChain配置中替换此行:

.pathMatchers(HttpMethod.GET, "/route/user/**", "/route/post/**").hasRole("USER")

使用除HTTP方法之外的更通用的方法,auth应该应用于所有HTTP方法。

.pathMatchers("/route/user/**", "/route/post/**").hasRole("USER")

答案 1 :(得分:0)

该应用程序可能会给您一个403,因为它在带有POST等副作用的请求中expecting a CSRF token。 Spring Security默认启用CSRF防御。

您需要configure your front-end才能在POST请求中发送CSRF令牌,或者您需要disable CSRF altogether