一、switchIfEmpty
在讨论登录流程之前,先看下Spring Web Flux中的switchIfEmpty
用法。
@Test
public void test1() {
Mono.just("test1")
.flatMap(val -> {
return Mono.just("test2");
})
.switchIfEmpty(method1())
.subscribe(s -> System.out.println(s));
}
private static Mono<String> method1() {
System.out.println("test3");
return Mono.empty();
}
输出:
test3
test2
其中subscribe(System.out::println)打印的是map的结果,而不是switchIfEmpty的返回结果。
继续看:
private static Mono<String> method1() {
System.out.println("test3");
return Mono.empty();
}
@Test
public void test2() {
Mono.just("test1")
.flatMap(val -> {
return Mono.empty();
})
.switchIfEmpty(method1())
.subscribe(s -> System.out.println(s));
}
输出:
test3
看到subscribe(s -> System.out.println(s))没有执行。从上面可知switchIfEmpty
的上一个操作返回空,则switchIfEmpty
的下一个操作不会执行。但是无论如何switchIfEmpty
对应的操作都会执行。例如上面的method1()都会执行。
二、认证流程
认证由AuthenticationWebFilter
处理。AuthenticationWebFilter相当于SpringSecurity的UsernamePasswordAuthenticationFilter。
AuthenticationWebFilter实现了WebFilter接口:
public interface WebFilter {
Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain);
}
AuthenticationWebFilter#filter
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return this.requiresAuthenticationMatcher.matches(exchange).filter((matchResult) -> matchResult.isMatch())
.flatMap((matchResult) -> this.authenticationConverter.convert(exchange))
.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
.flatMap((token) -> authenticate(exchange, chain, token))
.onErrorResume(AuthenticationException.class, (ex) -> this.authenticationFailureHandler
.onAuthenticationFailure(new WebFilterExchange(exchange, chain), ex));
}
ServerWebExchange相当于Servlet中的HttpServletRequest和HttpServletResponse。requiresAuthenticationMatcher
在ServerHttpSecurity中是ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, loginPage)
,匹配post方式的登录接口
。authenticationConverter是org.springframework.security.web.server.authentication.ServerAuthenticationConverter
,用户将表单信息转换成Authentication
。下一个操作是switchIfEmpty
。如果Authentication
为空就不会执行下一个操作authenticate
(进行用户认证)。如果出现AuthenticationException
异常,authenticationFailureHandler
处理认证失败。
authenticationConverter默认是ServerFormLoginAuthenticationConverter:
@SuppressWarnings("deprecation")
public class ServerFormLoginAuthenticationConverter
extends org.springframework.security.web.server.ServerFormLoginAuthenticationConverter
implements ServerAuthenticationConverter {
@Override
public Mono<Authentication> convert(ServerWebExchange exchange) {
return apply(exchange);
}
}
如果要自定义解析参数,可以实现org.springframework.security.web.server.authentication.ServerAuthenticationConverter
接口。 convert调用了org.springframework.security.web.server.ServerFormLoginAuthenticationConverter
的apply
:
private String usernameParameter = "username";
private String passwordParameter = "password";
@Override
@Deprecated
public Mono<Authentication> apply(ServerWebExchange exchange) {
return exchange.getFormData().map((data) -> createAuthentication(data));
}
private UsernamePasswordAuthenticationToken createAuthentication(MultiValueMap<String, String> data) {
String username = data.getFirst(this.usernameParameter);
String password = data.getFirst(this.passwordParameter);
return UsernamePasswordAuthenticationToken.unauthenticated(username, password);
}
从请求中获取用户名和密码组装成UsernamePasswordAuthenticationToken。
再来看下AuthenticationWebFilter的authenticate:
private Mono<Void> authenticate(ServerWebExchange exchange, WebFilterChain chain, Authentication token) {
return this.authenticationManagerResolver.resolve(exchange)
.flatMap((authenticationManager) -> authenticationManager.authenticate(token))
.switchIfEmpty(Mono.defer(
() -> Mono.error(new IllegalStateException("No provider found for " + token.getClass()))))
.flatMap((authentication) -> onAuthenticationSuccess(authentication,
new WebFilterExchange(exchange, chain)))
.doOnError(AuthenticationException.class,
(ex) -> logger.debug(LogMessage.format("Authentication failed: %s", ex.getMessage())));
}
authenticationManagerResolver可以看作是认证管理器。调用authenticationManager.authenticate进行用户认证。authenticationManager默认是UserDetailsRepositoryReactiveAuthenticationManager
。如果认证成功就调用onAuthenticationSuccess处理认证成功后的逻辑。
接下来看UserDetailsRepositoryReactiveAuthenticationManager,UserDetailsRepositoryReactiveAuthenticationManager继承了AbstractUserDetailsReactiveAuthenticationManager:
public Mono<Authentication> authenticate(Authentication authentication) {
String username = authentication.getName();
String presentedPassword = (String) authentication.getCredentials();
// @formatter:off
return retrieveUser(username)
.doOnNext(this.preAuthenticationChecks::check)
.publishOn(this.scheduler)
.filter((userDetails) -> this.passwordEncoder.matches(presentedPassword, userDetails.getPassword()))
.switchIfEmpty(Mono.defer(() -> Mono.error(new BadCredentialsException("Invalid Credentials"))))
.flatMap((userDetails) -> upgradeEncodingIfNecessary(userDetails, presentedPassword))
.doOnNext(this.postAuthenticationChecks::check)
.map(this::createUsernamePasswordAuthenticationToken);
// @formatter:on
}
retrieveUser通过用户名查询出UserDetails
。接下来的操作和SpringSecurity中的类似。都是检查用户状态是否锁定,用户状态是否过期,密码是否过期,是否启用账户,以及进行密码对比等。
UserDetailsRepositoryReactiveAuthenticationManager#retrieveUser
protected Mono<UserDetails> retrieveUser(String username) {
return this.userDetailsService.findByUsername(username);
}
调用ReactiveUserDetailsService
通过用户名查询出UserDetails。如果要从数据库查询出用户。可以实现ReactiveUserDetailsService。
再看下AuthenticationWebFilter的authenticationFailureHandler,默认实现是new RedirectServerAuthenticationFailureHandler(loginPage + "?error")。重定向到登录页面并携带错误提示信息。
最后看AuthenticationWebFilter的onAuthenticationSuccess:
protected Mono<Void> onAuthenticationSuccess(Authentication authentication, WebFilterExchange webFilterExchange) {
ServerWebExchange exchange = webFilterExchange.getExchange();
SecurityContextImpl securityContext = new SecurityContextImpl();
securityContext.setAuthentication(authentication);
return this.securityContextRepository.save(exchange, securityContext)
.then(this.authenticationSuccessHandler.onAuthenticationSuccess(webFilterExchange, authentication))
.subscriberContext(ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext)));
}
securityContextRepository默认实现是WebSessionServerSecurityContextRepository。将Authentication保存到WebSession
中。authenticationSuccessHandler实现是new RedirectServerAuthenticationSuccessHandler("/")。重定向到根路径。最后将securityContext设置到ReactiveSecurityContextHolder。