【问题标题】:Cannot make passport-saml work with ADFS无法使 passport-saml 与 ADFS 一起使用
【发布时间】:2022-01-23 11:04:35
【问题描述】:

我已经使用 express、passport 和 passport-saml 在 NodeJS 应用程序上通过 ADFS 实现了 SSO。我可以在 ADFS 上登录,并且与 SAML 令牌一起正确地发回了我的回调路由(即 localhost:3000/adfs/postResponse)。但是,当我到达回调路由时,似乎 SAML 令牌被拒绝,所以我被送回 ADFS 登录。然后重复此操作。

谁能提出什么问题?任何帮助将不胜感激。

更多细节:

这个 SAML 请求被发送到 ADFS:

<?xml version="1.0"?>
<samlp:AuthnRequest AssertionConsumerServiceURL="https://localhost:3000/adfs/postResponse"
    Destination="https://nonp-adfs.dsgapps.dk/adfs/ls" ID="_5e09625f5c3f5dbb0b6b"
    IssueInstant="2018-06-28T11:57:35.962Z"
    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
    <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">acme_tools_com</saml:Issuer>
    <samlp:RequestedAuthnContext Comparison="exact" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
        <saml:AuthnContextClassRef xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password</saml:AuthnContextClassRef>
    </samlp:RequestedAuthnContext>
</samlp:AuthnRequest>

此 SAML 响应从 ADFS 服务器发布到我的回调 https://localhost:3000/adfs/postResponse

<samlp:Response Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"
    Destination="https://localhost:3000/adfs/postResponse" ID="_e543e979-0d99-48fe-947f-1d1469da8c70"
    InResponseTo="_49ab1e1060c3d7849902" IssueInstant="2018-06-28T19:46:27.782Z" Version="2.0"
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
    <Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">http://nonp-adfs.dsgapps.dk/adfs/services/trust</Issuer>
    <samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>
    <Assertion ID="_cf245f57-1380-47cd-a5d3-05b13e4d9416" IssueInstant="2018-06-28T19:46:27.782Z"
        Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
        <Issuer>http://nonp-adfs.dsgapps.dk/adfs/services/trust</Issuer>
        <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
            <ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
                <ds:Reference URI="#_cf245f57-1380-47cd-a5d3-05b13e4d9416">
                    <ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                    <ds:DigestValue>r6voAsVq4yAJTn4BQLFsyaoiCK3b7KQbJ5jVqi53ceY=</ds:DigestValue>
                </ds:Reference>
            </ds:SignedInfo>
            <ds:SignatureValue>F55JA6jNp3qFfp7p/BSzQBRTtVPOlQvIfVNG3JiqjohVC7Et0+aiRVlHHvZNghPJxhmxhuAUbo2kOweN+lZKb+fqDgK51kZ/DrIVpkljmwP2gJYgOGpJti53wfH2qkdDsxNkR3e13mG7RKwBuA4gJ0NxUFshmxyun0HKefd10wjnFwHY6dELWFmTL1W5xd2ZF/98ahIaqEWAMCYsJewEg4ND8z4vG74miht3lWHfTJL6kQ0UGkTJVwGZy9L8zaY8AMDRujs8SlXvBx9nvUnvufpYqto4kd0O0USWMCOPipcF2sVYDOVzidRSRb79TK256Wg9EGiw1usVThfAJ8IBzQ==</ds:SignatureValue>
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <ds:X509Data>
                    <ds:X509Certificate>MIIC5DCCAcygAwIBAgIQWKHI7vunT6hIeNtPiejvbTANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNBREZTIFNpZ25pbmcgLSBub25wLWFkZnMuZHNnYXBwcy5kazAeFw0xODA2MTUxNDQ4NTdaFw0xOTA2MTUxNDQ4NTdaMC4xLDAqBgNVBAMTI0FERlMgU2lnbmluZyAtIG5vbnAtYWRmcy5kc2dhcHBzLmRrMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyN0SSHVjJML1lmcHB8RBNLXnegEISB66Nc75xEpscFGNPKoQloLck6XLPYvhmiL8WiVHTzghiJpU/faViR7s+wksj3n4IXVfCxb6wMd78LiOfeE6yyED+C/EprwoRWGXncUK4lwfLDGOPbWVqaPy0u14rQR0mvn0BsIOiML1JJvAPtf8fhavNmce2aEeRltLY3N8aoLMw8/TMrG+wk1imUo+JScp3gOPqrDnQBGgcjdBY/EaC9mFfAUbhyly0vKl/gYkOv1HFhUMtH7NlLUmDsvOCt3Nrbf6aKmi+H1EAfwJR/POnMbsoC8sqf4PWk/kMtj1POOpZAnQOBE8u4NtPwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQDIdu6cr7LgoNdXpgtwd/Zt7sb1N6dJ/GgULuxBm7Bm1Mdsc9+Q0lDhxeGtay9AIvpbF67xvSzrCz3eL9xPuNV6BYmZpYFsyBPP4MROlkgq1MkqLDpkB/zkiKQqZiJG3RHl5e+WniFrAmNxuuUAtdhKbh1ADJKc1bxte6uiY0dN/Mfw6WnY3m3VOtae9xoqHNM2i4uhEbMvXV9Pmb8BVv4eIZLtOgo+vgkusp3FZa2PL4UWQIPNiEggIxhs7MfpaoADT4taGeavpHWKuxIGvDQzoe7GP2iDGzyH1kS24rSeJRYOiyBq1zPJHrSPeLFsef/7LapCaz5x5+T/eWPhyJKd</ds:X509Certificate>
                </ds:X509Data>
            </KeyInfo>
        </ds:Signature>
        <Subject>
            <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><SubjectConfirmationData InResponseTo="_49ab1e1060c3d7849902"
                NotOnOrAfter="2018-06-28T19:51:27.782Z" Recipient="https://localhost:3000/adfs/postResponse"/></SubjectConfirmation>
        </Subject>
        <Conditions NotBefore="2018-06-28T19:46:27.781Z" NotOnOrAfter="2018-06-28T20:46:27.781Z">
            <AudienceRestriction>
                <Audience>acme_tools_com</Audience>
            </AudienceRestriction>
        </Conditions>
        <AuthnStatement AuthnInstant="2018-06-28T19:45:51.797Z">
            <AuthnContext>
                <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef>
            </AuthnContext>
        </AuthnStatement>
    </Assertion>
