シンプルな Spring Security WebFlux アプリケーションを実装したいと考えています。
次のようなJSONメッセージを使用したい
{
'username': 'admin',
'password': 'adminPassword'
}
in body (/signin への POST リクエスト) でアプリにサインインします。
私は何をしましたか?
この構成を作成しました
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity(proxyTargetClass = true)
public class WebFluxSecurityConfig {
@Autowired
private ReactiveUserDetailsService userDetailsService;
@Autowired
private ObjectMapper mapper;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(11);
}
@Bean
public ServerSecurityContextRepository securityContextRepository() {
WebSessionServerSecurityContextRepository securityContextRepository =
new WebSessionServerSecurityContextRepository();
securityContextRepository.setSpringSecurityContextAttrName("securityContext");
return securityContextRepository;
}
@Bean
public ReactiveAuthenticationManager authenticationManager() {
UserDetailsRepositoryReactiveAuthenticationManager authenticationManager =
new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService);
authenticationManager.setPasswordEncoder(passwordEncoder());
return authenticationManager;
}
@Bean
public AuthenticationWebFilter authenticationWebFilter() {
AuthenticationWebFilter filter = new AuthenticationWebFilter(authenticationManager());
filter.setSecurityContextRepository(securityContextRepository());
filter.setAuthenticationConverter(jsonBodyAuthenticationConverter());
filter.setRequiresAuthenticationMatcher(
ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/signin")
);
return filter;
}
@Bean
public Function<ServerWebExchange, Mono<Authentication>> jsonBodyAuthenticationConverter() {
return exchange -> {
return exchange.getRequest().getBody()
.cache()
.next()
.flatMap(body -> {
byte[] bodyBytes = new byte[body.capacity()];
body.read(bodyBytes);
String bodyString = new String(bodyBytes);
body.readPosition(0);
body.writePosition(0);
body.write(bodyBytes);
try {
UserController.SignInForm signInForm = mapper.readValue(bodyString, UserController.SignInForm.class);
return Mono.just(
new UsernamePasswordAuthenticationToken(
signInForm.getUsername(),
signInForm.getPassword()
)
);
} catch (IOException e) {
return Mono.error(new LangDopeException("Error while parsing credentials"));
}
});
};
}
@Bean
public SecurityWebFilterChain securityWebFiltersOrder(ServerHttpSecurity httpSecurity,
ReactiveAuthenticationManager authenticationManager) {
return httpSecurity
.csrf().disable()
.httpBasic().disable()
.logout().disable()
.formLogin().disable()
.securityContextRepository(securityContextRepository())
.authenticationManager(authenticationManager)
.authorizeExchange()
.anyExchange().permitAll()
.and()
.addFilterAt(authenticationWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
.build();
}
}
しかし、私は jsonBodyAuthenticationConverter() を使用し、着信リクエストの本文を読み取ります。Body は 1 回しか読み取れないため、エラーが発生しています
java.lang.IllegalStateException: Only one connection receive subscriber allowed.
実際には機能していますが、例外があります (セッションは Cookie に設定されています)。このエラーなしで再作成するにはどうすればよいですか?
今、私は次のようなものだけを作成しました:
@PostMapping("/signin")
public Mono<Void> signIn(@RequestBody SignInForm signInForm, ServerWebExchange webExchange) {
return Mono.just(signInForm)
.flatMap(form -> {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
form.getUsername(),
form.getPassword()
);
return authenticationManager
.authenticate(token)
.doOnError(err -> {
System.out.println(err.getMessage());
})
.flatMap(authentication -> {
SecurityContextImpl securityContext = new SecurityContextImpl(authentication);
return securityContextRepository
.save(webExchange, securityContext)
.subscriberContext(ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext)));
});
});
}
そして構成から削除されAuthenticationWebFilter
ました。