Node、Express、Oauth2 和 Passport 的 Angularjs CORS 问题

Posted

技术标签:

【中文标题】Node、Express、Oauth2 和 Passport 的 Angularjs CORS 问题【英文标题】:Angularjs CORS trouble with Node, Express, Oauth2, and Passport 【发布时间】:2016-08-06 15:54:13 【问题描述】:

最近我们决定将前端从 EJS 切换到 Angular,将前端和后端完全分开。在这样做的过程中,我们开始在多个浏览器中遇到几个问题。在后端,我们使用带有 express 的 Node 以及护照和 oauth2。对于前端,我们尝试使用 angular。 EJS 使用 express.render 工作,但我们更喜欢直接使用 angular,将 express 用作 RESTful API。

尊敬的,我在 localhost:8080 和 localhost:3000 本地运行后端和前端。当只使用后端(使用 EJS,而不是 ANGULAR)时,我可以成功地在浏览器中转到我们的后端端口,通过 passport-oauth 登录,然后重定向到我的 json 数据所在的帐户页面(从提供商登录屏幕)通过 res.json 呈现。问题是删除 EJS 后我无法从前端 UI 执行此操作。

我在使用三种不同的浏览器时尝试了十几种不同的方式来配置 CORS,但都没有成功。以下三个 sn-ps 是我在尝试通过 $http 和 $resource 从前端访问 localhost:8080 时在浏览器控制台中遇到的错误(代码见下文)。三个代码 sn-ps 下面的图像是节点控制台在尝试从每个不同的浏览器访问端口 8080 时告诉我的...

铬:

XMLHttpRequest cannot load 'PROVIDER-DETAILS-URL'. No 'Access-Control-Allow-    Origin' header is present on the requested resource. Origin 'null' is therefore     not allowed access.

火狐:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at 'PROVIDER-DETAILS-URL'. (Reason: CORS header 'Access-Control-Allow-Origin' missing).
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at 'PROVIDER-DETAILS-URL'. (Reason: CORS request failed).

Safari:

XMLHttpRequest cannot load http://localhost:8080/auth/PROVIDER. Request header field Accept-Encoding is not allowed by Access-Control-Allow-Headers.

控制台图片:

还有代码:

服务器:

app.js

'use strict';

const express           = require('express');
const session           = require('express-session');
const cookieParser      = require('cookie-parser');
const bodyParser        = require('body-parser');
const logger            = require('morgan');
const errorHandler      = require('errorhandler');
const path              = require('path');
const flash             = require('connect-flash');
const passport          = require('passport');
const expressValidator  = require('express-validator');

/**
 * Load environment variables, where API keys and passwords are configured.
 */
const config = require('./config/config');

/**
 * Route Handlers
 */
const index   = require('./routes/index');
const account = require('./routes/account');
const logout  = require('./routes/logout');

/**
 * API keys and Passport configuration.
 */
const passportConfig = require('./strategy');

/**
 * Create Express server.
 */
const app = express();

/**
 * Express configuration.
 */
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded( extended: true ));
app.use(function(req, res, next) 
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
    res.header("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT");
    next();
);
app.use(cookieParser());
app.use(expressValidator());
app.use(session(
    resave              : true,
    saveUninitialized   : true,
    secret              : config.sessionSecret,
));
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());

/**
 * Primary app routes.
 */
app.get('/', index.execute);
app.get('/account', passportConfig.isAuthenticated, account);
app.get('/logout', logout.execute);

/**
 * OAuth authorization routes.
 */
app.get('/auth/PROVIDER', passport.authenticate('PROVIDER'));
app.get('/auth/PROVIDER/callback', passport.authenticate('PROVIDER',  failureRedirect : '/'), function(req, res) 
    res.redirect('/account');
);

/**
 * Error Handler.
 */
app.use(errorHandler());

/**
 * Start Express server.
 */
app.listen(8080, () => 
    console.log('App listening on port 8080');
);

module.exports = app;

strategy.js

'use strict';

const passport        = require('passport');
const session         = require('express-session');
const config          = require('./config/config');
const OAuth2Strategy  = require('passport-oauth').OAuth2Strategy;

