spring-security使用-同一个账号只允许登录一次(五)
1.Spring-Security系列导航2.spring-security使用-登录(一)3.spring-security使用-自定义数据源(二)4.spring-security使用-更友好的方式扩展登录AuthenticationProvider(三)5.spring-security使用-获得当前用户信息(四)
6.spring-security使用-同一个账号只允许登录一次(五)
7.spring-security使用-session共享(六)8.spring-security使用-安全防护HttpFirewall(七)9.spring-security使用-权限控制(八)10.spring-security源码-初始化(九)11.spring-security源码-如何初始化SecurityFilterChain到Servlet12.spring-security源码-FilterChainProxy13.spring-security源码-Filter之WebAsyncManagerIntegrationFilter(十)14.Spring-security源码-Filter之SecurityContextPersistenceFilter(十一)15.Spring-security源码-Filter之HeaderWriterFilter(十二)16.Spring-security源码-Filter之LogoutFilter(十三)17.Spring-security源码-Filter之UsernamePasswordAuthenticationFilter(十四)18.Spring-security源码-Filter之ConcurrentSessionFilter(十五)19.Spring-security源码-Filter之SessionManagementFilter(十六)20.Spring-security源码-Filter之RememberMeAuthenticationFilter(十七)21.Spring-security源码-Filter之ExceptionTranslationFilter(十八)22.Spring-security源码-Filter之FilterSecurityInterceptor(十九)23.Spring-security源码-注解权限原理(二十)24.Spring-security源码-注解权限原理之MethodSecurityInterceptor(二十一)25.Spring-Security基于源码扩展-一套系统多套登录逻辑(二十二)26.Spring-Security基于源码扩展-自定义登录(二十三)27.Spring-Security基于源码扩展-自定义认证失败返回(二十四)28.Spring-Security基于源码扩展-自定义授权注解(二十五)自动挤掉前一个用户
1.配置一个用户只允许一个会话
protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .rememberMe() .key("system") .and() .formLogin() .authenticationDetailsSource(new MyWebAuthenticationDetailsSource()) .usernameParameter("loginName") .passwordParameter("loginPassword") .defaultSuccessUrl("/hello") .failureForwardUrl("/loginFail") .failureUrl("/login.html") .permitAll()//不拦截 .and() .csrf()//记得关闭 .disable() .sessionManagement() .maximumSessions(1); }
2.重写userDetail的hashCode和quals
public class UserInfoDto implements UserDetails { //....省略部分代码 @Override public String toString() { return this.username; } @Override public int hashCode() { return username.hashCode(); } @Override public boolean equals(Object obj) { return this.toString().equals(obj.toString()); } }
3.分别用同一个账号2个浏览器登录。然后再访问第一次登录成功的用户则出现提示
禁止新的账号登录
1.配置
protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .rememberMe() .key("system") .and() .formLogin() .authenticationDetailsSource(new MyWebAuthenticationDetailsSource()) .usernameParameter("loginName") .passwordParameter("loginPassword") .defaultSuccessUrl("/hello") .failureForwardUrl("/loginFail") .failureUrl("/login.html") .permitAll()//不拦截 .and() .csrf()//记得关闭 .disable() .sessionManagement() .maximumSessions(1) .maxSessionsPreventsLogin(true); }
2.增加一个监听的bean
spring事件使用参考<spring源码阅读(一)-附录例子>
@Bean HttpSessionEventPublisher httpSessionEventPublisher() { return new HttpSessionEventPublisher(); }
public class HttpSessionEventPublisher implements HttpSessionListener { private static final String LOGGER_NAME = org.springframework.security.web.session.HttpSessionEventPublisher.class.getName(); public HttpSessionEventPublisher() { } /** * 获得当前ServletContext的spring容器 * @param servletContext * @return */ ApplicationContext getContext(ServletContext servletContext) { return SecurityWebApplicationContextUtils.findRequiredWebApplicationContext(servletContext); } /** * 创建session的 spring事件发送 * @param event */ public void sessionCreated(HttpSessionEvent event) { HttpSessionCreatedEvent e = new HttpSessionCreatedEvent(event.getSession()); Log log = LogFactory.getLog(LOGGER_NAME); if (log.isDebugEnabled()) { log.debug("Publishing event: " + e); } this.getContext(event.getSession().getServletContext()).publishEvent(e); } /** * session销毁的spring事件发送 * @param event */ public void sessionDestroyed(HttpSessionEvent event) { HttpSessionDestroyedEvent e = new HttpSessionDestroyedEvent(event.getSession()); Log log = LogFactory.getLog(LOGGER_NAME); if (log.isDebugEnabled()) { log.debug("Publishing event: " + e); } this.getContext(event.getSession().getServletContext()).publishEvent(e); } }
3.如果有账号登录另外一个账号登录则会提示
源码
1.UsernamePasswordAuthenticationFilter的父类AbstractAuthenticationProcessingFilter
sessionStrategy默认为org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { //省略部分代码...... // 调用子类的attemptAuthentication 处理登录认证 authResult = this.attemptAuthentication(request, response); if (authResult == null) { return; } //认证成功走session校验 this.sessionStrategy.onAuthentication(authResult, request, response); //省略部分代码...... }
2.org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy#onAuthentication
public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) throws SessionAuthenticationException { SessionAuthenticationStrategy delegate; //遍历delegateStrategies 调用onAuthentication方法 for(Iterator var4 = this.delegateStrategies.iterator(); var4.hasNext(); delegate.onAuthentication(authentication, request, response)) { delegate = (SessionAuthenticationStrategy)var4.next(); if (this.logger.isDebugEnabled()) { this.logger.debug("Delegating to " + delegate); } } }
3.真正处理登录剔除和拦截的是SessionAuthenticationStrategy的实现类
org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy
public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) { //获得指定用户名所有的session authentication.getPrincipal()就是登陆名 List<SessionInformation> sessions = this.sessionRegistry.getAllSessions(authentication.getPrincipal(), false); int sessionCount = sessions.size(); //获取我们配置的最大数 int allowedSessions = this.getMaximumSessionsForThisUser(authentication); //如果大于最大配置数 if (sessionCount >= allowedSessions) { if (allowedSessions != -1) { if (sessionCount == allowedSessions) { HttpSession session = request.getSession(false); if (session != null) { Iterator var8 = sessions.iterator(); while(var8.hasNext()) { SessionInformation si = (SessionInformation)var8.next(); if (si.getSessionId().equals(session.getId())) { return; } } } } this.allowableSessionsExceeded(sessions, allowedSessions, this.sessionRegistry); } } } protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions, SessionRegistry registry) throws SessionAuthenticationException { //是否配置了禁止多个账号登录 if (!this.exceptionIfMaximumExceeded && sessions != null) { sessions.sort(Comparator.comparing(SessionInformation::getLastRequest)); int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1; List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy); Iterator var6 = sessionsToBeExpired.iterator(); //将登录的剔除 while(var6.hasNext()) { SessionInformation session = (SessionInformation)var6.next(); session.expireNow(); } } else { //抛出异常 禁止多端登陆 throw new SessionAuthenticationException(this.messages.getMessage("ConcurrentSessionControlAuthenticationStrategy.exceededAllowed", new Object[]{allowableSessions}, "Maximum sessions of {0} for this principal exceeded")); } }
获取登录失败原因
可以通过设置拦截器获取 保存到当前会话
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private ObjectMapper objectMapper; @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { logger.info("登陆失败"); response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(exception));//将异常写入response中,显示在页面上 }
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/authentication/require") .loginProcessingUrl("/authentication/form") .successHandler(myAuthenticationSuccessHandler)//配置successHandler .failureHandler(myAuthenticationFailureHandler)//配置failureHandler .and() .authorizeRequests() .antMatchers( "/loginPage.html", "/myLoginPage.html", "/authentication/require" ).permitAll() .anyRequest() .authenticated() .and() .csrf().disable();
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
2020-01-06 logback源码阅读-集成slf4j初始化过程(一)