如何用Angular自定义OAuth2登录页且保留原有授权会话流程?
自定义Angular登录页替换OAuth2授权服务器默认登录UI实操示例
核心目标
- 完全保留原有OAuth2授权会话流程,仅替换登录UI为Angular页面
- 确保账号密码提交后正常完成认证,无缝衔接后续授权跳转逻辑
步骤1:授权服务器端配置调整
1.1 配置SecurityFilterChain,指定Angular登录页并开启跨域
@Bean public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http // 未认证时跳转至Angular登录页 .exceptionHandling(exceptions -> exceptions .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("http://localhost:4200/login")) ) // 允许Angular域名的跨域请求,支持携带Cookie .cors(cors -> cors.configurationSource(corsConfigurationSource())) // 开放登录处理接口的访问权限 .authorizeHttpRequests(auth -> auth .requestMatchers("/login-process").permitAll() .anyRequest().authenticated() ); return http.build(); } // 跨域配置适配Angular前端 private CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "OPTIONS")); configuration.setAllowedHeaders(Arrays.asList("Content-Type", "Cookie")); configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; }
1.2 实现登录请求处理接口,维护会话关联
@RestController @RequestMapping("/login-process") public class CustomLoginController { private final AuthenticationManager authenticationManager; private final HttpSessionSecurityContextRepository securityContextRepository; public CustomLoginController(AuthenticationManager authenticationManager, HttpSessionSecurityContextRepository securityContextRepository) { this.authenticationManager = authenticationManager; this.securityContextRepository = securityContextRepository; } @PostMapping public ResponseEntity<Void> login(@RequestBody LoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()); try { Authentication authentication = authenticationManager.authenticate(authToken); // 将认证信息存入会话,保持原有流程的会话上下文 securityContextRepository.saveContext(new SecurityContextImpl(authentication), request, response); return ResponseEntity.ok().build(); } catch (AuthenticationException e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } } // 登录请求参数实体 public static class LoginRequest { private String username; private String password; // getter/setter public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } }
步骤2:Angular登录页实现
2.1 登录组件模板(login.component.html)
<div class="login-box"> <h3>系统登录</h3> <form (ngSubmit)="handleLogin()"> <div class="input-group"> <label>账号:</label> <input type="text" [(ngModel)]="username" name="username" required> </div> <div class="input-group"> <label>密码:</label> <input type="password" [(ngModel)]="password" name="password" required> </div> <button type="submit" class="login-btn">登录</button> <div *ngIf="isLoginFailed" class="error-tip">账号或密码错误,请重试</div> </form> </div>
2.2 登录组件逻辑(login.component.ts)
import { Component } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.css'] }) export class LoginComponent { username: string = ''; password: string = ''; isLoginFailed: boolean = false; private authQueryParams: string = ''; constructor(private http: HttpClient, private route: ActivatedRoute) { // 保存授权服务器跳转时携带的所有授权参数,后续回传 this.route.queryParams.subscribe(params => { this.authQueryParams = new URLSearchParams(params).toString(); }); } handleLogin() { const headers = new HttpHeaders({ 'Content-Type': 'application/json' }); // 开启withCredentials确保携带授权服务器的JSESSIONID Cookie this.http.post('http://auth-server:9000/login-process', { username: this.username, password: this.password }, { headers: headers, withCredentials: true } ).subscribe({ next: () => { // 登录成功后跳转回授权服务器的授权端点,携带原授权参数 window.location.href = `http://auth-server:9000/oauth2/authorize?${this.authQueryParams}`; }, error: () => { this.isLoginFailed = true; } }); } }
2.3 配置Angular路由(app-routing.module.ts)
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { LoginComponent } from './login/login.component'; const routes: Routes = [ { path: 'login', component: LoginComponent }, { path: '', redirectTo: '/login', pathMatch: 'full' } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
关键注意事项
- 会话Cookie保持:Angular请求必须设置
withCredentials: true,确保提交登录请求时携带授权服务器的JSESSIONID Cookie,认证成功后新Cookie能被正确保存 - 授权参数传递:必须完整保存授权服务器跳转至登录页时的所有查询参数(如
client_id、redirect_uri、state等),登录成功后原样回传,保证原有授权流程不中断 - 跨域配置:授权服务器必须允许Angular域名的跨域请求,且开启
AllowCredentials=true,否则Cookie无法正常传递
内容的提问来源于stack exchange,提问作者Nazhibolla Magzum
相关产品推荐
相关产品推荐

