六、认证流程

发布时间 2023-06-07 22:47:06作者: shigp1

一、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.ServerFormLoginAuthenticationConverterapply:

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。