</samlp:Response>

注意:可以使用 SAML Chrome 扩展程序检查 SAML 请求和响应。

我的 NodeJS 程序最核心的部分是:

const verifyFunction = function(profile, done) {
    console.log("Verifying"+ profile);
    return done(null,
        {
            upn: profile['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'],
            // e.g. if you added a Group claim
            group: profile['http://schemas.xmlsoap.org/claims/Group']
        });
};

var strategy = new SamlStrategy(
    {
        entryPoint: 'https://nonp-adfs.dsgapps.dk/adfs/ls',
        issuer: 'acme_tools_com',
        callbackUrl: 'https://localhost:3000/adfs/postResponse',
        privateCert: fs.readFileSync(root + '/acme_tools_com.key', 'utf-8'),
        cert: fs.readFileSync(root + '/acme_tools_com.cert', 'utf-8'),
        authnContext: 'http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password',
        // not sure if this is necessary?
        acceptedClockSkewMs: -1,
        identifierFormat: null,
        signatureAlgorithm: 'sha256'
    },
    verifyFunction
);

strategy.userProfile = function(accessToken, done) {
    console.log("UserProfile:" + accessToken);
    done(null, accessToken);
};


passport.use('provider', strategy);
passport.serializeUser(function(user, done) {
    console.log("Serializing user");
    done(null, user);
});
passport.deserializeUser(function(user, done) {
    console.log("Deserializing user");
    done(null, user);
});



app.get('/login',
    passport.authenticate('provider', { failureRedirect: '/', failureFlash: true })
);
app.post('/adfs/postResponse',
    function(req, res, next) {
        console.log("Before authenticating: " );
        next();
    },
    passport.authenticate('provider', { failureRedirect: '/', failureFlash: true }),
    function(req, res) {
        console.log("User: " + util.inspect(req.user));
        res.cookie('accessToken', req.user);
        res.redirect('/');
    }
);

登录时,我在 ADFS 服务器上看不到任何错误,所以那里看起来不错。

在我看到的 NodeJS 服务器上:

Express server started on https://localhost:3000 
postResponse entered:
postResponse entered:
...

换句话说,verifyFunction 永远不会被调用。这不是很奇怪吗?护照-saml 模块似乎没有接收到 SAML 响应。

从 ADFS 到我的回调的 POST 如下:

curl 'https://localhost:3000/adfs/postResponse' -H 'Origin: https://nonp-adfs.dsgapps.dk' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8' -H 'Referer: https://nonp-adfs.dsgapps.dk/adfs/ls?SAMLRequest=nVJLc9owEP4rHt2xZMdD....' -H 'Accept-Encoding: gzip, deflate, br' -H 'Accept-Language: en-US,en;q=0.9,da;q=0.8' -H  --data 'SAMLResponse=PHNhbWxwOlJlc3.......' --compressed --insecure

