赞
踩
要在SCG中整合Session会话管理,需要基于SpringSecurity+SpringSession,而用于SCG是基于WebFlux的,因此Security和Session也需要选择相应Reactive版本的组件;
spring-boot-starter-security:其本身同时支持Reactive和非Reactive,具体如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
spring-session-data-redis:其本身同时支持Reactive和非Reactive,具体如下:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
要使用支持Reactive的Security,其核心是配置SecurityWebFilterChainSpring Security Webflux;
@EnableRedisWebSession, @EnableWebFluxSecurity, @EnableReactiveMethodSecurity
;@Configuration
// @EnableRedisWebSession(redisNamespace = "bff:session",saveMode = SaveMode.ON_SET_ATTRIBUTE)
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SessionWebFluxSecurityExtContextConfig {
// ......
}
注意这里通常不应该直接使用@EnableRedisWebSession,如果显示的使用该注解会造成spirng.session下的部分外部化配置失效;
@Bean(PRIVATE_API_WEB_HTTP_SECURITY) @Order(ActuatorSecurityExtContextConfig.ACTUATOR_SECURITY_ORDER-2) SecurityWebFilterChain privateApiWebHttpSecurity(ServerHttpSecurity http) { //从BffSecurityProperties遍历需要认证的URI并设置 List<ServerWebExchangeMatcher> matchers = Lists.newArrayList(); matchers.add(new PathPatternParserServerWebExchangeMatcher("/authenticating/**")); matchers.add(new PathPatternParserServerWebExchangeMatcher("/logout")); for (int i = 0; i < bffSecurityProperties.getAuthenticatingResources().length; i++) { //符合指定 Path Patterns 的 private api 需要登录 //matchers[i] = new PathPatternParserServerWebExchangeMatcher("/wechat-xcx-api/**"); matchers.add(new PathPatternParserServerWebExchangeMatcher(bffSecurityProperties.getAuthenticatingResources()[i])); } //按Or匹配 OrServerWebExchangeMatcher orServerWebExchangeMatcher = new OrServerWebExchangeMatcher(matchers); http //所有匹配到的Uri将应用该SecurityWebFilterChain .securityMatcher(orServerWebExchangeMatcher) .httpBasic().disable() .formLogin() .loginPage("/authenticating") .requiresAuthenticationMatcher( //注意该uri必须使用Post访问,且content-type必须为application/x-www-form-urlencoded ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST,"/authenticating/form") ) .authenticationSuccessHandler( new RedirectServerAuthenticationSuccessHandler("/authenticatingSucceed") ) .authenticationFailureHandler( new RedirectServerAuthenticationFailureHandler("/authenticatingFailed") ) //logoutHandler可存在多个,如果不指定自定义的,将只有SecurityContextServerLogoutHandler .and() .logout().logoutSuccessHandler(logoutSuccessHandler()) .and() .authorizeExchange() .pathMatchers(excludedAuthPages).permitAll() //requires the role "ROLE_CUSTOM";凡是登录成功的role都是“ROLE_CUSTOM” .anyExchange().hasRole("CUSTOM") ; return http.build(); }
.securityMatcher(orServerWebExchangeMatcher)
配置被该SecurityWebFilterChain管控的条件,通常是符合一定Pattern的uri;.httpBasic().disable().formLogin()
确定登录模式;该例表示禁用httpBasic模式,只使用formLogin,也可以同时支持多种模式;.loginPage("/authenticating")
登录页所对应的uri,这里指的是View,并不是真正的login服务的uri;.requiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST,"/authenticating/form"))
指定真正的login服务的uri;其内部通过AuthenticationWebFilter来截获该请求,并进行一系列的登录处理,所以这里其实是配置的该Filter的Matcher,根据代码则该uri必须使用Post访问,请求uri必须为“/authenticating/form”且content-type必须为application/x-www-form-urlencoded;authenticationSuccessHandler
authenticationFailureHandler
logoutSuccessHandler
.authorizeExchange()
配置授权规则,该规则会被AuthorizationWebFilter使用;如上例:
.pathMatchers(excludedAuthPages).permitAll()
标识需要排除授权检查控制的匹配模式,这里使用简单的Uri模式,通常指定登录,登出相关的Uri;.anyExchange().hasRole("CUSTOM")
其他的任意请求都需要在Authentication中包含"ROLE_CUSTOM";结合上面的配置,AuthenticationWebFilter负责完成登录的逻辑:
接下来我们看一下AuthenticationWebFilter的主要属性:
private final ReactiveAuthenticationManagerResolver<ServerWebExchange> authenticationManagerResolver;
private ServerAuthenticationSuccessHandler authenticationSuccessHandler = new WebFilterChainServerAuthenticationSuccessHandler();
private ServerAuthenticationConverter authenticationConverter = new ServerHttpBasicAuthenticationConverter();
private ServerAuthenticationFailureHandler authenticationFailureHandler = new ServerAuthenticationEntryPointFailureHandler(
new HttpBasicServerAuthenticationEntryPoint());
private ServerSecurityContextRepository securityContextRepository = NoOpServerSecurityContextRepository
.getInstance();
private ServerWebExchangeMatcher requiresAuthenticationMatcher = ServerWebExchangeMatchers.anyExchange();
public class BffUserAuthenticationManager extends AbstractUserDetailsReactiveAuthenticationManager {
private final BffUserDetailsServiceImpl manageDetailsService;
public BffUserAuthenticationManager(BffUserDetailsServiceImpl manageDetailsService) {
this.manageDetailsService = manageDetailsService;
}
@Override
protected Mono<UserDetails> retrieveUser(String username) {
return manageDetailsService.findByUsername(username);
}
}
当上面的登录逻辑执行成功后,则会执行onAuthenticationSuccess,其内将登录后的Authentication对象存入当前线程的SecurityContext对象中,并且调用securityContextRepository#save,完成持久化,通常是(WebSessionServerSecurityContextRepository)保存到WebSession中,然后再由WebSession本身的实现决定如何持久化,默认情况通常是内存(InMemoryWebSession),整合spring-session-data-redis后则是由SpringSessionWebSessionStore中的ReactiveSessionRepository属性决定的;
最终上图的save方法,真正执行的是org.springframework.session.web.server.session.SpringSessionWebSessionStore.SpringSessionWebSession#save:
总的来说会话的管理主要是由下面两个接口完成的:
WebSessionIdResolver的Bean由WebSessionIdResolverAutoConfiguration自动装配,默认为CookieWebSessionIdResolver;
另外由于引入了spring-session-data-redis依赖,故RedisReactiveSessionConfiguration被激活,用于完成自动化配置,以上两个接口对应的Bean都是由该配置类注入的;以上接口及实现的关系图如下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。