当前位置:   article > 正文

与Security和Redis Session整合解析_enablerediswebsession

enablerediswebsession

要在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>
  • 1
  • 2
  • 3
  • 4

spring-session-data-redis:其本身同时支持Reactive和非Reactive,具体如下:

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4

安全配置

要使用支持Reactive的Security,其核心是配置SecurityWebFilterChainSpring Security Webflux

  1. 在启动配置类上使用相关注解: @EnableRedisWebSession, @EnableWebFluxSecurity, @EnableReactiveMethodSecurity;
@Configuration
// @EnableRedisWebSession(redisNamespace = "bff:session",saveMode = SaveMode.ON_SET_ATTRIBUTE)
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SessionWebFluxSecurityExtContextConfig {
    // ......
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

注意这里通常不应该直接使用@EnableRedisWebSession,如果显示的使用该注解会造成spirng.session下的部分外部化配置失效;

  1. 根据Path的Pattern配置相应的SecurityWebFilterChain,如下例:
@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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  1. 首先通过.securityMatcher(orServerWebExchangeMatcher)配置被该SecurityWebFilterChain管控的条件,通常是符合一定Pattern的uri;
  2. .httpBasic().disable().formLogin()确定登录模式;该例表示禁用httpBasic模式,只使用formLogin,也可以同时支持多种模式;
  3. 对于formLogin模式(注意该模式限制了content-type必须为application/x-www-form-urlencoded)指定相应的配置,主要包括:
    1. .loginPage("/authenticating")登录页所对应的uri,这里指的是View,并不是真正的login服务的uri;
    2. .requiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST,"/authenticating/form"))指定真正的login服务的uri;其内部通过AuthenticationWebFilter来截获该请求,并进行一系列的登录处理,所以这里其实是配置的该Filter的Matcher,根据代码则该uri必须使用Post访问,请求uri必须为“/authenticating/form”且content-type必须为application/x-www-form-urlencoded;
    3. 通常我们都需要自定义登录成功,登录失败和登出等返回结果;那么就需要相应的自定义处理;如上例:
      1. authenticationSuccessHandler
      2. authenticationFailureHandler
      3. logoutSuccessHandler
  4. .authorizeExchange()配置授权规则,该规则会被AuthorizationWebFilter使用;如上例:
    1. .pathMatchers(excludedAuthPages).permitAll()标识需要排除授权检查控制的匹配模式,这里使用简单的Uri模式,通常指定登录,登出相关的Uri;
    2. .anyExchange().hasRole("CUSTOM")其他的任意请求都需要在Authentication中包含"ROLE_CUSTOM";

登录逻辑

结合上面的配置,AuthenticationWebFilter负责完成登录的逻辑:

  • 一个请求进入,如果它不匹配setRequiresAuthenticationMatcher(ServerWebExchangeMatcher) ,则此过滤器不执行任何操作,并且WebFilterChain继续。如果它真的匹配那么…
  • 尝试将ServerWebExchange转换为Authentication。如果结果为空,则过滤器不再执行任何工作,并且继续执行WebFilterChain。如果它确实创建了Authentication…
  • ReactiveAuthenticationManager在AuthenticationWebFilter(ReactiveAuthenticationManager) 中用于进行身份验证。
  • 在AuthenticationWebFilter(ReactiveAuthenticationManagerResolver) 中指定的ReactiveAuthenticationManagerResolver用于从上下文解析相关的authentication manager以执行身份验证。
  • 如果身份验证成功,则调用ServerAuthenticationSuccessHandler ,并在ReactiveSecurityContextHolder上设置Authentication,否则调用ServerAuthenticationFailureHandler。

