You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 POST request to the default /login endpoint (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:

  1. 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.
  2. Mark the user as authenticated: The UsernamePasswordAuthenticationToken constructor you're using (new UsernamePasswordAuthenticationToken(username, null, simpleGrantedAuthorities)) creates an instance that's marked as authenticated. When you put this into SecurityContextHolder.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.
  3. Security best practice: Including the password in the Authentication object 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 23:47:29