Oauth2节点Passport授权标头错误(匿名用户)

时间:2016-04-02 17:59:27

标签: node.js authentication oauth-2.0 passport.js

最近我们连接的Oauth 2服务器已升级。为了让我们遵守并获取新数据,我们需要对这台新服务器进行身份验证。

以前的服务器在我即将布局的代码上运行良好,但是在我们完全验证并获得一致错误的新服务器上。

这是错误

Client side error

app.js

所有护照魔法都发生在这里

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var config = require('./config.js')
var passport = require('passport');
var OAuth2Strategy = require('passport-oauth').OAuth2Strategy;
var session = require('express-session');
var async = require('async');

var index = require('./routes/index');
var users = require('./routes/users');
var patientdata = require('./routes/patient');
var account = require('./routes/account');
var logout = require('./routes/logout');

var about = require('./routes/about');


// serialize and deserialize
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(obj, done) {
done(null, obj);
});

// config

passport.use('vendor', new OAuth2Strategy({
    authorizationURL: config.vendor.authorizationURL,
    tokenURL: config.vendor.tokenURL,
    clientID: config.vendor.clientID,
    clientSecret: config.vendor.clientSecret,
    callbackURL: config.vendor.callbackURL,
    passReqToCallback: true
    },
    function(req, accessToken, refreshToken, profile, done) {
        process.nextTick(function () {
            // store access token
            req.session.accessToken=accessToken;
            return done(null, profile);
        });
    }
));

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({secret: 'secretword'}))
app.use(passport.initialize());
app.use(passport.session());

app.get('/', index.execute);

app.get('/users', users.execute);
app.get('/account', ensureAuthenticated, account.execute);

app.get('/logout', logout.execute);

// vendor 
app.get('/auth/vendor',
passport.authenticate('vendor'),
function(req, res){
});

app.get('/auth/vendor/callback',
passport.authenticate('vendor', { failureRedirect: '/' }),
function(req, res) {
 res.redirect('/account');
}); 

// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});

function ensureAuthenticated(req, res, next) {
    if (req.isAuthenticated()) {
        return next();
    }
    res.redirect('/')
}

module.exports = app;

URL,ID,机密等信息是正确的,供供应商验证。

我们似乎已对Oauth2服务器进行了身份验证,并且我们正在获取已批准的令牌,但我们会在其日志中显示为"匿名"用户。

以下是供应商方面的日志文件。

我的应用尝试进行身份验证的服务器日志

-- Us starting request for token
...
09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 3 of 11 in additional filter chain; firing Filter: 'HeaderWriterFilter'
09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.s.w.h.writers.HstsHeaderWriter - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@77bd892d
09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 4 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/oauth/token'; against '/logout'
09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 5 of 11 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 6 of 11 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 7 of 11 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 8 of 11 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@6faab5ec: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@ffffc434: RemoteIpAddress: 68.46.8.80; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'
Break -- returns anonymous

09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 9 of 11 in additional filter chain; firing Filter: 'SessionManagementFilter'
09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 10 of 11 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 11 of 11 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/oauth/token'; against '/oauth/token'
09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Secure object: FilterInvocation: URL: /oauth/token; Attributes: [fullyAuthenticated]
09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@6faab5ec: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@ffffc434: RemoteIpAddress: 68.46.8.80; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
09:43:47.974 [http-nio-8282-exec-2] DEBUG o.s.s.access.vote.AffirmativeBased - Voter: org.springframework.security.web.access.expression.WebExpressionVoter@73821302, returned: -1
09:43:47.975 [http-nio-8282-exec-2] DEBUG o.s.b.a.audit.listener.AuditListener - AuditEvent [timestamp=Thu Mar 24 09:43:47 CDT 2016, principal=anonymousUser, type=AUTHORIZATION_FAILURE, data={type=org.springframework.security.access.AccessDeniedException, message=Access is denied}]
09:43:47.975 [http-nio-8282-exec-2] DEBUG o.s.s.w.a.ExceptionTranslationFilter - Access is denied (user is anonymous); redirecting to authentication entry point
...

我要求供应商使用他们制作的测试应用程序登录并向我发送日志,请参阅下文,他们有一个"基本认证标题"

供应商管理员app验证的服务器日志

