我需要一个单独的 JWT 过滤器来进行多次登录吗?

Posted

技术标签:

【中文标题】我需要一个单独的 JWT 过滤器来进行多次登录吗?【英文标题】:Do I need a separate JWT Filter for Multiple Logins? 【发布时间】:2021-12-11 16:23:40 【问题描述】:

用户登录运行良好,但我想将客户模块添加到项目中。我知道我需要编写一个自定义 UserDetails 类来获取客户用户名,但我想问我是否需要为客户登录验证编写另一个自定义 JWT 过滤器。目前这是我用于用户登录的过滤器类。我已向 Customer 实体添加了用户名和密码字段。


@Component
public class JwtRequestFilter extends OncePerRequestFilter 

    

    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    
    @Autowired
    private UserAccountService myUserDetailsService;
    
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException 

        final String requestTokenHeader = request.getHeader("Authorization");

        String username = null;
        String jwtToken = null;
        
        if (requestTokenHeader != null) 
            jwtToken = requestTokenHeader.substring(7);
            try 
                username = jwtTokenUtil.getUsernameFromToken(jwtToken);
             catch (IllegalArgumentException e) 
                System.out.println("Unable to get JWT Token");
             catch (ExpiredJwtException e) 
                System.out.println("JWT Token has expired");
            
         

        
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) 
      
            UserDetails userDetails = this.myUserDetailsService.loadUserByUsername(username);
        if (jwtTokenUtil.validateToken(jwtToken, userDetails)) 
            
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                
            String authorities = userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority)
                    .collect(Collectors.joining());
             System.out.println("Authorities granted : " + authorities);
                    
                usernamePasswordAuthenticationToken
                        .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            
            else 
                System.out.println("Not Valid Token");
            

        
        chain.doFilter(request, response);
    





如您所见,过滤器正在使用自定义 UserDetails 来验证用户名。如何将 Customer userdetails 服务添加到过滤器?这是我的第一个多重登录项目,请多多包涵。

【问题讨论】:

您介意提供您构建此过滤器所遵循的教程的链接吗?另外,您是否考虑过使用Resource Server support?它允许您在不编写自定义过滤器实现的情况下验证 JWT,并且内置在 Spring Security 中。 【参考方案1】:

登录时区分用户和客户。因此,调用不同的服务来获取用户详细信息。更多可以在这里找到。 Spring Security user authentication against customers and employee

【讨论】:

也通过这个turreta.com/2016/12/26/…【参考方案2】:

    如何将 Customer userdetails 服务添加到过滤器?:像使用 UserAccountService 一样注入它。如果你这样做,你正在使用 1 个过滤器(当然,这个过滤器在 1 个 SecurityFilterChain 中),你基本上可以实现你的过滤器,比如:尝试通过 myUserDetailsService 验证你的用户,如果它不成功,继续myCustomerDetailsService

    对于多登录项目。第二种方法是使用 2 SecurityFilterChain。例如,1 SecurityFilterChain 的 UserJwtFilter 和 1 SecurityFilterChain 的 CustomJwtFilter。人们通常对不同的登录机制 Basic、OAuth2、SAML2 这样做。例如:

基本身份验证:
@Configuration
@Order(2)
public class BasicAuthenticationFilterChain extends WebSecurityConfigurerAdapter 

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http
                .requestMatchers()
                    .antMatchers("/login", "/logout")
                .and()
OAuth2 身份验证:
@Configuration
@Order(3)
public class OAuth2AuthenticationFilterChain extends WebSecurityConfigurerAdapter 

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http
                .requestMatchers()
                    .antMatchers("/oauth")
                .and()

在这种情况下,带有“/login”的请求将被定向到BasicAuthenticationFilterChain,而带有“/oauth”的请求将被定向到OAuth2AuthenticationFilterChain。关于Order:越低优先级越高,一旦使用SecurityFilterChain 处理请求,它就不会转到另一个SecurityFilterChain。您可以通过这种方式实施您的项目。

结论:您可以通过多种方式使用 Spring Security 实现您的想法,这取决于您的选择。

【讨论】:

【参考方案3】:

在我看来你已经做到了。

@Autowired
private UserAccountService myUserDetailsService;

但我建议使用构造函数而不是 @Autowired。 Spring 将同样填写构造函数参数。当您也使用 lombok 库时,这可能会非常小。 使用构造函数还可以更轻松地进行模拟测试。


如 cmets 中所述已更新:


@Log //another lombok thing
@RequiredArgsConstructor
@Component
public class JwtRequestFilter extends Filter
    
    private final JwtTokenUtil jwtTokenUtil;
    
    private final UserAccountService myUserDetailsService;
    
    private final CustomerAccountService myCustomerDetailsService;

    private static final String AUTH_HEADER = "authorization";
    
    @Override
    protected void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException 

        String tokenHeader = ((HttpServletRequest) request).getHeader(AUTH_HEADER);
        if(hasValue(tokenHeader) && tokenHeader.toLowerCase().startsWith("bearer "))
            jwtToken = requestTokenHeader.substring(7);

            String username;
            String jwtToken;
 
            try 
                username = jwtTokenUtil.getUsernameFromToken(jwtToken);
        
                if (uSecurityContextHolder.getContext().getAuthentication() == null) 

                    UserDetails userDetails = myUserDetailsService.loadUserByUsername(username);
                    if(isNull(userDetails))
                        userDetails = myCustomerDetailsService.loadCustomerByUsername(username);
                    
                    if (jwtTokenUtil.validateToken(jwtToken, userDetails)) 
                        var token = createSecurityToken(userDetails);
                        SecurityContextHolder.getContext().setAuthentication(token);
                     else 
                        throw new RuntimeException("Not a Valid Token.");
                    
                 else 
                    log.info("Authorization already present");
                
             catch (IllegalArgumentException e) 
                throw new("Unable to get JWT Token",e);
             catch (ExpiredJwtException e) 
                throw new("JWT Token has expired",e);
            
         else 
            throw new RuntimeException("No valid authorization header found.");
        
        chain.doFilter(request, response);
    

    private UsernamePasswordAuthenticationToken createSecurityToken(UserDetails userDetails)
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
        userDetails, null, userDetails.getAuthorities());
        log.info("Authorities granted : ", userDetails.getAuthorities());
        token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        return token;
    

【讨论】:

非常感谢@3Fish,自动装配的 UserAccountService 类适用于 Users 。现在我也有一个 CustomerAccountService 用于客户登录。如何使这个单一的过滤器类验证这两个服务。 您可以像使用 myUserDetailsS​​ervice 一样将其服务添加为另一个注入的依赖项,或者添加另一个执行相同操作的过滤器类。第二种方法可能有更多的代码,但如果两种情况是不同的关注点,它们也应该单独实现。这将使您的课程简短且可维护。您可以使用 @Order() 注释定义所有过滤器的执行顺序。此外,您可能希望将chain.doFilter(request, response); 移至“有效”部分,否则无效请求也会被进一步处理。

以上是关于我需要一个单独的 JWT 过滤器来进行多次登录吗?的主要内容,如果未能解决你的问题,请参考以下文章

我需要为每个用户单独管理 jwt 密钥吗?

如果使用 JWT,我需要 CSRF 吗?

微服务 - 如何使用 JWT 对单独的 API 微服务进行身份验证

如何为 JWT 生成和访问密钥

带有 JWT 的 Spring Boot 和 Apache Shiro - 我使用正确吗?

每次使用 JWT 登录时允许一个并发用户