spring-security使用-session共享(六)

session共享的几种方案

方案一

不放在服务器应用中管理,放在第三方统一管理,如redis,数据库等,现在主流都是放在redis 因为redis高效qps最高能达到10万+

方案二

session 拷贝,集群情况某一台服务器session发生改变,通知其他服务器,这样会有个问题,如果集群实例太多,要通知太多的服务器,而且原子性也需要保证(所有服务要么一起成功要么一起失败)

方案三

粘滞会话,我第一家公司就是这样处理,根据用户id nginx都路由到一个实例

自定义

使用spring自带的实现

通过SpringSessionBackedSessionRegistry 通过关键字百度搜搜很多方案

自定义SessionRegistory

可以参考默认的org.springframework.security.core.session.SessionRegistryImpl 因为是基于应用的 sessionCRUD都是操作应用内数据结构我们

1.接口定义

public interface SessionRegistry {
    //获得所有会话sessionId
    List<Object> getAllPrincipals();

    //获得指定用户的会话列表
    List<SessionInformation> getAllSessions(Object userId, boolean var2);

    //获得指定sessionId的会话
    SessionInformation getSessionInformation(String sessionId);

    //刷新访问时间
    void refreshLastRequest(String var1);

    //注册会话
    void registerNewSession(String sessionId, Object var2);

    //删除会话
    void removeSessionInformation(String var1);
}

2.实现

public class RedisSessionRegistryImpl implements SessionRegistry, ApplicationListener<SessionDestroyedEvent> {

    private RedisTemplate redisTemplate;
    //维护所有sessionId列表的zset score为过期时间
    private final static String SESSION_LIST_KEY = "session:list";
    //维护指定用户sessionId列表的zset score为过期时间
    private final static String SESSION_USER_LIST_KEY = "session:list:userId:%s";
    //存储sessionId对应的数据
    private final static String SESSION_ITEM_KEY = "session:item:%s";

