背景
私の記事「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 を付けないと循環参照が発生する問題については、あまり気にしたくないです。ずっと見ていても、誰が誰と循環参照しているのか分かりませんでした。。。。。