spring-boot - 无法使用自定义 Filter 在 Spring Security 中实现 session 限制

标签 spring-boot spring-security session-management

我的要求是限制多个用户登录,并在应用程序中一次只允许一个用户登录。 我的应用程序是一个 Spring Boot 应用程序,为用户授权和身份验证实现了 JWT 身份验证。 我读了几篇文章,了解到可以在 Spring Boot 中使用 Spring Security 来实现。

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();
    }

}

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

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 你能检查一下我现在做错了什么吗?

最佳答案

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

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

要在自定义过滤器中具有相同的行为,您需要确保在 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,但即使没有扩展,配置也会类似。

关于spring-boot - 无法使用自定义 Filter 在 Spring Security 中实现 session 限制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65182973/

相关文章:

java - 使用Futures时Spring Boot超时

spring-security - 如何使用 Spring WebFlux 获取 "Authentication"对象?

spring - Spring-Security 中默认的 AuthenticationManager 是什么?它是如何进行身份验证的?

spring-boot - 在具有单独的 IdP 服务器的 spring boot 应用程序中使用 redis

java - Spring Cloud/Boot 与 Wildfly Swarm

gradle - Spring Boot Fat jar 的 Java 代码签名

java - Spring Data JPA同时保存两个实体,列 "user_id"中为空值

spring - 在 Spring Boot 中保护内部微服务调用的最佳方法是什么

java - 未经身份验证的应用程序的 Http session