Spring Security10、账号登录并发控制
在微信登录账号中,如果我们在其他电脑上登录,会导致当前电脑的登录账号被登出,并提示在其他地方登录了。
如果我们也需要这样的控制,防止一个账号在多个地方登录。在Spring Security中也是可以做到了。
在configure配置中,有一个叫
sessionManagement
配置,我们通过对其配置可以达到相同的效果。
一、配置HttpSecurity
// 省略其他
// 这里注入的类看第二步
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
是到达最大并发数时的处理器,们可以自定义一个处理器,用来处理到达最大并发数时应该如何处理。
二、自定义到达最大并发数时的处理器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的equals和hashCode
在实际环境中,我们不会直接使用 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);
}
}
三、登录并发测试
- 我们在IDEA的http工具中登录账号 user1,然后请求接口
/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
- 我们在postman登录账号 user1,然后请求接口
/get
,请求返回数据正常;
- 我们返回IDEA的http工具中,请求接口
/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系列文章请 点击这里 查看。
这是代码 码云地址 。
注意注意!!!项目是使用分支的方式来提交每次测试的代码的,请根据章节来我切换分支。
原创内容,如果你觉得文章还可以的话,不妨点个赞支持一下!转载请注明出处。