无法使用自定义过滤器在 Spring 安全性中实施会话限制

Not able to implement session limiting in Spring Security with custom Filter

我的要求是限制多用户登录,在应用程序中一次只允许一个用户登录。 我的应用程序是一个 spring 启动应用程序,具有用于用户授权和身份验证的 JWT 身份验证。 我已经阅读了几篇文章,并且了解到可以在 spring 引导中使用 spring 安全性来实现。

package com.cellpointmobile.mconsole.security;
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private JWTAuthEntryPoint unauthorizedHandler;

    @Bean
    public JWTAuthenticationTokenFilter authenticationJwtTokenFilter() {
        return new JWTAuthenticationTokenFilter();
    }

    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception
    {
        authenticationManagerBuilder
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }


    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }

    @Bean
    public org.springframework.security.crypto.password.PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }

    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        http.cors().and().csrf().disable().
                 authorizeRequests()
                .antMatchers("/mconsole/app/login").permitAll()
                .antMatchers("/mconsole/**").authenticated()
                .and()
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS).maximumSessions(1).
                maxSessionsPreventsLogin(true);

        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        http.apply(customConfigurer());

    }


    @Bean
    public CustomJwtAuthenticationTokenConfigurer customConfigurer() {
        return new CustomJwtAuthenticationTokenConfigurer();
    }

}

这是我的配置 class,我在其中添加了所需的代码。 还添加了一个新的 class 如下:

public class CustomJwtAuthenticationTokenConfigurer extends
        AbstractHttpConfigurer<CustomJwtAuthenticationTokenConfigurer, HttpSecurity> {
    @Autowired
    private JWTAuthenticationTokenFilter myFilter;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        SessionAuthenticationStrategy sessionAuthenticationStrategy = http
                .getSharedObject(SessionAuthenticationStrategy.class);
        myFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
        http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

并在 JWTAuthenticationTokenFilter 中扩展了 AbstractAuthenticationProcessingFilter。

public class JWTAuthenticationTokenFilter extends AbstractAuthenticationProcessingFilter
{
    @Autowired
    private JWTProvider tokenProvider;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

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

    public JWTAuthenticationTokenFilter() {super("/mconsole/**");  }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        String header = request.getHeader("x-cpm-auth-token");
        if (header == null) {
            filterChain.doFilter(request, response);
            return;
        }
        String sToken = header.replace("x-cpm-auth-token", "");
        try {
            if (sToken != null && tokenProvider.validateJwtToken(sToken, userDetailsService)) {
                Authentication authResult;
                UsernamePasswordAuthenticationToken authentication;
                String sUserName = tokenProvider.getUserNameFromJwtToken(sToken, userDetailsService);
                UserDetails userDetails = userDetailsService.loadUserByUsername(sUserName);
                if (userDetails != null) {
                    authentication
                            = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    logger.info("entered in authentication>> " + authentication);
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                } else {
                    throw new AuthenticationCredentialsNotFoundException("Could not createAuthentication user");
                }                try {

                    authResult = attemptAuthentication(request, response);
                    if (authResult == null) {
                        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                        return;
                    }
                } catch (AuthenticationException failed) {
                    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    return;
                }

            }
        } catch (Exception e) {
            logger.error("Access denied !! Unable to set authentication", e);
            SecurityContextHolder.clearContext();
        }
        filterChain.doFilter(request, response);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null)
            throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
        return authentication;
    }

    @Override
    @Autowired
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager);
    }

我仍然可以多次登录,自 10 天以来我一直在努力实现这一目标,@Eleftheria Stein-Kousathana 你能检查一下我现在做错了什么吗?

首先,让我们考虑一下在使用表单登录的应用程序中,并发会话控制在没有自定义过滤器的简单情况下是如何工作的。

有效的登录请求将到达 UsernamePasswordAuthenticationFilter
在此过滤器中,通过调用 SessionAuthenticationStrategy#onAuthentication.
检查会话并发限制 如果未超过最大限制,则用户登录成功。如果超过限制,则抛出 SessionAuthenticationException 一个 returns 对用户的错误响应。

要在自定义过滤器中具有相同的行为,您需要确保在 doFilter 方法中调用了 SessionAuthenticationStrategy#onAuthentication

实现此目的的一种方法是创建自定义配置器并将其应用到 HttpSecurity 配置中。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .apply(customConfigurer())
    // ...
}

public static class CustomJwtAuthenticationTokenConfigurer extends
        AbstractHttpConfigurer<CustomJwtAuthenticationTokenConfigurer, HttpSecurity> {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        CustomJwtAuthenticationTokenFilter myFilter = new CustomJwtAuthenticationTokenFilter();
        SessionAuthenticationStrategy sessionAuthenticationStrategy = http
                .getSharedObject(SessionAuthenticationStrategy.class);
        myFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
        http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
    }

    public static CustomJwtAuthenticationTokenConfigurer customConfigurer() {
        return new CustomJwtAuthenticationTokenConfigurer();
    }
}

这假设 CustomJwtAuthenticationTokenFilter 扩展了 AbstractAuthenticationProcessingFilter,但即使没有扩展,配置也会相似。