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

Angular与Symfony项目中,为何要采用令牌系统做身份认证?

Angular + Symfony JWT 身份认证方案梳理与实现建议

嘿,你这套Angular前端搭配Symfony后端的JWT认证思路很靠谱!我结合你提到的Bundle和认证流程,把关键实现细节和注意事项整理出来,帮你把这个体系落地得更顺畅:

项目核心架构与认证流程

你当前的认证流程逻辑是完全合理的,梳理下来是这样:

  • 前端提交用户名+密码到后端登录接口
  • 后端生成访问令牌(Access Token)和刷新令牌(Refresh Token)
  • 前端通过localStorage存储这两个令牌
  • 每次发起业务请求前,前端检查Access Token是否过期:
    • 未过期:直接携带令牌请求后端
    • 已过期:调用刷新令牌接口获取新的Access Token,再发起请求

已引入的关键Symfony Bundle

你提到的两个Bundle是JWT认证体系的核心:

  • LexikJWTAuthenticationBundle:负责JWT令牌的生成、签名、验证全流程,是整个认证体系的基础
  • JWTRefreshTokenBundle(应该是指gesdinet/jwt-refresh-token-bundle):专门处理刷新令牌的持久化、校验和刷新逻辑,解决Access Token过期后无需重复登录的痛点

后端Symfony配置与实现要点

1. 基础配置

确保在config/packages/lexik_jwt_authentication.yaml中正确配置密钥和令牌有效期:

lexik_jwt_authentication:
    secret_key: '%env(resolve:JWT_SECRET_KEY)%'
    public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
    pass_phrase: '%env(JWT_PASSPHRASE)%'
    token_ttl: 3600 # Access Token建议设为1小时,安全性更高

配置刷新令牌的有效期和关联字段(config/packages/gesdinet_jwt_refresh_token.yaml):

gesdinet_jwt_refresh_token:
    token_parameter_name: refresh_token
    ttl: 2592000 # 刷新令牌可以设为30天,平衡安全性和用户体验
    user_identity_field: username # 关联用户的唯一标识字段

2. 路由配置

在config/routes.yaml中添加登录和刷新令牌的接口路由:

api_login_check:
    path: /api/login_check
    methods: [POST]

api_refresh_token:
    path: /api/token/refresh
    methods: [POST]

3. 额外优化建议

  • 可以在用户注销时,调用接口删除数据库中对应的刷新令牌,防止令牌被滥用
  • 考虑给刷新令牌添加唯一标识,每次刷新时生成新的刷新令牌并作废旧的,提升安全性

前端Angular实现要点

1. 封装认证服务(AuthService)

把令牌的存储、获取、过期检查逻辑封装成一个全局服务:

import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class AuthService {
    private readonly accessTokenKey = 'access_token';
    private readonly refreshTokenKey = 'refresh_token';

    getAccessToken(): string | null {
        return localStorage.getItem(this.accessTokenKey);
    }

    getRefreshToken(): string | null {
        return localStorage.getItem(this.refreshTokenKey);
    }

    setTokens(accessToken: string, refreshToken: string): void {
        localStorage.setItem(this.accessTokenKey, accessToken);
        localStorage.setItem(this.refreshTokenKey, refreshToken);
    }

    isTokenExpired(token: string): boolean {
        const payload = JSON.parse(atob(token.split('.')[1]));
        const expirationTimestamp = payload.exp * 1000;
        return Date.now() > expirationTimestamp;
    }

    logout(): void {
        localStorage.removeItem(this.accessTokenKey);
        localStorage.removeItem(this.refreshTokenKey);
        // 这里可以添加跳转到登录页的逻辑,比如this.router.navigate(['/login'])
    }
}

2. 实现HTTP拦截器自动处理令牌

用Angular的HttpInterceptor拦截所有请求,自动携带令牌并处理过期刷新:

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, switchMap } from 'rxjs/operators';
import { AuthService } from './auth.service';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
    constructor(private authService: AuthService, private http: HttpClient) {}

    intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
        const accessToken = this.authService.getAccessToken();
        
        if (!accessToken) {
            return next.handle(request);
        }

        // 检查Access Token是否过期
        if (this.authService.isTokenExpired(accessToken)) {
            // 调用刷新令牌接口
            return this.refreshToken().pipe(
                switchMap((newTokens: { access_token: string; refresh_token: string }) => {
                    this.authService.setTokens(newTokens.access_token, newTokens.refresh_token);
                    const updatedRequest = request.clone({
                        setHeaders: { Authorization: `Bearer ${newTokens.access_token}` }
                    });
                    return next.handle(updatedRequest);
                }),
                catchError(error => {
                    // 刷新失败,强制登出
                    this.authService.logout();
                    return throwError(() => new Error('令牌刷新失败,请重新登录'));
                })
            );
        }

        // 令牌有效,直接携带请求
        const updatedRequest = request.clone({
            setHeaders: { Authorization: `Bearer ${accessToken}` }
        });
        return next.handle(updatedRequest);
    }

    private refreshToken(): Observable<{ access_token: string; refresh_token: string }> {
        const refreshToken = this.authService.getRefreshToken();
        return this.http.post<{ access_token: string; refresh_token: string }>(
            '/api/token/refresh', 
            { refresh_token: refreshToken }
        );
    }
}

3. 登录组件处理登录请求

在登录组件中调用登录接口,存储返回的令牌:

import { Component } from '@angular/core';
import { AuthService } from '../auth.service';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';

@Component({
    selector: 'app-login',
    templateUrl: './login.component.html'
})
export class LoginComponent {
    username = '';
    password = '';
    loginError = '';

    constructor(
        private http: HttpClient, 
        private authService: AuthService,
        private router: Router
    ) {}

    onSubmit(): void {
        this.http.post<{ access_token: string; refresh_token: string }>(
            '/api/login_check', 
            { username: this.username, password: this.password }
        ).subscribe({
            next: tokens => {
                this.authService.setTokens(tokens.access_token, tokens.refresh_token);
                this.router.navigate(['/']); // 登录成功跳转到首页
            },
            error: () => {
                this.loginError = '用户名或密码错误';
            }
        });
    }
}

重要安全提示

  • localStorage存储令牌存在XSS攻击风险,如果你的项目对安全性要求极高,可以考虑将刷新令牌存储在HttpOnly Cookie中,配合前端处理跨域问题
  • 定期清理过期的刷新令牌,可以在Symfony中添加命令或者事件监听来自动删除数据库中过期的令牌
  • 避免在前端暴露敏感的令牌细节,比如不要在控制台打印令牌内容

内容的提问来源于stack exchange,提问作者SlimenTN

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 08:04:28