这是完整的 NodeJS 程序:

'use strict';

var util = require('util');
var https = require('https');
var app = require('express')();
var cookieParser = require('cookie-parser');
var passport = require('passport');
var fs = require('fs');
var SamlStrategy = require('passport-saml').Strategy;
var path = require("path");

const root = __dirname;
const verifyFunction = function(profile, done) {
    console.log("Verifying"+ profile);
    return done(null,
        {
            upn: profile['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn'],
            // e.g. if you added a Group claim
            group: profile['http://schemas.xmlsoap.org/claims/Group']
        });
};

var strategy = new SamlStrategy(
    {
        entryPoint: 'https://nonp-adfs.dsgapps.dk/adfs/ls',
        issuer: 'acme_tools_com',
        callbackUrl: 'https://localhost:3000/adfs/postResponse',
        privateCert: fs.readFileSync(root + '/acme_tools_com.key', 'utf-8'),
        cert: fs.readFileSync(root + '/acme_tools_com.cert', 'utf-8'),
        authnContext: 'http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password',
        // not sure if this is necessary?
        acceptedClockSkewMs: -1,
        identifierFormat: null,
        signatureAlgorithm: 'sha256'
    },
    verifyFunction
);

strategy.userProfile = function(accessToken, done) {
    console.log("UserProfile:" + accessToken);
    done(null, accessToken);
};


passport.use('provider', strategy);
passport.serializeUser(function(user, done) {
    console.log("Serializing user");
    done(null, user);
});
passport.deserializeUser(function(user, done) {
    console.log("Deserializing user");
    done(null, user);
});

function validateAccessToken(accessToken) {
    console.log("AccessToken: "+ accessToken);
    return;
}


// Configure express app
app.use(cookieParser());
app.use(passport.initialize());

app.get('/login',
    passport.authenticate('provider', { failureRedirect: '/', failureFlash: true })
);
app.post('/adfs/postResponse',
    function(req, res, next) {
        console.log("Before authenticating: " );
        next();
    },
    passport.authenticate('provider', { failureRedirect: '/', failureFlash: true }),
    function(req, res) {
        console.log("User: " + util.inspect(req.user));
        res.cookie('accessToken', req.user);
        res.redirect('/');
    }
);
app.get('/', function (req, res) {
    req.user = req.cookies['accessToken'];
    res.send(
        !req.user ? '<a href="/login">Log In</a>' : '<a href="/logout">Log Out</a>' +
        '<pre>' + JSON.stringify(req.user, null, 2) + '</pre>');
});
app.get('/logout', function (req, res) {
    res.clearCookie('accessToken');
    res.redirect('/');
});

var certOptions = {
    key: fs.readFileSync(root + '/localhost.key'),
    cert: fs.readFileSync(root + '/localhost.cert')
}

var server = https.createServer(certOptions, app).listen(3000)
console.log('Express server started on https://localhost:3000');

【问题讨论】:

  • 您已经发布了两次请求。请张贴回复。
  • 谢谢,我添加了回复。
  • 状态为 Responder 表示出错。 ADFS 事件日志显示什么?看起来 ADFS 使用的是 SHA256,但 SAML 通常是 SHA1。您可以在“高级”选项卡上的 RP 上更改此设置。
  • 关于 SHA256:在 ADFS 中的 RP Advanced 选项卡上选择了 SHA256。所以我认为这会很好。我会尝试将其更改为 SHA1。
  • ADFS 事件日志仅显示此错误,这是由我的应用程序和 ADFS 之间的登录循环引起的:Microsoft.IdentityServer.Web.InvalidRequestException:MSIS7042:相同的客户端浏览器会话已设为“6”在最后 '1' 秒内的请求。有关详细信息,请联系您的管理员。

标签: node.js passport.js saml-2.0 adfs


【解决方案1】:

问题变得非常简单。我需要包含中间件来解析 POST 数据。

将这些行添加到我的 NodeJS 模块的顶部后,它就可以工作了。

var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded());
app.use(bodyParser.json());

【讨论】:

  • 只是一个问题。您是如何在服务器上模拟 ADFS 的?
猜你喜欢
  • 2016-08-08
  • 1970-01-01
  • 2013-05-26
  • 1970-01-01
  • 1970-01-01
  • 2018-07-11
  • 2013-04-23
  • 2013-10-01
  • 2016-06-09
相关资源
最近更新 更多