接下来我们看一下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();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. ReactiveAuthenticationManagerResolver:其实就是ReactiveAuthenticationManager的容器,其内定义的函数方法resolve,用于获取ReactiveAuthenticationManager;所以关键的逻辑在ReactiveAuthenticationManager内,如下,而其内的ReactiveUserDetailsService真正用于登录的用户获取逻辑:
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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  1. ServerAuthenticationSuccessHandler
  2. ServerAuthenticationFailureHandler
  3. ServerAuthenticationConverter:用于从ServerWebExchange中获取AbstractAuthenticationToken,其本身就是Authentication和CredentialsContainer这两个接口的实现;其默认配置为ServerHttpBasicAuthenticationConverter,该类返回的实现类是UsernamePasswordAuthenticationToken,即通过用户名和密码组合成的AbstractAuthenticationToken;而formLogin模式下默认是ServerFormLoginAuthenticationConverter,在FormLoginSpec#configure方法中设置;
    1. ServerFormLoginAuthenticationConverter限定了从FormData中获取"username"和"password"这两个参数,然后实例化为UsernamePasswordAuthenticationToken,注意:在Security体系中用principal和credentials,这两个Object属性抽象"username"和"password",因此理论上可以是任何数据,对于一些复杂的登录应用,便于自定义扩展;
  4. ServerSecurityContextRepository:其有两个实现,分别是WebSessionServerSecurityContextRepository和NoOpServerSecurityContextRepository,默认配置的是NoOpServerSecurityContextRepository,其实什么也不做;而在SecurityWebFilterChain#build方法中,如果设置了formLogin,则改变默认配置,即使用WebSessionServerSecurityContextRepository。
  5. ServerWebExchangeMatcher requiresAuthenticationMatcher:用于设置进行是否通过“ServerAuthenticationConverter创建Authentication”的匹配器。如果ServerAuthenticationConverter返回空结果,则不会执行后续的身份验证逻辑。默认为任何进入该Chain-Filter请求,代码如下图:

image.png

  1. 结合上面SecurityWebFilterChain的配置,指定了对外暴露登录服务的Uri,那么当请求“/authenticating/form”时,此处的匹配逻辑满足条件,并进一步执行convert操作以创建Authentication对象,然后执行authenticate逻辑,已完成登录;
  2. authenticate,认证逻辑也在该Filter中,如下图,其主要由authenticationManagerResolver获取当前authenticationManager,并根据上面的converter转换的Authentication执行authenticationManager的认证逻辑;此处的authenticationManager其实通常就是自定义的ReactiveAuthenticationManager:

image.png

会话管理

当上面的登录逻辑执行成功后,则会执行onAuthenticationSuccess,其内将登录后的Authentication对象存入当前线程的SecurityContext对象中,并且调用securityContextRepository#save,完成持久化,通常是(WebSessionServerSecurityContextRepository)保存到WebSession中,然后再由WebSession本身的实现决定如何持久化,默认情况通常是内存(InMemoryWebSession),整合spring-session-data-redis后则是由SpringSessionWebSessionStore中的ReactiveSessionRepository属性决定的;
image.png
最终上图的save方法,真正执行的是org.springframework.session.web.server.session.SpringSessionWebSessionStore.SpringSessionWebSession#save:
image.png
总的来说会话的管理主要是由下面两个接口完成的:

  • ReactiveSessionRepository:该接口主要负责Session的CRUD,当前依赖Redis环境下的实现为ReactiveRedisSessionRepository;
  • WebSessionManager:该接口只负责获取Session,其只有一个默认实现DefaultWebSessionManager,另外需要注意其内有一个WebSessionIdResolver接口属性,该接口用于获取WebSessionId,有两个实现:
    • HeaderWebSessionIdResolver:从Http Header 中获取“SESSION”的值作为SessionId;
    • CookieWebSessionIdResolver:从Cookie 中获取“SESSION”的值作为SessionId;

WebSessionIdResolver的Bean由WebSessionIdResolverAutoConfiguration自动装配,默认为CookieWebSessionIdResolver;
另外由于引入了spring-session-data-redis依赖,故RedisReactiveSessionConfiguration被激活,用于完成自动化配置,以上两个接口对应的Bean都是由该配置类注入的;以上接口及实现的关系图如下:
image.png

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/68888
推荐阅读
相关标签
  

闽ICP备14008679号