首页 > 编程语言 > 详细

SpringSecurity3.2.10 + SpringBoot2.1.11 + ConcurrentSession(分布式会话)+ redis

时间:2020-07-12 10:02:42      阅读:74      评论:0      收藏:0      [点我收藏+]

引言

 

1、引入maven依赖

本项目中用的SpringBoot2.1.11,引入自带的 spring-boot-starter-security 版本为 5.1.7,但是由于是老项目需要兼容旧版本,所以使用了低版本的 3 个SpringSecurity包:

     <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>3.2.10.RELEASE</version> 
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>3.2.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>3.2.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <!-- <version>1.5.20.RELEASE</version> -->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.security</groupId>
                    <artifactId>spring-security-core</artifactId>  <!-- default version is 5.1.7 -->
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.security</groupId>
                    <artifactId>spring-security-web</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.security</groupId>
                    <artifactId>spring-security-config</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

当应用多节点分布式部署的时候,SpringSecurity本身是不能控制分布式会话的,所以需要第三方介质的介入,这里选择 redis,导入redis依赖:

        <dependency>
             <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>1.5.22.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>  <!-- 注意,这个包必须引入,它可以支持SpringBoot的内置tomcat通过redis同步所有session -->
        </dependency>    

 

2、自定义分布式会话控制类

这里先贴代码,后面再说怎么注入到SpringBoot 和SpringSecurity

import com.test.MyUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.session.SessionDestroyedEvent;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.stereotype.Component;

import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

@Component
public class MySessionRegistryImpl implements SessionRegistry, ApplicationListener<SessionDestroyedEvent> {

    private static final Logger logger = LoggerFactory.getLogger(MySessionRegistryImpl.class);

    // 以下两个集合的代码作用,参考Spring原生实现类: org.springframework.security.core.session.SessionRegistryImpl
     
    /** <principal:Object,SessionIdSet> */
    private final ConcurrentMap<Object, Set<String>> principals = new ConcurrentHashMap<Object,Set<String>>();
    /** <sessionId:Object,SessionInformation> */
    private final Map<String, SessionInformation> sessionIds = new ConcurrentHashMap<String, SessionInformation>();

    @Autowired
    @Qualifier("redisTemplate")
    RedisTemplate redisTemplate;

    @Value("${session.timeout.minutes}")
    private Integer sessionTimeoutMinutes;

    @Override
    public void registerNewSession(String sessionId, Object principal) {
        MyUser myUser = (MyUser) principal;
        try {

            // put login user to local collection
            sessionIds.put(sessionId, new SessionInformation(principal, sessionId, new Date()));
            logger.info("put login user to local collection success, username={}, sessionId={}", myUser.getUsername(), sessionId);

            // put login user to redis
            byte[] bytes = myUserToBytes(myUser);
            redisTemplate.opsForValue().set(sessionId, bytes, sessionTimeoutMinutes, TimeUnit.MINUTES);
            myUser.toString().getBytes();
            logger.info("put login user to redis success, username={}, sessionId={}, bytes.length={}", myUser.getUsername(), sessionId, bytes.length);

        } catch (IOException e) {
            logger.error("register new sessionId[{}] to redis is fail, username={}", sessionId, myUser.getUsername(), e);
        }
    }

    /**
     * 这里参考kafka的优秀思想,存储数据都使用byte[]数组 
     * object to byte[]
     */
    public byte[] myUserToBytes(MyUser myUser) throws IOException {
        try(
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                ObjectOutputStream sOut = new ObjectOutputStream(out);
        ){
            sOut.writeObject(myUser);
            sOut.flush();
            byte[] bytes = out.toByteArray();
            return bytes;
        }
    }

    /**
     * byte[] to object
     */
    public MyUser bytesToMyUser(byte[] bytes) throws IOException, ClassNotFoundException {
        try(
                ByteArrayInputStream in = new ByteArrayInputStream(bytes);
                ObjectInputStream sIn = new ObjectInputStream(in);
        ){
            return (MyUser) sIn.readObject();
        }
    }

    @Override
    public SessionInformation getSessionInformation(String sessionId) {

        // get login user from local collection
        SessionInformation sessionInformation = sessionIds.get(sessionId);
        if(null != sessionInformation){
            MyUser myUser = (MyUser) sessionInformation.getPrincipal();
            logger.info("get login user from local collection by sessionId success, username={}, sessionId={}", myUser.getUsername(), sessionId);

            return sessionInformation;
        }

        // get login user from redis
        Object sessionValue = redisTemplate.opsForValue().get(sessionId);
        if(null == sessionValue){
            logger.info("can‘t find login user from redis by sessionId[{}]", sessionId);
            return null;
        }

        try {
            byte[] bytes = (byte[]) sessionValue;
            logger.info("get login user from redis by sessionId success, bytes.length={}", bytes.length);

            MyUser myUser = bytesToMyUser(bytes);
            logger.info("get login user from redis by sessionId success, username={}, sessionId={}, bytes.length={}", myUser.getUsername(), sessionId, bytes.length);

            SessionInformation sessionInfo = new SessionInformation(myUser, sessionId, new Date());
            return sessionInfo;

        } catch (ClassNotFoundException | IOException e) {
            logger.error("get myUser from redis by session[{}] is fail", sessionId, e);
        }
        return null;
    }

    @Override
    public void removeSessionInformation(String sessionId) {
        boolean isDelete = redisTemplate.delete(sessionId);
        logger.info("remove sessionId from redis is sucess. isDelete={}", isDelete);
    }

    @Override
    public List<Object> getAllPrincipals() {
        return new ArrayList<Object>(principals.keySet());
    }

    @Override
    public List<SessionInformation> getAllSessions(Object principal, boolean includeExpiredSessions) {
        final Set<String> sessionsUsedByPrincipal = principals.get(principal);

        if (sessionsUsedByPrincipal == null) {
            return Collections.emptyList();
        }

        List<SessionInformation> list = new ArrayList<SessionInformation>(sessionsUsedByPrincipal.size());

        for (String sessionId : sessionsUsedByPrincipal) {
            SessionInformation sessionInformation = getSessionInformation(sessionId);

            if (sessionInformation == null) {
                continue;
            }

            if (includeExpiredSessions || !sessionInformation.isExpired()) {
                list.add(sessionInformation);
            }
        }

        return list;
    }

    @Override
    public void onApplicationEvent(SessionDestroyedEvent event) {
        String sessionId = event.getId();
        removeSessionInformation(sessionId);
    }

    @Override
    public void refreshLastRequest(String sessionId) {
        SessionInformation info = getSessionInformation(sessionId);

        if (info != null) {
            info.refreshLastRequest();
        }
    }
}

 

2、MySessionRegistryImpl 以SpringBoot的方式注入SpringSecurity

 这部分由于我使用了低版本的SpringSecurity,所以参考spring官网地址是: https://docs.spring.io/spring-security/site/docs/3.1.x/reference/session-mgmt.html 《12. Session Management Prev Part III. Web Application Security》

SpringSecurity其他高版本的官方文档地址参考: https://docs.spring.io/spring-security/site/docs/

 

 

 

 

 

 

 

end.

SpringSecurity3.2.10 + SpringBoot2.1.11 + ConcurrentSession(分布式会话)+ redis

原文:https://www.cnblogs.com/zhuwenjoyce/p/13287038.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!