在 UsernamePasswordAuthenticationFilter 或 Controller 中创建 JWT 令牌?

Posted

技术标签:

【中文标题】在 UsernamePasswordAuthenticationFilter 或 Controller 中创建 JWT 令牌?【英文标题】:Create JWT Token within UsernamePasswordAuthenticationFilter or Controller? 【发布时间】:2019-12-09 07:20:17 【问题描述】:

我尝试使用 Spring Security 生成 JWT 令牌,但我不知道如何正确执行(使用最佳实践)。

我是否应该在某处“拦截” UsernamePasswordAuthenticationFilter 中的身份验证方法并在其中生成令牌? 或者最好使用控制器'/login'中自动装配的AuthenticationManager?

如果我使用控制器机制,我害怕对用户进行两次身份验证。

我用过这个教程:tutorial Jwt Token

这是我的代码:

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter 

    @Autowired
    UserService userService;

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

//  @Autowired
//  private UserDetailsService jwtUserDetailsService;

    @Autowired
    private JwtTokenFilter jwtTokenFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http
            .httpBasic()
                .and()
            .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
            .cors().and()
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/css/**", "/login/**", "/register/**").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")      
                .anyRequest().authenticated()
                .and()
            //.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
            .formLogin()
                .usernameParameter("email")
                //.loginPage("http://localhost:4200/login").failureUrl("/login-error")  
                .and()
            .logout() 
                .permitAll();
        http
            .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
    

    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception 
        auth.authenticationProvider(authenticationProvider());
    

    @Bean
    public CustomDaoAuthenticationProvider authenticationProvider() 
        CustomDaoAuthenticationProvider authenticationProvider = new CustomDaoAuthenticationProvider();
        authenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
        authenticationProvider.setUserDetailsService(userService);
        return authenticationProvider;
    

    @Bean
    public WebMvcConfigurer corsConfigurer() 
        return new WebConfig() 
            @Override
            public void addCorsMappings(CorsRegistry registry) 
                registry.addMapping("/**")
                .allowedOrigins(
                        "http://localhost:4200")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS")
                .allowedHeaders("Content-Type", "X-Requested-With", "accept", "Origin", "Access-Control-Request-Method",
                                "Access-Control-Request-Headers", "Authorization", "Cache-Control",
                                "Access-Control-Allow-Origin")
                .exposedHeaders("Access-Control-Allow-Origin", "Access-Control-Allow-Credentials")
                .allowCredentials(true).maxAge(3600);
            
        ;
    

令牌过滤器

public class JwtTokenFilter extends GenericFilterBean 

    private JwtTokenProvider jwtTokenProvider;

    public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) 
        this.jwtTokenProvider = jwtTokenProvider;
    

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException 
        String token = jwtTokenProvider.resolveToken((HttpServletRequest) req);
        if (token != null && jwtTokenProvider.validateToken(token)) 
            Authentication auth = token != null ? jwtTokenProvider.getAuthentication(token) : null;
            SecurityContextHolder.getContext().setAuthentication(auth);
        
        filterChain.doFilter(req, res);
    

令牌提供者

@Component
public class JwtTokenProvider 

    @Value("$security.jwt.token.secret-key:secret")
    private String secretKey = "secret";

    @Value("$security.jwt.token.expire-length:3600000")
    private long validityInMilliseconds = 3600000; // 1h    

    @Autowired
    private UserDetailsService userDetailsService;

    @PostConstruct
    protected void init() 
        secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
    

    public String createToken(String username, List<String> roles) 

        Claims claims = Jwts.claims().setSubject(username);
        claims.put("roles", roles);
        Date now = new Date();
        Date validity = new Date(now.getTime() + validityInMilliseconds);
        return Jwts.builder()//
                    .setClaims(claims)//
                    .setIssuedAt(now)//
                    .setExpiration(validity)//
                    .signWith(SignatureAlgorithm.HS256, secretKey)//
                    .compact();
    

    public Authentication getAuthentication(String token) 
        UserDetails userDetails = this.userDetailsService.loadUserByUsername(getUsername(token));
        return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
    

    public String getUsername(String token) 
        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
    

    public String resolveToken(HttpServletRequest req) 
        String bearerToken = req.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) 
            return bearerToken.substring(7, bearerToken.length());
        
        return null;
    

    public boolean validateToken(String token) 
        try 
            Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
            if (claims.getBody().getExpiration().before(new Date())) 
                return false;
            
            return true;
         catch (JwtException | IllegalArgumentException e) 
            throw new InvalidJwtAuthenticationException("Expired or invalid JWT token");
        
    

【问题讨论】:

【参考方案1】:

根据您的上下文,控制器负责发布新令牌(在验证凭据之后),而过滤器负责根据给定令牌对用户进行身份验证。控制器不应填充安全上下文(验证用户),这是过滤器的责任。

为了更好地理解这两个阶段:

Spring 使用两个过滤器来验证和登录用户。 请参阅 Spring Security 项目中“用户名/密码”场景中的 UsernamePasswordAuthenticationFilter 和 SecurityContextPersistenceFilter:第一个处理身份验证尝试(用户名/密码),而后者从 SecurityContextRepository 填充安全上下文(来自一般会话)。

【讨论】:

我也更喜欢过滤器而不是控制器。是否可以将这两个过滤器与登录控制器上的发布请求一起使用?该请求由一个控制器管理,该控制器构建一个 Authentication 对象,该对象被发送到过滤器(authenticate() 然后 generateToken())。我会检查你的链接。 您不能从控制器调用过滤器(因为请求首先通过一系列过滤器,然后通过适当的控制器)。由于您需要在成功的身份验证请求上返回令牌,因此最好的选择仍然是使用负责发布新令牌的控制器(使用 AuthenticationManager)。然后,要根据给定令牌对用户进行身份验证,只需使用 JWTAuthenticationFilter

以上是关于在 UsernamePasswordAuthenticationFilter 或 Controller 中创建 JWT 令牌?的主要内容,如果未能解决你的问题,请参考以下文章

秋的潇洒在啥?在啥在啥?

上传的数据在云端的怎么查看,保存在啥位置?

在 React 应用程序中在哪里转换数据 - 在 Express 中还是在前端使用 React?

存储在 plist 中的数据在模拟器中有效,但在设备中无效

如何在保存在 Mongoose (ExpressJS) 之前在模型中格式化数据

如何在保存在 Mongoose (ExpressJS) 之前在模型中格式化数据