Невозможно заставить passport-saml работать с ADFS - PullRequest
0 голосов
/ 28 июня 2018

Я реализовал SSO с ADFS в приложении NodeJS, используя Express, Passport и Passport-Saml. Я могу войти в ADFS, и я правильно отправлен обратно на мой маршрут обратного вызова (который является localhost: 3000 / adfs / postResponse) вместе с SAML-токеном. Однако, когда я достигаю маршрута обратного вызова, кажется, что 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>

NB. Запрос и ответ SAML можно проверить с помощью расширения SAML Chrome.

Самая центральная часть моей программы 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 никогда не вызывается. Разве это не странно? Кажется, что модуль passport-saml не принимает ответ SAML.

POST от ADFS к моему обратному вызову выглядит следующим образом:

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:

<code>'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) + '
'); }); app.get ('/ logout', function (req, res) { res.clearCookie ( 'маркер доступа'); res.redirect ( '/'); }); var certOptions = { ключ: fs.readFileSync (root + '/localhost.key'), cert: fs.readFileSync (root + '/localhost.cert') } var server = https.createServer (certOptions, app) .listen (3000) console.log ('Экспресс-сервер запущен https://localhost:3000');

1 Ответ

0 голосов
/ 29 июня 2018

Проблема оказалась действительно простой. Мне нужно было включить промежуточное ПО для анализа данных POST.

После добавления этих строк в начало моего модуля NodeJS все заработало.

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