SpringSecurity は springBoot と redis を統合してログインキックを実現します

背景

私の記事「SpringSecurityはspringBootとredisトークンの動的URL権限検証を統合する」に基づいています。実装する機能は、ユーザーが 2 台のデバイスに同時にログインできないようにすることであり、
(1) 次回以降のログインで以前のログインを自動的に追い出す 2 つのアイデアがあります。
(2) すでにログイン済みの場合、後発者はログインできません。
プロジェクトの基礎は、redis によってすでに維持されているセッションであることに注意してください。

redisHttpSession を構成する

Spring セッションが Redis によって管理されるように設定します。
2.1 yml の http セッション設定を削除し、yml とアノテーションのいずれか 1 つだけを選択します (同時に設定すると、アノテーションの設定のみが有効になります)。なぜymlを使わないのかについては後述します。
ここに画像の説明を挿入します
2.2 アノテーション @EnableRedisHttpSession を webSecurityConfig に追加する
ここに画像の説明を挿入します

@EnableRedisHttpSession(redisNamespace = "spring:session:myframe", maxInactiveIntervalInSeconds = 1700
        , flushMode = FlushMode.ON_SAVE)

ログイン後、redis セッション名前空間がすでに指定されていることがわかりました。

ここに画像の説明を挿入します

Redis によって管理されている sessionRepository を取得します

ユーザーのログインを制限したい場合は、当然のことながら、システム内のすべてのユーザーのセッションを取得する必要があります。

2. springSession 公式 Web サイトのドキュメントを確認してください。springsession 公式 Web サイトにはドキュメントが提供されています https://docs.spring.io/spring-session/docs/2.2.2.RELEASE/reference/html5/#api-findbyindexnamesessionrepository

SessionRepository 実装では、FindByIndexNameSessionRepository の実装を選択することもできます

FindByIndexNameSessionRepository は、指定されたインデックス名とインデックス値を持つすべてのセッションを検索するメソッドを提供します

FindByIndexNameSessionRepository 実装は、特定のユーザーのすべてのセッションを検索するための便利なメソッドを提供します

/**
     * redis获取sessionRepository
     * RedisIndexedSessionRepository实现 FindByIndexNameSessionRepository接口
     */
    @Autowired
    //不加@Lazy这个会报什么循环引用...
    // Circular reference involving containing bean '.RedisHttpSessionConfiguration' 
    @Lazy   
    private FindByIndexNameSessionRepository<? extends Session> sessionRepository;

ここで注意すべき点の 1 つは、yml を通じて Redis セッションを構成すると、sessionRepository の下に赤い線が表示されることです。
ここに画像の説明を挿入します
動作には影響ありませんが、強迫性があるので、代わりに @EnableWebSecurity アノテーションを使用します (理由は知りたくないのですが…)。

sessionRepository を SpringSessionBackedSessionRegistry に注入する

これは Spring Security 用に Spring session によって提供されるセッション同時実行セッション レジストリの実装です。おそらく springSecurity を使用してログインを制限できるようになります。sessionRepository だけでは十分ではなく、いくつかのツールを自分で追加する必要があります。
webSecurityConfig が追加されました:

/**
     * 是spring session为Spring Security提供的,
     * 用于在集群环境下控制会话并发的会话注册表实现
     * @return
     */
    @Bean
    public SpringSessionBackedSessionRegistry sessionRegistry(){
    
    
        return new SpringSessionBackedSessionRegistry<>(sessionRepository);
    }

注:
https://blog.csdn.net/qq_34136709/article/details/106012825 この記事には、セッションの破棄を監視するには HttpSessionEventPublisher を追加する必要があると記載されていますが、これはおそらく Redis セッションを使用しているためですが、これは必要ありません。その後もエラーが報告されます。何が問題なのでしょうか? 忘れた。

セッションの有効期限が切れた後に新しい処理クラスを追加する

まず、セッションの有効期限が切れた後にフロントエンド処理クラスに通知する方法を処理する CustomSessionInformationExpiredStrategy.java を作成します。内容は次のとおりです。

