在微信登录账号中,如果我们在其他电脑上登录,会导致当前电脑的登录账号被登出,并提示在其他地方登录了。
如果我们也需要这样的控制,防止一个账号在多个地方登录。在Spring Security中也是可以做到了。
在configure配置中,有一个叫
sessionManagement
配置,我们通过对其配置可以达到相同的效果。
// 省略其他 // 这里注入的类看第二步 private final JsonSessionInformationExpiredStrategy sessionInformationExpiredStrategy; // 省略其他 @Autowired public SecurityConfig(JsonSessionInformationExpiredStrategy sessionInformationExpiredStrategy) { this.sessionInformationExpiredStrategy = sessionInformationExpiredStrategy; } @Override protected void configure(HttpSecurity http) throws Exception { http ...(省略前面配置).. // 以下为主要配置 // 配置一个账号登录的并发数 .sessionManagement().maximumSessions(1) // 是否保留旧用户 .maxSessionsPreventsLogin(false) // 登录失效提示 .expiredSessionStrategy(sessionInformationExpiredStrategy); }
其中 maximumSessions(1)
主要就是配置并发数的,可以根据实际情况进行修改。
maxSessionsPreventsLogin
表示是否保留旧用户,如果保留了旧用户,再在新的设备上登录,就会发现登录不上去。
expiredSessionStrategy
是到达最大并发数时的处理器,们可以自定义一个处理器,用来处理到达最大并发数时应该如何处理。
JsonSessionInformationExpiredStrategy.java package com.miaopasi.securitydemo.config.security.handler; import cn.hutool.core.lang.Console; import cn.hutool.core.lang.Dict; import cn.hutool.core.util.CharsetUtil; import cn.hutool.extra.servlet.ServletUtil; import cn.hutool.http.ContentType; import cn.hutool.json.JSONUtil; import org.springframework.security.web.session.SessionInformationExpiredEvent; import org.springframework.security.web.session.SessionInformationExpiredStrategy; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 并发登录导致session失效时处理 * * @author lixin */ @Component public class JsonSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy { @Override public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException { Console.log("你的账号已在其他地方登录"); // 获取请求对象 final HttpServletResponse response = event.getResponse(); // 返回json字符串提示 Dict res = Dict.create().set("code", 1000).set("msg", "你的账号已在其他地方登录"); String contentType = ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8); ServletUtil.write(response, JSONUtil.toJsonStr(res), contentType); } }
在实际环境中,我们不会直接使用 UserDetails ,例如前面的文章中,我就自定义了SysUser这个类(Spring Security7、使用动态用户进行登录),这时如果我需要控制并发数,就需要重写equals和hashCode。不然使用默认的equals方法就可能出现不相同的情况。
我们只需要验证SysUser的账号ID一致就表示是同一个用户。
package com.miaopasi.securitydemo.config.security; import lombok.Data; import org.springframework.security.core.CredentialsContainer; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.math.BigDecimal; import java.util.Collection; import java.util.Date; import java.util.Objects; import java.util.StringJoiner; /** * 用户信息 * * @author lixin */ @Data public class SysUser implements UserDetails, CredentialsContainer { ...(省略)... @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof SysUser)) { return false; } SysUser sysUser = (SysUser) o; // id相同就是同一个用户 return Objects.equals(id, sysUser.id); } @Override public int hashCode() { return Objects.hash(id); } }
/get
,请求返回数据正常;GET http://127.0.0.1:8080/get HTTP/1.1 200 Vary: Origin Vary: Access-Control-Request-Method Vary: Access-Control-Request-Headers X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Cache-Control: no-cache, no-store, max-age=0, must-revalidate Pragma: no-cache Expires: 0 X-Frame-Options: DENY Content-Type: text/plain;charset=UTF-8 Content-Length: 7 Date: Sat, 07 Aug 2021 13:42:42 GMT Keep-Alive: timeout=60 Connection: keep-alive success Response code: 200; Time: 34ms; Content length: 7 bytes
/get
,请求返回数据正常;/get
,请求返回已在其他地方登录。GET http://127.0.0.1:8080/get HTTP/1.1 200 Vary: Origin Vary: Access-Control-Request-Method Vary: Access-Control-Request-Headers X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Cache-Control: no-cache, no-store, max-age=0, must-revalidate Pragma: no-cache Expires: 0 X-Frame-Options: DENY Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Sat, 07 Aug 2021 13:47:54 GMT Keep-Alive: timeout=60 Connection: keep-alive { "msg": "你的账号已在其他地方登录", "code": 1000 } Response code: 200; Time: 11ms; Content length: 34 bytes
spring security系列文章请 点击这里 查看。
这是代码 码云地址 。
注意注意!!!项目是使用分支的方式来提交每次测试的代码的,请根据章节来我切换分支。