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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 06:05:56