/**
 * Put together the right header info for PROVIDER
 */
 var authString      = new Buffer(config.PROVIDER.clientID + ':' + config.PROVIDER.clientSecret);
 var customHeader    = 
    "Authorization": "Basic " + authString.toString('base64')
;

/**
 * OAuth2Strategy containing the customHeader created above.
 */
 passport.use('PROVIDER', new OAuth2Strategy(
    authorizationURL    : config.PROVIDER.authorizationURL,
    tokenURL            : config.PROVIDER.tokenURL,
    clientID            : config.PROVIDER.clientID,
    clientSecret        : config.PROVIDER.clientSecret,
    callbackURL         : config.PROVIDER.callbackURL,
    customHeaders       : customHeader,
    passReqToCallback   : true
,
function( req, accessToken, refreshToken, profile, done ) 
    req.session.accessToken = accessToken;
    return done(null, profile); 

));

 passport.serializeUser(function(user, done) 
    return done(null, user);
);

 passport.deserializeUser(function(obj, done) 
    return done(null, obj);
);

/**
 * Login Required middleware.
 */
 exports.isAuthenticated = function(req, res, next) 
    if (req.isAuthenticated()) 
        console.log('isAuthenticated');
        return next();
    
    res.redirect('/');
;

/**
 * Authorization Required middleware.
 */
 exports.isAuthorized = function(req, res, next) 
    var provider = req.path.split('/').slice(-1)[0];
    if (_.find(req.user.tokens,  kind: provider )) 
        next();
     else 
        res.redirect('/auth/' + provider);
    
;

index.js

exports.execute = function (req, res) 
    if (req.user) 
        console.log('========== ROUTES/INDEX.JS | 3 ==========');
        res.redirect('/account');
     else 
        console.log('========== ROUTES/INDEX.JS | 6 ==========');
        res.redirect('/auth/PROVIDER');
    
;

客户:

我把它结合起来使它更容易阅读。

angular.module('***Post', [])

.factory('APIService', function() 
    function getData( $q, $http ) 
        var defer = $q.defer();
        $http.get( 'localhost:8080' )
            .success( getDataComplete )
            .catch( getDataFailed );

        function getDataComplete( response ) 
            console.log( response.Authorization );
            defer.resolve(response.data.results );
        

        function getDataFailed( error ) 
            console.log( error.data );
            defer.reject( 'XHR Failed for getData - ' + error.data );
        
        return defer.promise;
    
)

.controller('MainCtrl', function( APIService ) 
    var vm = this;

    vm.getDataTest = function() 
        APIService.getData().then(function( returnedData ) 
            console.log( returnedData );
        )
    
);

任何帮助或指导将不胜感激。

更新 (2016 年 4 月 28 日):我用更多细节更新了原始帖子。经过一周的反复试验,我还更新了代码。

【问题讨论】:

【参考方案1】:

请检查一下

https://gist.github.com/dirkk0/5967221

代码应该是

// in AngularJS (client)

myApp.config(['$httpProvider', function($httpProvider) 

    $httpProvider.defaults.useXDomain = true;
    delete $httpProvider.defaults.headers.common['X-Requested-With'];
]);

// in Express/nodeJS

// in NodeJS/Express (server)
app.all('/*', function(req, res, next) 
   res.header("Access-Control-Allow-Origin", "*");
   res.header("Access-Control-Allow-Headers", "X-Requested-With");
   res.header("Access-Control-Allow-Methods", "GET, POST","PUT");
   next();

);

【讨论】:

不幸的是,我在所有三种浏览器的控制台中仍然遇到相同的错误,但我很感激这次尝试。 @Matt 你有什么解决办法吗?

以上是关于Node、Express、Oauth2 和 Passport 的 Angularjs CORS 问题的主要内容,如果未能解决你的问题,请参考以下文章

Angular 问题和“没有 'Access-Control-Allow-Origin' 标头” - 使用 OAuth 2、Passport、Express 和 Node

ExpressJS Node.js Angular 错误:获取 OAuth2 API 授权令牌时出现 getaddrinfo ENOTFOUND

Discord OAuth2 冻结

每个循环都带有 express js 的节点

从 node.js 中的 API 数据中提取值

使用 Passport.js 的 Vue.js 客户端和 Express.js 服务器的完整 Oauth2 身份验证流程