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系列文章请 点击这里 查看。
这是代码 码云地址
注意注意!!!项目是使用分支的方式来提交每次测试的代码的,请根据章节来我切换分支。

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