Spring Security+Angular7 LDAP认证登录功能实现求助
Angular 7 + Spring Boot + Spring Security LDAP 认证完整流程修复指南
看起来你已经搭好了基础框架,但因为前后端分离的适配问题,导致完整登录流程卡壳了。我帮你梳理下问题点,然后一步步修复整个流程:
一、后端Spring Boot配置修复
1. 适配SPA的REST登录(替换原WebSecurity配置)
你原来的formLogin是针对服务器端页面跳转的,而Angular是单页应用,需要返回JSON响应而非页面跳转。修改WebSecurityConfiguration的configure(HttpSecurity http)方法:
@Override protected void configure(HttpSecurity http) throws Exception { http .cors() // 开启跨域支持,解决Angular跨域问题 .and() .authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginProcessingUrl("/api/auth/login") // 前端POST请求的登录接口路径 .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(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("{\"status\":\"error\",\"message\":\"" + exception.getMessage() + "\"}"); }) .permitAll() // 允许登录接口匿名访问 .and() .logout() .logoutUrl("/api/auth/logout") .logoutSuccessHandler((request, response, authentication) -> { response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"status\":\"success\",\"message\":\"退出成功\"}"); }) .and() .csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); }
2. 修正LDAP ContextSource的笔误
你原来的contextSource Bean里的Base DN写错了,把cd=org改成dc=org,和application.properties保持一致:
@Bean public DefaultSpringSecurityContextSource contextSource() { return new DefaultSpringSecurityContextSource(Arrays.asList("ldap://localhost:8389/"), "dc=springframework,dc=org"); }
3. 添加CORS配置类
新建一个CORS配置类,允许Angular的域名跨域请求:
@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("http://localhost:4200"); // Angular默认运行地址 config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }
二、前端Angular 7改造
1. 完善登录组件逻辑(login.component.ts)
当前登录页面只有UI,需要添加请求发送和CSRF处理逻辑。首先安装ngx-cookie-service用来读取CSRF Token:
npm install ngx-cookie-service --save
然后编写组件代码:
import { Component, OnInit } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { CookieService } from 'ngx-cookie-service'; @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.css'] }) export class LoginComponent implements OnInit { username: string = ''; password: string = ''; loginStatus: string = ''; private loginApi = 'http://localhost:8082/api/auth/login'; constructor(private http: HttpClient, private cookieService: CookieService) { } ngOnInit(): void {} onLogin(): void { // 从Cookie中获取Spring Security生成的CSRF Token const xsrfToken = this.cookieService.get('XSRF-TOKEN'); const headers = new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded', 'X-XSRF-TOKEN': xsrfToken }); // 构造表单数据,参数名和后端配置一致 const formData = new FormData(); formData.append('_username_', this.username); formData.append('_password_', this.password); // 发送登录请求,withCredentials必须开启才能传递认证Cookie this.http.post(this.loginApi, formData, { headers, withCredentials: true }) .subscribe({ next: (res: any) => { this.loginStatus = res.message; // 登录成功后可以跳转到主页,或者通知AppComponent初始化WebSocket console.log('登录成功', res); }, error: (err) => { this.loginStatus = err.error.message || '登录失败,请检查账号密码'; console.error('登录失败', err); } }); } }
2. 绑定登录页面UI逻辑
修改login.component.html,把输入框和按钮绑定到组件方法:
<div id="container" fxLayout="column" fxLayoutAlign="start center"> <div class="spacer" fxFlex="10%"></div> <div id="login" fxFlex="25%" fxLayout="column" fxLayoutAlign="start center"> <h1 id="loginTitle" fxFlex="35%">LOGIN</h1> <input id="user" placeholder="username" fxFlex="17.5%" [(ngModel)]="username" (keydown.enter)="onLogin()" /> <div class="spacer" fxFlex="10%"></div> <input type="password" id="password" placeholder="password" fxFlex="17.5%" [(ngModel)]="password" (keydown.enter)="onLogin()" /> <div class="spacer" fxFlex="10%"></div> <img id="loginButton" src="../../assets/login/img/right-arrow.png" fxFlex="10%" alt="登录" (click)="onLogin()" style="cursor: pointer;" /> <div *ngIf="loginStatus" class="status-text" fxFlex="10%">{{ loginStatus }}</div> </div> <div id="spacer" fxFlex="74%"></div> <div id="attention" fxFlex="1%">Icons made by Lyolya</div> </div>
记得在app.module.ts中导入FormsModule和注册CookieService:
import { FormsModule } from '@angular/forms'; import { CookieService } from 'ngx-cookie-service'; @NgModule({ imports: [ // 其他模块... FormsModule ], providers: [CookieService], // 其他配置... }) export class AppModule { }
3. 调整AppComponent的WebSocket逻辑
原来的WebSocket初始化不要在构造函数里执行,应该在登录成功后再启动,避免未认证的连接失败:
import {Component, OnInit} from '@angular/core'; import {HttpClient} from '@angular/common/http'; import * as Stomp from 'stompjs'; import * as SockJS from 'sockjs-client'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { title = 'Memory'; status: string = '未登录'; private serverUrl = 'http://localhost:8082/'; private stompClient; constructor(private http: HttpClient) {} ngOnInit() {} initializeWebSocketConnection() { let ws = new SockJS(this.serverUrl); this.stompClient = Stomp.over(ws); const that = this; this.stompClient.connect({}, (frame) => { that.stompClient.subscribe('/chat', (message) => { console.log('收到消息:', message); }); that.status = '已登录'; setTimeout(() => that.sendMessage('测试消息'), 5000); }, (error) => { console.error('WebSocket连接失败:', error); that.status = 'WebSocket连接失败'; } ); } sendMessage(msg: string) { if (this.stompClient?.connected) { this.stompClient.send('/app/send/message', {}, msg); } else { console.error('请先登录'); } } // 供登录组件调用,登录成功后初始化WebSocket onLoginSuccess() { this.initializeWebSocketConnection(); } }
三、关键注意事项
- CSRF Token传递:后端配置了
CookieCsrfTokenRepository.withHttpOnlyFalse(),所以前端必须从Cookie读取XSRF-TOKEN,并在请求头中携带X-XSRF-TOKEN,否则Spring Security会拒绝请求。 - 跨域Credentials:前端请求必须设置
withCredentials: true,后端CORS配置要开启allowCredentials(true),这样才能传递认证Cookie。 - LDAP用户匹配:确保你的测试LDIF中用户DN符合
uid={0},ou=people格式,比如uid=ben,ou=people,dc=springframework,dc=org,登录时输入用户名ben即可。 - 密码一致性:后端用的
LdapShaPasswordEncoder要求LDAP中的userPassword是SHA加密后的字符串,比如密码password对应的SHA值是{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=。
四、测试流程
- 启动Spring Boot应用,确认嵌入式LDAP服务在8389端口正常运行。
- 启动Angular应用(
ng serve,默认4200端口)。 - 访问登录页面,输入LDAP测试账号密码(比如
ben/password,根据你的test-server.ldif配置)。 - 查看浏览器控制台和后端日志,确认登录请求返回成功JSON。
- 登录成功后,WebSocket会自动初始化,测试发送消息验证通信正常。
内容的提问来源于stack exchange,提问作者Andreas Rosian
相关产品推荐
相关产品推荐

