摘要:本文主要目的是解决spring security在使用自定义登录授权过滤器时,在protected void configure(final HttpSecurity http)方法中设置maximumSessions属性不生效的问题。本文只是进行了粗略的思路,包含主要的代码片段,并非完整的业务代码,仅供参考。
目录
起因
业务需要设置账号只能存在唯一一个有效的登录,因为使用的spring security,所以第一时间考虑使用security内置的sessionManagement().maximumSessions(1)来设置,但是设置完相关参数之后发现不生效。
调查
通过对业务代码的以及security的源代码的跟踪,结合网上查询的资料,最终发现导致不生效的原因:
- 代码实现的登录授权过滤器直接继承自AbstractAuthenticationProcessingFilter过滤器,并且在过滤器只进行了setAuthenticationManager操作,而AbstractAuthenticationProcessingFilter的默认的SessionAuthenticationStrategy处理类是NullAuthenticatedSessionStrategy,此处既是导致数量设置不生效的根本原因;
- 使用redis做的共享session,session的创建及销毁等操作不通过security;
- 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<SessionAuthenticationStrategy> authenticationStrategies = new ArrayList<SessionAuthenticationStrategy>();
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<SessionAuthenticationStrategy> 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即可。
后记
致歉:由于解决这个问题的时间较长,参考的文章数量较多,解决后已经记不得具体参考引用了哪些文章,如果文章作者在本文中看到相似代码,请联系我,核实后,会在文章末尾添加文章的引用。