spring security自定义登录授权过滤器限制同一账号同时在线数量

摘要:本文主要目的是解决spring security在使用自定义登录授权过滤器时,在protected void configure(final HttpSecurity http)方法中设置maximumSessions属性不生效的问题。本文只是进行了粗略的思路,包含主要的代码片段,并非完整的业务代码,仅供参考。

 

目录

起因

调查

解决

后记


起因

        业务需要设置账号只能存在唯一一个有效的登录,因为使用的spring security,所以第一时间考虑使用security内置的sessionManagement().maximumSessions(1)来设置,但是设置完相关参数之后发现不生效。

调查

        通过对业务代码的以及security的源代码的跟踪,结合网上查询的资料,最终发现导致不生效的原因:

  1. 代码实现的登录授权过滤器直接继承自AbstractAuthenticationProcessingFilter过滤器,并且在过滤器只进行了setAuthenticationManager操作,而AbstractAuthenticationProcessingFilter的默认的SessionAuthenticationStrategy处理类是NullAuthenticatedSessionStrategy,此处既是导致数量设置不生效的根本原因;
  2. 使用redis做的共享session,session的创建及销毁等操作不通过security;
  3. ConcurrentSessionControlAuthenticationStrategy类时负责session数量验证的接口,登录请求处理链在执行的时候未执行该类及类中的相关方法;

       想要解决这个问题,只需要让ConcurrentSessionControlAuthenticationStrategy在登录授权的处理链中被调用即可。通过查找资料,发现ConcurrentSessionControlAuthenticationStrategy只是处理session数量验证的一个单以的处理,在链条中还需要进行其他的业务处理,所以最终实现的目标是引入CompositeSessionAuthenticationStrategy

 解决

        在WebSecurityConfigurerAdapter的实现类中对上述所需要的类进行初始化,关键代码如下:

    @Autowired
    private AuthenticationManager authenticationManager;
    // 此处为自定义的授权处理类
    @Autowired
    private MyAuthenticationService authenticationService;
    // 此处为超出数量时的处理类
    @Autowired
    private SessionExpiredStrategy sessionInformationExpiredStrategy;

    @Autowired(required = false)
    private SessionRegistry sessionRegistry;

    @Bean
    public MySessionAuthenticationFilter accountAuthenticationFilter() {
    	List authenticationStrategies = new ArrayList();
    	authenticationStrategies.add(controlAuthenticationStrategy(sessionRegistry));
    	authenticationStrategies.add(sessionFixationProtectionStrategy());
    	authenticationStrategies.add(registerSessionAuthenticationStrategy(sessionRegistry));
    	CompositeSessionAuthenticationStrategy strategy = sessionAuthenticationStrategy(authenticationStrategies);
        // 自定义的授权过滤器,继承自AbstractAuthenticationProcessingFilter
        final MySessionAuthenticationFilter filter = new MySessionAuthenticationFilter(authenticationService);
        filter.setAuthenticationManager(authenticationManager);
        filter.setSessionAuthenticationStrategy(strategy);
        return filter;
    }

    @Bean
    public ConcurrentSessionFilter concurrentSessionFilter(SessionRegistry sessionRegistry){
        // 需要在此处初类构造时加入数量超过时的处理类
        return new ConcurrentSessionFilter(sessionRegistry, sessionInformationExpiredStrategy);
    }
    
    @Bean
    public ConcurrentSessionControlAuthenticationStrategy controlAuthenticationStrategy(SessionRegistry sessionRegistry){
        ConcurrentSessionControlAuthenticationStrategy strategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
        // 需要在此处设置允许同时在线的最大数量
        strategy.setMaximumSessions(1);
        return strategy;
    }
    
    @Bean
    public SessionFixationProtectionStrategy sessionFixationProtectionStrategy(){
        return new SessionFixationProtectionStrategy();
    }


    @Bean
    public RegisterSessionAuthenticationStrategy registerSessionAuthenticationStrategy(SessionRegistry sessionRegistry){
        return new RegisterSessionAuthenticationStrategy(sessionRegistry);
    }

    @Bean
    public CompositeSessionAuthenticationStrategy sessionAuthenticationStrategy(List authenticationStrategies){
        return new CompositeSessionAuthenticationStrategy(authenticationStrategies);
    }

    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;

import org.apache.http.HttpStatus;
import org.springframework.security.web.session.SessionInformationExpiredEvent;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.stereotype.Component;

@Component
public class SessionExpiredStrategy implements SessionInformationExpiredStrategy {

	@Override
	public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
		HttpServletResponse response = event.getResponse();
        response.setStatus(HttpStatus.SC_UNAUTHORIZED);
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write("您的账号已经在别的地方登录!");
	}

}

 最后在HttpSecurity配置时通过addFilter方法加入MySessionAuthenticationFilter即可。

后记

        致歉:由于解决这个问题的时间较长,参考的文章数量较多,解决后已经记不得具体参考引用了哪些文章,如果文章作者在本文中看到相似代码,请联系我,核实后,会在文章末尾添加文章的引用。

你可能感兴趣的:(解决方案,java,spring,security,问题解决)