Break -- Starting  admin request for token
...
10:07:48.560 [http-nio-8282-exec-3] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
10:07:48.560 [http-nio-8282-exec-3] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
10:07:48.560 [http-nio-8282-exec-3] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 3 of 11 in additional filter chain; firing Filter: 'HeaderWriterFilter'
10:07:48.560 [http-nio-8282-exec-3] DEBUG o.s.s.w.h.writers.HstsHeaderWriter - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@77bd892d
10:07:48.560 [http-nio-8282-exec-3] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 4 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
10:07:48.560 [http-nio-8282-exec-3] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/oauth/token'; against '/logout'
10:07:48.560 [http-nio-8282-exec-3] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 5 of 11 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
10:07:48.562 [http-nio-8282-exec-3] DEBUG o.s.s.w.a.w.BasicAuthenticationFilter - Basic Authentication Authorization header found for user ' admin'
10:07:48.562 [http-nio-8282-exec-3] DEBUG o.s.s.authentication.ProviderManager - Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
10:07:48.563 [http-nio-8282-exec-3] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'scopedTarget.clientDetailsService'
10:07:48.564 [http-nio-8282-exec-3] DEBUG o.s.s.w.a.w.BasicAuthenticationFilter - Authentication success: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@36677da1: Principal: org.springframework.security.core.userdetails.User@664f353: Username:  admin; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_CLIENT; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@ffff6a82: RemoteIpAddress: 66.41.28.185; SessionId: null; Granted Authorities: ROLE_CLIENT
10:07:48.580 [http-nio-8282-exec-3] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 6 of 11 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
10:07:48.580 [http-nio-8282-exec-3] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 7 of 11 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
10:07:48.580 [http-nio-8282-exec-3] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 8 of 11 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
10:07:48.580 [http-nio-8282-exec-3] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - SecurityContextHolder not populated with anonymous token, as it already contained: 'org.springframework.security.authentication.UsernamePasswordAuthenticationToken@36677da1: Principal: org.springframework.security.core.userdetails.User@664f353: Username:  admin; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_CLIENT; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@ffff6a82: RemoteIpAddress: 66.41.28.185; SessionId: null; Granted Authorities: ROLE_CLIENT'
10:07:48.580 [http-nio-8282-exec-3] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 9 of 11 in additional filter chain; firing Filter: 'SessionManagementFilter'
10:07:48.580 [http-nio-8282-exec-3] DEBUG o.s.s.w.a.s.CompositeSessionAuthenticationStrategy - Delegating to org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy@285827ac
10:07:48.580 [http-nio-8282-exec-3] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 10 of 11 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
10:07:48.580 [http-nio-8282-exec-3] DEBUG o.s.security.web.FilterChainProxy - /oauth/token at position 11 of 11 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
10:07:48.580 [http-nio-8282-exec-3] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/oauth/token'; against '/oauth/token'
10:07:48.580 [http-nio-8282-exec-3] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Secure object: FilterInvocation: URL: /oauth/token; Attributes: [fullyAuthenticated]
10:07:48.580 [http-nio-8282-exec-3] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@36677da1: Principal: org.springframework.security.core.userdetails.User@664f353: Username:  admin; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_CLIENT; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@ffff6a82: RemoteIpAddress: 66.41.28.185; SessionId: null; Granted Authorities: ROLE_CLIENT
10:07:48.581 [http-nio-8282-exec-3] DEBUG o.s.s.access.vote.AffirmativeBased - Voter: org.springframework.security.web.access.expression.WebExpressionVoter@73821302, returned: 1
10:07:48.581 [http-nio-8282-exec-3] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Authorization successful
...

我们知道我们没有发送"授权:BASIC(RANDOMCODE)"但我们一直无法手动注入。我的印象是护照为我们做了这件事,但似乎不是。

最后我们回去检查旧服务器并确认我们之前从未在标头中发送过这个BASIC代码。我不确定服务器之前从未发现过这个问题,但是我们需要尽快升级并坚持如何使用它。

2 个答案:

答案 0 :(得分:3)

因此,对于OAuth2,预期的令牌标头是Authorization:Bearer标头。您可以在此处看到此内容:规范文档中的https://tools.ietf.org/html/rfc6750#section-2.1

在这种情况下,如果供应商正在寻找Basic标头,则该供应商是错误的。在引擎盖下,passport.js oauth2策略正在利用以下node.js模块:https://github.com/ciaranj/node-oauth

要真正开始深入挖掘这个问题,你可能希望手动实现一个样本,看看事情正在“陷入困境”。但是,在查看此内容时,您可以将标头从承载令牌更改为node-oauth库中的基本令牌:https://github.com/ciaranj/node-oauth/blob/master/lib/oauth2.js#L15

为此,我们需要更改一些代码并访问一些可能更容易发生更改的区域,因为我们正在访问内部属性。

首先,更改策略以将其推入变量:

var oauthStrategy = new OAuth2Strategy({
    authorizationURL: config.vendor.authorizationURL,
    tokenURL: config.vendor.tokenURL,
    clientID: config.vendor.clientID,
    clientSecret: config.vendor.clientSecret,
    callbackURL: config.vendor.callbackURL,
    passReqToCallback: true
    },
    function(req, accessToken, refreshToken, profile, done) {
        process.nextTick(function () {
            // store access token
            req.session.accessToken=accessToken;
            return done(null, profile);
        });
    }
);

然后,我们需要访问“受保护”属性(它没有真正受到保护,因为javascript无法强制执行) - 有关详情,请参阅:https://github.com/jaredhanson/passport-oauth2/blob/master/lib/strategy.js#L91

oauthStrategy._oauth2.setAuthMethod('BASIC');

现在把战略放在一起:

passport.use('vendor', oauthStrategy);

我实际上没有对此进行测试,但在查看源代码时,这应该对您有用,或者至少让您走上正确的道路。

答案 1 :(得分:1)

Mwillbank, you are correct about the issue you address, but the problem here is in a different area. I work with the vendor in this issue. The issue is with the Authorization header in the getAccessToken call to the Authorization server during the authorization_code flow. The standard you referenced is regarding how to request data from a resource server once you have the access token. For that call you are absolutely correct and we implement that standard as you referenced. The portion of the OAuth2 standard applicable in our situation is Section 4.1.3 of the main standard (rfc6749). This section references Section 3.2.1 on Client Authentication, which in turn references Section 2.3, also on Client Authentication. Here you will see that there isn't a mandated standard for Client Authentication of Confidential Clients. However, the one example given, in 2.3.1 on the next page, is to use Basic Authentication. I believe this is the default and most commonly used method for authenticating clients during the getAccessToken call of the authorization_code flow.

Perhaps part of the issue is that passport.js is presuming this situation involved the implicit_grant flow between a mobile app and a web server, where there isn't a separate call to getAccessToken. I noticed in the links you posted a comment that said the passport implementation was mostly OAuth1 with spotty support for OAuth2. Perhaps we have run into one of those spots that were missed. Nevertheless I found your post helpful in understanding better how passport works. Thanks.