    public RedisSessionRegistryImpl(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

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

    /**
     * 获得所有会话
     *
     * @return
     */
    @Override
    public List<Object> getAllPrincipals() {
        Set<Object> sessionIds = redisTemplate.boundZSetOps(SESSION_LIST_KEY).range(0, -1);
        if (CollectionUtils.isEmpty(sessionIds)) {
            return null;
        }
        return sessionIds.stream().collect(Collectors.toList());
    }

    /**
     * 获得指定用户列表的会话
     *
     * @param includeExpiredSessions
     * @return
     */
    @Override
    public List<SessionInformation> getAllSessions(Object userId, boolean includeExpiredSessions) {
        Set<String> sessionsUsedByPrincipal = redisTemplate.boundZSetOps(String.format(SESSION_USER_LIST_KEY, userId)).range(0, -1);
        if (sessionsUsedByPrincipal == null) {
            return Collections.emptyList();
        } else {
            List<SessionInformation> list = new ArrayList(sessionsUsedByPrincipal.size());
            Iterator var5 = sessionsUsedByPrincipal.iterator();

            while (true) {
                SessionInformation sessionInformation;
                do {
                    do {
                        if (!var5.hasNext()) {
                            return list;
                        }

                        String sessionId = (String) var5.next();
                        sessionInformation = this.getSessionInformation(sessionId);
                    } while (sessionInformation == null);
                } while (!includeExpiredSessions && sessionInformation.isExpired());

                list.add(sessionInformation);
            }
        }
    }

    /**
     * 根据sessionId获得会话
     *
     * @param sessionId
     * @return
     */
    @Override
    public SessionInformation getSessionInformation(String sessionId) {
        Object jsonValue = redisTemplate.opsForValue().get(String.format(SESSION_ITEM_KEY, sessionId));
        if (StringUtils.isEmpty(jsonValue)) {
            return null;
        }
        JSONObject jsonObject= JSON.parseObject(jsonValue.toString());
        SessionInformation sessionInformation=new  SessionInformation(JSON.parseObject(jsonObject.getString("principal"), UserInfoDto.class),jsonObject.getString("sessionId"),jsonObject.getDate("lastRequest"));
        if(jsonObject.getBoolean("expired")){
            sessionInformation.expireNow();
        }
        return sessionInformation;
    }

    /**
     * 刷新session过期时间
     *
     * @param sessionId
     */
    @Override
    public void refreshLastRequest(String sessionId) {
        SessionInformation info = this.getSessionInformation(sessionId);
        if (info != null) {
            info.refreshLastRequest();
        }
        registerNewSession(sessionId, info);
    }

    /**
     * 注册session
     *
     * @param sessionId
     * @param principal
     */
    @Override
    public void registerNewSession(String sessionId, Object principal) {
        SessionInformation sessionInformation =new SessionInformation(principal, sessionId, new Date());
        redisTemplate.opsForValue().set(String.format(SESSION_ITEM_KEY, sessionId), JSON.toJSONString(sessionInformation));
        redisTemplate.boundZSetOps(SESSION_LIST_KEY).add(sessionId, System.currentTimeMillis() + 30000);
        String username;
        //因为第一次传入的是username 登录成功传入的是我们的UserDetails对象
        if(principal instanceof UserDetails){
            username=((UserDetails)principal).getUsername();
        }else{
            username=principal.toString();
        }

        redisTemplate.boundZSetOps(String.format(SESSION_ITEM_KEY, username)).add(sessionId, System.currentTimeMillis() + 30000);

    }

    /**
     * 从会话中移除
     *
     * @param sessionId
     */
    @Override
    public void removeSessionInformation(String sessionId) {
        SessionInformation sessionInformation = getSessionInformation(sessionId);
        redisTemplate.delete(String.format(SESSION_ITEM_KEY, sessionId));
        redisTemplate.boundZSetOps(SESSION_LIST_KEY).add(sessionId, System.currentTimeMillis() + 30000);
        if (sessionInformation != null) {
            redisTemplate.boundZSetOps(String.format(SESSION_ITEM_KEY, sessionInformation.getPrincipal())).add(sessionId, System.currentTimeMillis() + 30000);
        }
    }
}

3.替换

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .rememberMe()
                .key("system")
                .and()
                .formLogin()
                .authenticationDetailsSource(new MyWebAuthenticationDetailsSource())
                .usernameParameter("loginName")
                .passwordParameter("loginPassword")
                .defaultSuccessUrl("/hello")
                .failureForwardUrl("/loginFail")
                .failureUrl("/login.html")
                .permitAll()//不拦截
                .and()
                .csrf()//记得关闭
                .disable()
                .sessionManagement()
                .maximumSessions(1)
                .maxSessionsPreventsLogin(true)
                .sessionRegistry(new RedisSessionRegistryImpl(redisTemplate));
    }

源码

回头看《spring-security使用-同一个账号只允许登录一次(五)》的源码

1.org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy#onAuthentication

public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) throws SessionAuthenticationException {
        SessionAuthenticationStrategy delegate;
        //遍历delegateStrategies 调用onAuthentication方法
        for(Iterator var4 = this.delegateStrategies.iterator(); var4.hasNext(); delegate.onAuthentication(authentication, request, response)) {
            delegate = (SessionAuthenticationStrategy)var4.next();
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Delegating to " + delegate);
            }
        }

    }

2.org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy#onAuthentication

public class RegisterSessionAuthenticationStrategy implements SessionAuthenticationStrategy {
    private final SessionRegistry sessionRegistry;

    public RegisterSessionAuthenticationStrategy(SessionRegistry sessionRegistry) {
        Assert.notNull(sessionRegistry, "The sessionRegistry cannot be null");
        this.sessionRegistry = sessionRegistry;
    }

    public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
        this.sessionRegistry.registerNewSession(request.getSession().getId(), authentication.getPrincipal());
    }
}

 

posted @ 2021-01-07 11:00  意犹未尽  阅读(2292)  评论(0编辑  收藏  举报