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);
}
  1. 其中 maximumSessions(1) 主要就是配置併發數的,可以根據實際情況進行修改。

  2. maxSessionsPreventsLogin 表示是否保留舊用戶,如果保留了舊用戶,再在新的設備上登錄,就會發現登錄不上去。

  3. 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 ,請求返回數據正常;

image-20210807214452766

  • 我們返回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系列文章請 點擊這裏 查看。
這是代碼 碼雲地址
注意注意!!!項目是使用分支的方式來提交每次測試的代碼的,請根據章節來我切換分支。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章