public class CustomSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
    
    

    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException {
    
    
        if (log.isDebugEnabled()) {
    
    
           log.debug("{} {}", event.getSessionInformation(), MessageConstant.SESSION_EVICT);
        }
        HttpServletResponse response = event.getResponse();
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        String responseJson = JackJsonUtil.object2String(ResponseFactory.fail(CodeMsgEnum.SESSION_EVICT, MessageConstant.SESSION_EVICT));
        response.getWriter().write(responseJson);
    }
}

注: 通常、フロントエンドに返される情報は自分で書き換え、フレームワークによってスローされたエラー情報を直接使用しません。

configure(HttpSecurity http) メソッドで設定します。

.csrf().disable()
//登录互踢
.sessionManagement()
//在这里设置session的认证策略无效
//.sessionAuthenticationStrategy(new ConcurrentSessionControlAuthenticationStrategy(httpSessionConfig.sessionRegistry()))
.maximumSessions(1)
.sessionRegistry(sessionRegistry())
.maxSessionsPreventsLogin(false) //false表示不阻止登录,就是新的覆盖旧的
//session失效后要做什么(提示前端什么内容)
.expiredSessionStrategy(new CustomSessionInformationExpiredStrategy()); 

注: https://blog.csdn.net/qq_34136709/article/details/106012825 この記事では、セッション認証の原理について説明していますが、セッション認証戦略が実装されていることがわかりましたが、対応するコードをデバッグしたところ、次のことがわかりました。
ここに画像の説明を挿入します
このセッション認証戦略は NullAuthenticatedSessionStrategy であり、それに記載されている ConcurrentSessionControlAuthenticationStrategy ではありません。つまり、このセッション認証ポリシーをどこで設定する必要があるのか​​ということです。最初に考えたのは、configure(HttpSecurity http) の設定が
ここに画像の説明を挿入します
無効であることが判明したということでした。他の人のコードを見て、ログイン時にこの戦略を追加する必要があると思いました。通常、ログインは自分で書き換える必要があるため、上記の書き方は当然無効になります。そこでカスタムログインフィルターを見つけました。
ここに画像の説明を挿入します
ここに画像の説明を挿入します
すると、 this.setSessionAuthenticationStrategy(sessionStrategy); が存在することがわかりました。

public LoginFilter(UserVerifyAuthenticationProvider authenticationManager,
                       CustomAuthenticationSuccessHandler successHandler,
                       CustomAuthenticationFailureHandler failureHandler,
                       SpringSessionBackedSessionRegistry springSessionBackedSessionRegistry) {
    
    
        //设置认证管理器(对登录请求进行认证和授权)
        this.authenticationManager = authenticationManager;
        //设置认证成功后的处理类
        this.setAuthenticationSuccessHandler(successHandler);
        //设置认证失败后的处理类
        this.setAuthenticationFailureHandler(failureHandler);
        //配置session认证策略(将springSecurity包装redis Session作为参数传入)
        ConcurrentSessionControlAuthenticationStrategy sessionStrategy = new
                ConcurrentSessionControlAuthenticationStrategy(springSessionBackedSessionRegistry);
        //最多允许一个session
        sessionStrategy.setMaximumSessions(1);
        this.setSessionAuthenticationStrategy(sessionStrategy);
        //可以自定义登录请求的url
        super.setFilterProcessesUrl("/myLogin");
    }

起動後、セッション認証ポリシーが設定したポリシーに変更されていることがわかりました。

完全な webSecurityConfig は次のとおりです。

