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

如何用Angular登录替代Spring Security OAuth2服务器的默认登录?

解耦Spring Security默认登录,用Angular实现自定义登录流程

一、Spring Security OAuth2授权服务器配置调整

1. 关闭默认登录页面,开放核心端点

通过配置SecurityFilterChain,关闭Spring Security自带的表单登录,同时放行OAuth2授权、令牌端点,并配置跨域允许Angular客户端访问:

@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
    http
        .cors(cors -> cors.configurationSource(corsConfigurationSource()))
        .formLogin(form -> form.disable()) // 禁用默认登录页
        .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
    return http.build();
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOrigins(List.of("http://localhost:4200")); // 替换为你的Angular域名
    config.setAllowedMethods(List.of("GET", "POST", "OPTIONS"));
    config.setAllowedHeaders(List.of("*"));
    config.setAllowCredentials(true);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);
    return source;
}

2. 自定义认证端点(处理用户名密码验证)

创建REST接口接收Angular的登录请求,完成用户名密码校验后生成授权码:

@RestController
@RequestMapping("/api/auth")
public class AuthController {

    private final AuthenticationManager authManager;
    private final OAuth2AuthorizationService authzService;
    private final RegisteredClientRepository clientRepo;

    public AuthController(AuthenticationManager authManager,
                          OAuth2AuthorizationService authzService,
                          RegisteredClientRepository clientRepo) {
        this.authManager = authManager;
        this.authzService = authzService;
        this.clientRepo = clientRepo;
    }

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest request) {
        // 1. 校验用户名密码
        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                request.getUsername(), request.getPassword());
        Authentication auth = authManager.authenticate(authToken);
        SecurityContextHolder.getContext().setAuthentication(auth);

        // 2. 获取客户端配置
        RegisteredClient client = clientRepo.findByClientId(request.getClientId());
        if (client == null) {
            return ResponseEntity.badRequest().body("无效客户端ID");
        }

        // 3. 构建授权请求并生成授权码
        OAuth2AuthorizationRequest authzRequest = OAuth2AuthorizationRequest.authorizationCode()
                .clientId(client.getClientId())
                .redirectUri(request.getRedirectUri())
                .scope(client.getScopes())
                .state(request.getState())
                .build();

        OAuth2Authorization authz = OAuth2Authorization.withRegisteredClient(client)
                .principalName(auth.getName())
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .attribute(OAuth2AuthorizationRequest.class.getName(), authzRequest)
                .build();
        authzService.save(authz);

        // 4. 返回授权码
        return ResponseEntity.ok(new AuthResponse(authzRequest.getAuthorizationCode().getValue()));
    }

    // 请求/响应实体类
    public static class LoginRequest {
        private String username;
        private String password;
        private String clientId;
        private String redirectUri;
        private String state;
        // Getter/Setter
    }

    public static class AuthResponse {
        private String code;
        public AuthResponse(String code) { this.code = code; }
        // Getter
    }
}

同时配置AuthenticationManager供认证使用:

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
    return config.getAuthenticationManager();
}

二、Angular客户端实现流程

1. 构建登录表单

创建基础登录组件,包含用户名、密码输入框:

<form (ngSubmit)="handleLogin()">
  <input type="text" [(ngModel)]="username" name="username" placeholder="用户名" required>
  <input type="password" [(ngModel)]="password" name="password" placeholder="密码" required>
  <button type="submit">登录</button>
</form>

2. 提交登录请求并换取令牌

在组件TS代码中,先发送请求获取授权码,再用授权码换取access_token:

import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent {
  username: string = '';
  password: string = '';
  private clientId = 'your-client-id'; // 与后端配置一致
  private redirectUri = 'http://localhost:4200/callback'; // 与后端配置一致
  private state = this.generateRandomState();

  constructor(private http: HttpClient) {}

  // 生成随机state防CSRF
  generateRandomState(): string {
    return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
  }

  handleLogin() {
    const loginPayload = {
      username: this.username,
      password: this.password,
      clientId: this.clientId,
      redirectUri: this.redirectUri,
      state: this.state
    };

    // 第一步:获取授权码
    this.http.post<any>('http://localhost:8080/api/auth/login', loginPayload)
      .subscribe(res => {
        // 第二步:用授权码换令牌
        this.exchangeCodeForToken(res.code);
      });
  }

  exchangeCodeForToken(code: string) {
    const tokenForm = new FormData();
    tokenForm.append('grant_type', 'authorization_code');
    tokenForm.append('code', code);
    tokenForm.append('redirect_uri', this.redirectUri);
    tokenForm.append('client_id', this.clientId);
    // 若客户端配置了密钥,需添加:tokenForm.append('client_secret', 'your-client-secret');

    this.http.post<any>('http://localhost:8080/oauth2/token', tokenForm)
      .subscribe(tokenRes => {
        // 存储令牌到本地(建议用HttpOnly Cookie更安全)
        localStorage.setItem('access_token', tokenRes.access_token);
        localStorage.setItem('refresh_token', tokenRes.refresh_token);
        // 跳转至应用首页
        window.location.href = '/home';
      });
  }
}

三、关键注意事项

  • 客户端配置一致性:Angular中使用的clientId、redirectUri必须与后端RegisteredClient中的配置完全匹配。
  • 安全防护:务必验证state参数防止CSRF攻击;令牌存储优先选择HttpOnly Cookie,避免XSS风险。
  • 后续请求处理:Angular可通过HTTP拦截器,在所有请求头中自动添加Authorization: Bearer {access_token},实现接口权限验证。

内容的提问来源于stack exchange,提问作者Carlos C.M

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 05:37:14