我尝试制作如下所示的一些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配置有一些问题?请给我您的答复。
答案 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。