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

    原创内容,如果你觉得文章还可以的话,不妨点个赞支持一下!转载请注明出处。
  • 相关阅读:
    HDU_oj_2048 错排问题
    HDU_oj_2047 阿牛的EOF牛肉面
    HDU_oj_2046 骨牌铺方格
    HDU_oj_2045 不容易系列之RPG问题
    拷贝构造函数(三)——重载赋值运算符
    拷贝构造函数(二)——深拷贝与浅拷贝
    拷贝构造函数(一)——哲学三连
    HDU_oj_2044 一只小蜜蜂
    【转发】【linux】【ftp】CentOS 7.0安装配置Vsftp服务器
    【编码】【转发】enca 转换编码
  • 原文地址:https://www.cnblogs.com/lixingwu/p/15113453.html
Copyright © 2011-2022 走看看