在微信登錄賬號中,如果我們在其他電腦上登錄,會導致當前電腦的登錄賬號被登出,並提示在其他地方登錄了。
如果我們也需要這樣的控制,防止一個賬號在多個地方登錄。在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系列文章請 點擊這裏 查看。
這是代碼 碼雲地址 。
注意注意!!!項目是使用分支的方式來提交每次測試的代碼的,請根據章節來我切換分支。