Angular 前端和 Spring Boot 后端之间的 JWT 身份验证问题

问题描述 投票:0回答:1

我目前正在开发一个 Angular 应用程序,该应用程序与 Spring Boot API 进行通信,以通过 JWT 进行身份验证和用户管理。但是,我在前端和后端使用 JWT 令牌处理身份验证时遇到了问题。

背景:

前端:使用 JwtInterceptor 的 Angular 在请求标头中添加 JWT 令牌。 后端:带有 Spring Security 的 Spring Boot,用于生成和验证 JWT 令牌。 我已经配置了部分代码来处理注册、登录、密码更改和帐户删除。这是我迄今为止所做的总结。

前端(角度)

JwtInterceptor:从 localStorage 检索令牌并将其添加到每个 HTTP 请求中。 AccountsService:管理注册、登录和用户管理的 API 调用。令牌存储在 localStorage 中。 JwtInterceptor.ts代码:


    @Injectable()
    export class JwtInterceptor implements HttpInterceptor {

      constructor(private accountsService: AccountsService) { }

      intercept(req: HttpRequest<any>, next: HttpHandler) {
        const token = this.accountsService.getToken();
        if (token) {
          console.log('JWT Token:', token);
          const cloned = req.clone({
             headers: req.headers.set('Authorization', `Bearer ${token}`)
          });
          return next.handle(cloned);
        }
        return next.handle(req);
      }
   }

账户.service.ts

  export class AccountsService {
  private apiUrl = 'http://localhost:8080';

  constructor(private http: HttpClient) { }

  register(user: { username: string; password: string }): Observable<string> {
    return this.http.post<string>(`${this.apiUrl}/auth/register`, user, { responseType: 'text' as 'json' })
      .pipe(
        tap(response => console.log('User registered:', response)),
        catchError(this.handleError)
      );
  }

  login(credentials: { username: string; password: string }): Observable<string> {
    return this.http.post<string>(`${this.apiUrl}/auth/login`, credentials, { responseType: 'text' as 'json' })
      .pipe(
        tap(response => console.log('Login successful:', response)),
        catchError(this.handleError)
      );
  }

  saveToken(token: string) {
    localStorage.setItem('jwtToken', token);
  }

  getToken() {
    return localStorage.getItem('jwtToken');
  }

  logout() {
    localStorage.removeItem('jwtToken');
    console.log('User logged out, token removed');
  }
  }

  @Configuration
  @EnableWebSecurity
  public class SecurityConfig {

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .cors().and()  // Adding CORS support
            .authorizeRequests()
                .requestMatchers("/submit", "/auth/register", "/auth/login").permitAll()  // Public routes
                .anyRequest().authenticated()  // Authentication required for other routes
            .and()
            .addFilterBefore(jwtRequestFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    public JwtRequestFilter jwtRequestFilter() {
        return new JwtRequestFilter();
    }
  }

  @Component
  public class JwtTokenUtil {

    private static final String SECRET_KEY = "mysecret";

    public String generateToken(String username) {
        Map<String, Object> claims = new HashMap<>();
        return Jwts.builder()
            .setClaims(claims)
            .setSubject(username)
            .setIssuedAt(new Date(System.currentTimeMillis()))
            .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 hours
            .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
            .compact();
    }

    public boolean validateToken(String token, String username) {
        final String tokenUsername = extractUsername(token);
        return (tokenUsername.equals(username) && !isTokenExpired(token));
    }

    public String extractUsername(String token) {
        return extractAllClaims(token).getSubject();
    }

    private Claims extractAllClaims(String token) {
        return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
    }

    private boolean isTokenExpired(String token) {
        return extractAllClaims(token).getExpiration().before(new Date());
    }
  }

  public class JwtRequestFilter extends OncePerRequestFilter {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        final String authorizationHeader = request.getHeader("Authorization");

        String username = null;
        String jwt = null;

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7);
            username = jwtTokenUtil.extractUsername(jwt);
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                    username, null, new ArrayList<>());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        chain.doFilter(request, response);
    }
  }

问题:

JWT 令牌已正确生成并在登录期间发送到前端。但是,当我使用授权标头中的令牌从 Angular 发送经过身份验证的请求时,后端似乎没有正确验证令牌。受保护的请求返回 401(未经授权)错误。

我检查过的内容:

令牌由 Angular 拦截器正确存储和发送。 JwtRequestFilter 拦截请求,但身份验证失败。 我检查了 Spring Security 配置,受保护的路由已正确指定。 我在寻找什么:

有没有人遇到过类似的问题,并且可以提供有关在 Spring Boot 中进行 JWT 令牌验证期间解决此 401 错误的见解?问题可能与 Spring Security 配置、令牌签名或 Angular 发送请求的方式有关吗?

预先感谢您的帮助!

angular spring-security
1个回答
0
投票

在 Spring Security 6 中,需要显式保存安全上下文。

就好像有如下配置:

http
    // ...
    .securityContext(securityContext -> securityContext.requireExplicitSave(true));

要保留安全上下文,首先公开

SecurityContextRepository
bean。

@Bean
public SecurityContextRepository securityContextRepository() {
    return new DelegatingSecurityContextRepository(
        new RequestAttributeSecurityContextRepository(),
        new HttpSessionSecurityContextRepository()
    ); // expose the default one
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        // other configuration...
        .securityContext(securityContext -> securityContext
            .securityContextRepository(securityContextRepository())
        );
    return http.build();
}

然后,在需要更新安全上下文时自动装配它。

public classJwtRequestFilter extends OncePerRequestFilter {
    @Autowired
    private SecurityContextRepository securityContextRepository;
    // ...
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        // ...
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        securityContextRepository.saveContext(securityContext, request, response);
        // ...
    }
}
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.