JwtUsernameAndPasswordAuthenticationFilter在Spring Security配置示例中的工作机制及相关技术疑问
Hey there! Let's break down your questions about these Spring Security filters clearly, using your provided configuration and code as context.
First, let's recap your setup to make sure we're on the same page: you've added JwtUsernameAndPasswordAuthenticationFilter (handles login and token issuance) before JwtTokenVerifier (validates tokens on subsequent requests), with a stateless session policy.
1. Does JwtUsernameAndPasswordAuthenticationFilter check for username/password on every request? When does it trigger?
Great question! The short answer is no—it only runs its username/password validation logic for specific requests. Here's why:
Your filter extends UsernamePasswordAuthenticationFilter, which by default is mapped to handle POST requests to the /login endpoint (this is the default path defined in the parent class; you could override it with setFilterProcessesUrl() if you wanted a different login path).
Looking at your attemptAuthentication() method: it reads the request body (expecting a JSON payload with username/password). This method will only execute when the incoming request matches the filter's target path (and HTTP method). For all other requests (like your /api/** endpoints), this filter will pass the request along the filter chain without running the username/password check.
To put it simply:
- Trigger condition: A
POSTrequest to the default/loginendpoint (or your custom path if you set one) - All other requests skip this filter's authentication logic entirely
2. Why create a password-less Authentication object in JwtTokenVerifier and put it in SecurityContext?
This is all about how Spring Security tracks authenticated users and what happens after JWT validation:
When a user logs in successfully (via the login filter), you issue a JWT that contains their username and authorities. On subsequent requests, the JwtTokenVerifier's job is to validate that the token is legitimate, then restore the user's authenticated identity so Spring Security knows who's making the request.
Here's the breakdown of why we use a password-less token:
- We don't need the password anymore: The JWT was already issued after successful password validation during login. Once the token is verified (signature is valid, hasn't expired, etc.), we trust the claims inside it (username and authorities) — there's no need to re-check the password against the database.
- Mark the user as authenticated: The
UsernamePasswordAuthenticationTokenconstructor you're using (new UsernamePasswordAuthenticationToken(username, null, simpleGrantedAuthorities)) creates an instance that's marked as authenticated. When you put this intoSecurityContextHolder.getContext().setAuthentication(authentication), Spring Security's downstream filters and authorization logic will recognize the user as valid and check their permissions against the request's required roles. - Security best practice: Including the password in the
Authenticationobject here would be unnecessary and a potential security risk—we don't want to expose or carry sensitive credentials around after the initial login.
Your Configuration & Filter Code for Reference
Security Config
@Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(), jwtConfig, secretKey)) .addFilterAfter(new JwtTokenVerifier(secretKey, jwtConfig),JwtUsernameAndPasswordAuthenticationFilter.class) .authorizeRequests() .antMatchers("/", "index", "/css/*", "/js/*").permitAll() .antMatchers("/api/**").hasRole(STUDENT.name()) .anyRequest() .authenticated(); }
JwtUsernameAndPasswordAuthenticationFilter
public class JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final AuthenticationManager authenticationManager; private final JwtConfig jwtConfig; private final SecretKey secretKey; public JwtUsernameAndPasswordAuthenticationFilter(AuthenticationManager authenticationManager, JwtConfig jwtConfig, SecretKey secretKey) { this.authenticationManager = authenticationManager; this.jwtConfig = jwtConfig; this.secretKey = secretKey; } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { try { UsernameAndPasswordAuthenticationRequest authenticationRequest = new ObjectMapper() .readValue(request.getInputStream(), UsernameAndPasswordAuthenticationRequest.class); Authentication authentication = new UsernamePasswordAuthenticationToken( authenticationRequest.getUsername(), authenticationRequest.getPassword() ); Authentication authenticate = authenticationManager.authenticate(authentication); return authenticate; } catch (IOException e) { throw new RuntimeException(e); } } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { String token = Jwts.builder() .setSubject(authResult.getName()) .claim("authorities", authResult.getAuthorities()) .setIssuedAt(new Date()) .setExpiration(java.sql.Date.valueOf(LocalDate.now().plusDays(jwtConfig.getTokenExpirationAfterDays()))) .signWith(secretKey) .compact(); response.addHeader(jwtConfig.getAuthorizationHeader(), jwtConfig.getTokenPrefix() + token); } }
JwtTokenVerifier
public class JwtTokenVerifier extends OncePerRequestFilter { private final SecretKey secretKey; private final JwtConfig jwtConfig; public JwtTokenVerifier(SecretKey secretKey, JwtConfig jwtConfig) { this.secretKey = secretKey; this.jwtConfig = jwtConfig; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authorizationHeader = request.getHeader(jwtConfig.getAuthorizationHeader()); if (Strings.isNullOrEmpty(authorizationHeader) || !authorizationHeader.startsWith(jwtConfig.getTokenPrefix())) { filterChain.doFilter(request, response); return; } String token = authorizationHeader.replace(jwtConfig.getTokenPrefix(), ""); try { Jws<Claims> claimsJws = Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token); Claims body = claimsJws.getBody(); String username = body.getSubject(); var authorities = (List<Map<String, String>>) body.get("authorities"); Set<SimpleGrantedAuthority> simpleGrantedAuthorities = authorities.stream() .map(m -> new SimpleGrantedAuthority(m.get("authority"))) .collect(Collectors.toSet()); Authentication authentication = new UsernamePasswordAuthenticationToken( username, null, simpleGrantedAuthorities ); SecurityContextHolder.getContext().setAuthentication(authentication); } catch (JwtException e) { throw new IllegalStateException(String.format("Token %s cannot be trusted", token)); } filterChain.doFilter(request, response); } }
内容的提问来源于stack exchange,提问作者user15599360

