Angular+Java后端CORS跨域报错及登录功能故障求助
你好呀,看了你的问题描述和贴出来的代码,我先帮你理清楚核心问题:你用Spring Boot(带Spring Security)做后端、Angular做前端、MySQL存数据,跟着教程做的登录功能在Postman和直接访问后端地址时正常,但前端调用时就出问题,点登录也跳不到目标页面。下面针对你的情况给出几个排查和解决的方向:
一、先解决CORS跨域问题
结合前后端分离的场景,你遇到的报错大概率和CORS配置不全有关。虽然你在UserController上加了@CrossOrigin(origins = "http://localhost:4200"),但Spring Security的拦截器会在Controller之前处理请求,只加Controller的跨域注解根本拦不住预检请求,得在Spring Security里做全局CORS配置:
修改你的securityFilterChain方法,新增全局CORS配置:
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .cors(cors -> cors.configurationSource(corsConfigurationSource())) // 新增全局CORS配置 .csrf(csrf -> csrf.disable()) .authorizeRequests(authorizeRequests -> authorizeRequests .requestMatchers("/api/users", "/api/user/**","/api/authenticate").permitAll() .anyRequest().authenticated() ) .formLogin(formLogin -> formLogin .loginPage("/api/authenticate") .loginProcessingUrl("/api/authenticate") .defaultSuccessUrl("/api/users", true) .permitAll() ) .logout(logout -> logout .invalidateHttpSession(true) .clearAuthentication(true) .logoutRequestMatcher(new AntPathRequestMatcher("/api/logout")) .logoutSuccessUrl("/api/authenticate") .permitAll() ); return http.build(); } // 新增CORS配置源,专门处理跨域请求 @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200")); // 允许你的Angular前端地址 configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); // 放行所有常用请求方法 configuration.setAllowedHeaders(Arrays.asList("*")); // 允许所有请求头 configuration.setAllowCredentials(true); // 允许携带Cookie(登录会话必须开这个) UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); // 对所有后端接口生效 return source; }
前端发送登录请求前会先发OPTIONS预检请求,如果Spring Security没配置CORS,会直接拦截这个请求,导致跨域报错,这是前后端分离场景的常见坑。
二、检查前端登录请求的发送方式
你用的是Spring Security的表单登录,前端得符合它的要求:
- 登录请求必须是
POST方法,因为loginProcessingUrl只接收POST请求 - 表单字段名要和Spring Security默认的一致:用户名字段叫
username,密码字段叫password(如果你的User实体字段名不一样,得在Security里配置自定义参数名) - 请求格式得是
application/x-www-form-urlencoded,不能直接发JSON(Angular里可以用FormData或者设置请求头)
给你个Angular组件的登录示例参考:
login(username: string, password: string) { const formData = new FormData(); formData.append('username', username); formData.append('password', password); // 一定要加withCredentials: true,不然浏览器不会携带登录会话的Cookie this.http.post('http://localhost:8083/api/authenticate', formData, { withCredentials: true }) .subscribe(response => { // 登录成功后跳转到用户列表页 this.router.navigate(['/api/users']); }, error => { console.error('登录失败', error); }); }
三、调整Spring Security表单登录的配置细节
你的loginPage("/api/authenticate")是指向后端返回login视图的GET接口,但Angular是单页应用,根本不需要后端返回HTML视图啊!如果你的登录页面是Angular自己做的,这里的配置就得改:
- 如果你想继续用会话式登录,可以去掉
loginPage配置,只保留loginProcessingUrl来处理前端的登录请求:
.formLogin(formLogin -> formLogin .loginProcessingUrl("/api/authenticate") // 只保留处理登录请求的地址 .defaultSuccessUrl("/api/users", true) .failureUrl("/api/authenticate?error=true") // 登录失败的跳转地址 .permitAll() )
然后你的/api/authenticate GET接口可以改成返回登录状态,或者直接删掉,因为前端自己有登录页面。
四、再确认密码加密的问题
你说试过去掉密码加密但没用,这里要注意:Spring Security默认强制要求密码加密,如果你数据库里存的是明文密码,要么临时用测试用的加密器(仅测试,生产绝对不能用),要么正确配置BCrypt加密:
测试用配置(仅本地调试):
@Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); }
生产环境必须用BCrypt:
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
而且添加用户时,必须用这个加密器把密码加密后再存入数据库,不然登录时会提示密码不匹配。
你可以先按上面的步骤排查,优先解决CORS和前端请求格式的问题,应该能搞定你遇到的三个报错。
备注:内容来源于stack exchange,提问作者Roxana

