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

Angular/Express/Passport谷歌认证跨域问题及无状态JWT实现咨询

解决Angular + Express + Passport Google OAuth的CORS问题与无状态JWT认证流程

看起来你踩了一个常见的OAuth2认证误区——直接用AJAX请求调用Google OAuth的授权路由,这不仅会触发CORS问题,本身也不符合OAuth2的授权码流程逻辑。下面我会一步步帮你修正这个问题,实现无状态的Google认证+JWT方案:

一、为什么会出现CORS错误?

当你用Angular的http.get('http://localhost:3000/api/auth/google')发起请求时,浏览器会先发送一个OPTIONS预检请求到你的Express服务器,检查是否允许跨域请求。如果你的Express服务器没有配置CORS中间件,或者配置不正确,就会返回"No Access-Control-Allow-Origin"错误。但更关键的是:Google OAuth的授权流程需要重定向到Google的登录页面,AJAX请求无法处理浏览器的重定向跳转,所以这种调用方式从一开始就不对。

二、正确的实现步骤

1. Express端配置

首先安装必要的依赖:

npm install cors passport passport-google-oauth20 jsonwebtoken dotenv

然后配置CORS、Passport Google策略,同时处理回调生成JWT:

// server.js
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const jwt = require('jsonwebtoken');

const app = express();

// 配置CORS,允许你的Angular前端域名
app.use(cors({
  origin: 'http://localhost:4200',
  credentials: true 
}));

// 初始化Passport
app.use(passport.initialize());

// 配置Google OAuth策略
passport.use(new GoogleStrategy({
    clientID: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    callbackURL: 'http://localhost:3000/api/auth/google/callback' // 需与Google开发者控制台配置一致
  },
  (accessToken, refreshToken, profile, done) => {
    // 这里可根据profile信息创建/查找数据库中的用户
    const user = {
      id: profile.id,
      name: profile.displayName,
      email: profile.emails[0].value
    };
    done(null, user);
  }
));

// 1. 触发Google登录的路由(供前端跳转,而非AJAX调用)
app.get('/api/auth/google',
  passport.authenticate('google', { scope: ['profile', 'email'] })
);

// 2. Google回调路由,处理授权码并生成JWT
app.get('/api/auth/google/callback',
  passport.authenticate('google', { session: false }), // 禁用session,保持无状态
  (req, res) => {
    // 生成JWT令牌
    const token = jwt.sign(req.user, process.env.JWT_SECRET, { expiresIn: '1h' });
    // 重定向回Angular前端,携带JWT参数
    res.redirect(`http://localhost:4200/auth/callback?token=${token}`);
  }
);

// 示例受保护API路由,验证JWT
app.get('/api/protected', (req, res) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).send('Unauthorized');
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    res.json({ message: `Welcome ${decoded.name}`, user: decoded });
  } catch (err) {
    res.status(401).send('Invalid token');
  }
});

app.listen(3000, () => console.log('Server running on port 3000'));

2. Angular端配置

修改登录服务,改用页面跳转触发Google认证:

// auth.service.ts
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class AuthService {
  login() {
    // 跳转到Express的Google认证路由,让浏览器处理重定向
    window.location.href = 'http://localhost:3000/api/auth/google';
  }

  // 从URL参数中提取并存储JWT
  handleCallback() {
    const urlParams = new URLSearchParams(window.location.search);
    const token = urlParams.get('token');
    if (token) {
      localStorage.setItem('authToken', token);
    }
  }

  // 获取存储的令牌
  getToken(): string | null {
    return localStorage.getItem('authToken');
  }
}

创建回调组件处理Google返回的JWT:

// auth-callback.component.ts
import { Component, OnInit } from '@angular/core';
import { AuthService } from './auth.service';
import { Router } from '@angular/router';

@Component({
  template: '<p>Processing login...</p>'
})
export class AuthCallbackComponent implements OnInit {
  constructor(private authService: AuthService, private router: Router) {}

  ngOnInit() {
    this.authService.handleCallback();
    this.router.navigate(['/']); // 处理完成后跳转到首页
  }
}

在路由中添加回调路由:

// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthCallbackComponent } from './auth-callback.component';

const routes: Routes = [
  // 其他路由...
  { path: 'auth/callback', component: AuthCallbackComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

3. Google开发者控制台配置

确保你在Google开发者控制台中:

  • 添加http://localhost:3000/api/auth/google/callback作为授权重定向URI
  • 添加http://localhost:4200作为JavaScript来源

三、关键注意点

  • 不要用AJAX调用OAuth授权路由:OAuth2授权码流程依赖浏览器重定向,AJAX无法处理这个流程,这是问题的核心原因。
  • 保持无状态:禁用Passport的session,用JWT维持认证状态,后续请求API时在请求头携带Authorization: Bearer <token>即可。
  • JWT存储风险:示例用localStorage存储JWT,生产环境需注意XSS风险,也可考虑使用HttpOnly Cookie(跨域场景需额外配置CORS credentials)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 06:29:23