@Configuration
@EnableWebSecurity
//RedisFlushMode有两个参数:ON_SAVE(表示在response commit前刷新缓存),IMMEDIATE(表示只要有更新,就刷新缓存)
//yml和注解两者只选其一(同时配置,只有注解配置生效)
@EnableRedisHttpSession(redisNamespace = "spring:session:myframe", maxInactiveIntervalInSeconds = 5000
        , flushMode = FlushMode.ON_SAVE)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    @Autowired
    private UserVerifyAuthenticationProvider authenticationManager;//认证用户类

    @Autowired
    private CustomAuthenticationSuccessHandler successHandler;//登录认证成功处理类

    @Autowired
    private CustomAuthenticationFailureHandler failureHandler;//登录认证失败处理类

    @Autowired
    private MyFilterInvocationSecurityMetadataSource securityMetadataSource;//返回当前URL允许访问的角色列表
    @Autowired
    private MyAccessDecisionManager accessDecisionManager;//除登录登出外所有接口的权限校验


    /**
     * redis获取sessionRepository
     * RedisIndexedSessionRepository实现 FindByIndexNameSessionRepository接口
     */
    @Autowired
    //不加@Lazy这个会报什么循环引用...
    // Circular reference involving containing bean '.RedisHttpSessionConfiguration'
    @Lazy
    private FindByIndexNameSessionRepository<? extends Session> sessionRepository;


    /**
     * 是spring session为Spring Security提供的,
     * 用于在集群环境下控制会话并发的会话注册表实现
     * @return
     */
    @Bean
    public SpringSessionBackedSessionRegistry sessionRegistry(){
    
    
        return new SpringSessionBackedSessionRegistry<>(sessionRepository);
    }

    /**
     * 密码加密
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(PasswordEncoder.class)
    public PasswordEncoder passwordEncoder() {
    
    
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置 HttpSessionIdResolver Bean
     * 登录之后将会在 Response Header x-auth-token 中 返回当前 sessionToken
     * 将token存储在前端 每次调用的时候 Request Header x-auth-token 带上 sessionToken
     */
    @Bean
    public HttpSessionIdResolver httpSessionIdResolver() {
    
    
        return HeaderHttpSessionIdResolver.xAuthToken();
    }






    /**
     * Swagger等静态资源不进行拦截
     */
    @Override
    public void configure(WebSecurity web) {
    
    
        web.ignoring().antMatchers(
                "/*.html",
                "/favicon.ico",
                "/**/*.html",
                "/**/*.css",
                "/**/*.js",
                "/error",
                "/webjars/**",
                "/resources/**",
                "/swagger-ui.html",
                "/swagger-resources/**",
                "/v2/api-docs");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.authorizeRequests()
                //配置一些不需要登录就可以访问的接口,这里配置失效了,放到了securityMetadataSource里面
                //.antMatchers("/demo/**", "/about/**").permitAll()
                //任何尚未匹配的URL只需要用户进行身份验证
                .anyRequest().authenticated()
                //登录后的接口权限校验
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
    
    
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
    
    
                        object.setAccessDecisionManager(accessDecisionManager);
                        object.setSecurityMetadataSource(securityMetadataSource);
                        return object;
                    }
                })
                .and()
                //配置登出处理
                .logout().logoutUrl("/logout")
                .logoutSuccessHandler(new CustomLogoutSuccessHandler())
                .clearAuthentication(true)
                .and()
                //用来解决匿名用户访问无权限资源时的异常
                .exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())
                //用来解决登陆认证过的用户访问无权限资源时的异常
                .accessDeniedHandler(new CustomAccessDeniedHandler())
                .and()
                //配置登录过滤器
                .addFilter(new LoginFilter(authenticationManager, successHandler, failureHandler, sessionRegistry()))

                .csrf().disable()
                //登录互踢
                .sessionManagement()
                //在这里设置session的认证策略无效
                //.sessionAuthenticationStrategy(new ConcurrentSessionControlAuthenticationStrategy(httpSessionConfig.sessionRegistry()))
                .maximumSessions(1)
                .sessionRegistry(sessionRegistry())
                .maxSessionsPreventsLogin(false) //false表示不阻止登录,就是新的覆盖旧的
                //session失效后要做什么(提示前端什么内容)
                .expiredSessionStrategy(new CustomSessionInformationExpiredStrategy());
        //配置头部
        http.headers()
                .contentTypeOptions()
                .and()
                .xssProtection()
                .and()
                //禁用缓存
                .cacheControl()
                .and()
                .httpStrictTransportSecurity()
                .and()
                //禁用页面镶嵌frame劫持安全协议  // 防止iframe 造成跨域
                .frameOptions().disable();
    }




}

他の

@Lazy
private FindByIndexNameSessionRepository<? extends Session> sessionRepository;

@lazy を付けないと循環参照が発生する問題については、あまり気にしたくないです。ずっと見ていても、誰が誰と循環参照しているのか分かりませんでした。

おすすめ

転載: blog.csdn.net/mofsfely2/article/details/116569316