在 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?