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

Spring Security结合Angular登录页面无法正常工作问题排查

Spring+Angular登录功能异常问题解决

问题概述

开发Spring+Angular应用,采用Spring Security实现登录功能。Postman发送POST请求到localhost:8080/auth,携带form-data格式的username和password可正常登录,但Angular自定义登录页面请求时出现以下问题:

  • POST请求返回302状态码,Spring返回login?error页面并提示"Invalid credentials"
  • GET请求返回404,Postman发送GET请求也得到相同结果
  • Postman认证成功后可正常发送其他请求,但Angular中认证后其他请求仍返回302并跳转到Spring Security登录页

Angular登录代码

loginUser(username: string | undefined, password: string | undefined) {
    const headers = new HttpHeaders(username ? {
      authorization : 'Basic ' + btoa(username + ':' + password)
    } : {});
    console.log(username)
    console.log(password)
    this.http.post(this.uri + "/auth", {headers}).subscribe(response => {
        console.log(response);
    });
}

Spring WebSecurityConfig配置

@Configuration
@AllArgsConstructor
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    private final AppUserService userService;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues())
                .and()
                .csrf().disable() //to delete
                .authorizeRequests()
                .antMatchers("/v*/registration/**", "/login", "/auth")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/auth")
                .usernameParameter("username")
                .passwordParameter("password")
                .permitAll()
                .and().logout().permitAll().and()
                .httpBasic()
        ;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(daoAuthenticationProvider());
    }

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider(){
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setPasswordEncoder(bCryptPasswordEncoder);
        provider.setUserDetailsService(userService);
        return provider;

    }
}

问题根源

  1. Angular请求格式错误:
    Spring Security的formLogin配置预期接收form-data/x-www-form-urlencoded格式的username和password参数,但你的代码使用了Basic Auth请求头,且POST请求体错误地传递了{headers}对象,未将用户名密码正确发送给后端。
  2. Spring配置冲突与重定向问题:
    同时开启formLogin和httpBasic导致认证逻辑冲突;formLogin默认在认证失败或未认证时重定向到登录页面(302状态码),不符合前后端分离场景,Angular无法处理该重定向,导致后续请求仍处于未认证状态。

解决方案

1. 修改Angular登录代码,发送正确表单请求

调整请求格式,发送x-www-form-urlencoded或form-data格式的表单数据,匹配Spring Security的formLogin预期:

import { HttpHeaders, HttpClient } from '@angular/common/http';

loginUser(username: string | undefined, password: string | undefined) {
  if (!username || !password) return;

  // 构造form-data格式数据
  const formData = new FormData();
  formData.append('username', username);
  formData.append('password', password);

  this.http.post(this.uri + "/auth", formData).subscribe({
    next: (response) => {
      console.log('登录成功', response);
      // 可在此保存认证状态至localStorage等
    },
    error: (err) => {
      console.error('登录失败', err);
    }
  });
}

2. 优化Spring Security配置,适配前后端分离

修改配置,关闭重定向逻辑,改为返回JSON响应,同时明确认证规则:

import org.springframework.http.HttpStatus;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
@AllArgsConstructor
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    private final AppUserService userService;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(corsFilter(), SessionManagementFilter.class)
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/v*/registration/**", "/auth")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/auth")
                .usernameParameter("username")
                .passwordParameter("password")
                // 登录成功返回JSON
                .successHandler((request, response, authentication) -> {
                    response.setContentType("application/json;charset=UTF-8");
                    response.getWriter().write("{\"status\":\"success\",\"message\":\"登录成功\"}");
                })
                // 登录失败返回JSON
                .failureHandler((request, response, exception) -> {
                    response.setContentType("application/json;charset=UTF-8");
                    response.setStatus(HttpStatus.UNAUTHORIZED.value());
                    response.getWriter().write("{\"status\":\"error\",\"message\":\"无效的用户名或密码\"}");
                })
                .permitAll()
                .and()
                .logout()
                .logoutSuccessHandler((request, response, authentication) -> {
                    response.setContentType("application/json;charset=UTF-8");
                    response.getWriter().write("{\"status\":\"success\",\"message\":\"退出成功\"}");
                })
                .permitAll()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(daoAuthenticationProvider());
    }

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider(){
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setPasswordEncoder(bCryptPasswordEncoder);
        provider.setUserDetailsService(userService);
        return provider;
    }

    // 精细配置CORS,替代默认规则
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOriginPattern("*"); // 生产环境建议指定具体域名
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

3. 确保后续请求携带认证信息

登录成功后,Spring Security会创建会话,Angular后续请求需开启withCredentials以携带会话Cookie:

// 全局配置HttpClient拦截器,自动添加withCredentials
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const authReq = req.clone({
      withCredentials: true
    });
    return next.handle(authReq);
  }
}

// 在app.module.ts的providers中注册拦截器
{
  provide: HTTP_INTERCEPTORS,
  useClass: AuthInterceptor,
  multi: true
}

内容的提问来源于stack exchange,提问作者nika932

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 01:30:47