一次关于Spring Security的session剔除问题

2019-07-12  本文已影响0人  just_like_you

发生的主要情况是这样的,在使用Spring Security搭建后台的RBAC权限系统的时候遇到一个需要 :

在管理员修改权限的时候,要求将已经被修改的用户的会话剔除。

个人实现的解决方案以及步骤

自定义事件以及发送事件,携带角色id(方法获取该角色对应的所有用户id)

public class PermissionChangeEvent extends ApplicationEvent {

    private final Integer roleId;

    public PermissionChangeEvent(Object source, Integer roleId) {
        super(source);
        this.roleId = roleId;
    }

    public Integer getRoleId() {
        return roleId;
    }
}
    @Transactional(rollbackFor = Exception.class)
    @Override
    public CommonResult setPermission(Integer roleId, List<Integer> resourceIds) {
        if (roleId == null || roleId <= 0) {
            return CommonResult.fail(AccountEnum.INVALID_ROLE_ID);
        }
        if (CollectionUtils.isEmpty(resourceIds)) {
            return CommonResult.fail(AccountEnum.NOT_EMPTY_RESOURCE);
        }
        int flag = changePermission(roleId, resourceIds);
        if (flag > 0) {
            //发送重置权限事件,让当前token失效
            applicationEventPublisher.publishEvent(new PermissionChangeEvent(this,roleId));
            return CommonResult.success();
        }
        return CommonResult.fail(AccountEnum.SET_PERMISSION_ERROR);
    }
@Component
@Slf4j
public class PermissionChangeListener {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private AdminAccountService adminAccountService;
    @Autowired
    private SessionRegistry sessionRegistry;

    @EventListener(PermissionChangeEvent.class)
    @Async
    public void permissionListenPostProcess(PermissionChangeEvent event) {
        Integer roleId = event.getRoleId();
        log.debug("receive permission changed event , eventClassName : [{}]  ,roleId : [{}]", event.getClass().getName(), roleId);
        //获取在线的用户列表
        List<JWTUserDetails> onlineUserList = getOnlineUserList();

        adminAccountService.findAccountListByRoleId(roleId)
                .forEach(accountId -> {
                    stringRedisTemplate.delete(JWTUtils.REDIS_KEY_PREFIX + accountId);
                    //完成session踢出
                    excludeChangedSession(onlineUserList, accountId);
                });
    }

    /**
     * 剔除被修改权限后的会话
     *
     * @param onlineUserList
     * @param accountId
     */
    private void excludeChangedSession(List<JWTUserDetails> onlineUserList, Integer accountId) {
        onlineUserList.stream().filter(userDetails -> userDetails.getUserId().equals(accountId))
                .forEach(userDetails -> {
                    //session剔除
                    sessionRegistry.getAllSessions(userDetails, false).forEach(SessionInformation::expireNow);
                });
    }

    /**
     * 获取在线的userDetails列表
     *
     * @return
     */
    private List<JWTUserDetails> getOnlineUserList() {
        return sessionRegistry.getAllPrincipals()
                .stream()
                .filter(p->p instanceof JWTUserDetails)
                .map(p-> ((JWTUserDetails) p))
                .collect(toList());
    }
}

主要步骤就是:

最后使用Spring Security的Session配置管理来完成自定义的Session失效处理,配置如下,这里我自己贴上了完整的配置,如果只需要自定义Session失效处理,只需要怕配置.sessionManagement().maximumSessions(1).expiredUrl(traceSystemProperties.getSessionInvalidUrl()).sessionRegistry(sessionRegistry()) 和注入一个SessionRegistryBean即可

@Configuration
@EnableWebSecurity
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final JWTAuthenticationFilter jwtAuthenticationFilter;

    private final CustomSuccessHandler customSuccessHandler;

    private final CustomFailureHandler customFailureHandler;

    private final ObjectMapper objectMapper;

    private final TraceSystemProperties traceSystemProperties;

    private final CustomUserDetailsService customUserDetailsService;

    private final DataSource dataSource;

    public WebSecurityConfig(JWTAuthenticationFilter jwtAuthenticationFilter,
                             CustomSuccessHandler customSuccessHandler,
                             CustomFailureHandler customFailureHandler,
                             ObjectMapper objectMapper,
                             TraceSystemProperties traceSystemProperties,
                             CustomUserDetailsService customUserDetailsService,
                             DataSource dataSource) {
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
        this.customSuccessHandler = customSuccessHandler;
        this.customFailureHandler = customFailureHandler;
        this.objectMapper = objectMapper;
        this.traceSystemProperties = traceSystemProperties;
        this.customUserDetailsService = customUserDetailsService;
        this.dataSource = dataSource;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .formLogin()
                .failureHandler(customFailureHandler)
                .successHandler(customSuccessHandler)
                .and()
                .rememberMe()
                .tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(traceSystemProperties.getRememberMeExpire())
                .userDetailsService(customUserDetailsService)
                .and()
                .sessionManagement().maximumSessions(1).expiredUrl(traceSystemProperties.getSessionInvalidUrl()).sessionRegistry(sessionRegistry())
                .and().and()
                .authorizeRequests()
                .antMatchers(traceSystemProperties.getLogoutUrl(),traceSystemProperties.getSessionInvalidUrl())
                .permitAll()
                .and()
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) //jwt校验filter
                .authorizeRequests().anyRequest().access("@checkPermissionProcessor.checkPermission(request)")
                .and()
                .exceptionHandling().authenticationEntryPoint(
                (req, rsp, e) -> rsp.getWriter().write(objectMapper.writeValueAsString(
                        CommonError.builder()
                                .msg(e.getMessage())
                                .status(HttpStatus.UNAUTHORIZED.value())
                                .build())))//自定义401错误解析
                .accessDeniedHandler(
                        (req, rsp, e) -> rsp.getWriter().write(objectMapper.writeValueAsString(
                                CommonError.builder()
                                        .msg(e.getMessage())
                                        .status(HttpStatus.FORBIDDEN.value())
                                        .build())));//自定义403错误解析

    }

    /**
     * 配置remember-me
     *
     * @return
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
//        repository.setCreateTableOnStartup(true); //开机启动生成表结构
        repository.setDataSource(dataSource);
        return repository;
    }

    /**
     * 设置session注册器
     * @return
     */
    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    /**
     * 加密解密
     *
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
@RestController
@Slf4j
public class SessionHandler {

    @RequestMapping("/session/timeout")
    public CommonResult sessionTimeoutHandler() {
        log.debug("current session timeout");
        return CommonResult.fail("session timeout");
    }
}

然后就解决了上述的session剔除问题。

上一篇下一篇

猜你喜欢

热点阅读