首页 > 解决方案 > WSO2IS passport-saml SLO,从编码字符串构造 AuthRequest 时出错

问题描述

我目前正在使用 WSO2-IS 5.4.1 和 passport-saml 来实现 SSO 和 SLO。虽然单点登录正常工作,但我似乎无法正确配置完整的单点注销。

老实说,我对一些事情感到困惑

  1. 为什么 WSO2IS 向发起注销的 SP 发送注销请求?此操作未在 SAML 请求中捕获,但在如下所示的 IdP 终端错误中说明。
  2. samlStrategy.logout 是否也执行本地注销或仅构建请求并重定向到 WSO2IS,当我记录请求时似乎是后者?我之所以问,是因为我查看过的所有示例代码都在注销回调中包含 req.logout() ,但是在 /wso2app/logout/callback 帖子中的 req.logout() 之前,我将 req.isAuthenticated() 设为 false。

该应用程序正确重定向到 WSO2IS saml url,并且 WSO2IS 回传到我的应用程序配置中的回调。WSO2IS 也成功将用户注销,但似乎将不必要的 SLO 请求发送回节点应用程序。有关 IdP 中的节点 SP 配置,请参见下面的图片链接。

WSO2IS 中的节点应用程序配置

使用 saml DEBUG 在 IdP 终端中生成的主要错误:

[2018-05-01 10:44:47,135] DEBUG {org.wso2.carbon.identity.sso.saml.servlet.SAMLSSOProviderServlet} - Query string : slo=true&SAMLRequest=nVFLS8QwEP4rJfdtm272NbRdF4pQWD2oePAioc26hTRTM1P15xu7KyyCHoS5ZOZ7ZSbffvQ2ejOeOnSFkHEqtmVOurcD7PEFR74zr6MhjgLOEUyTQozeAWrqCJzuDQE3cL%2B72UMWpzB4ZGzQigvK3wxNZDyHACKqq0I8b5aLTaN0dlCtXmmZzlu1FtHjd8hACUCi0dSOWDsOrVSuZ%2BlilsoHqUCFWsUyWz6JqArZO6d5Yh6ZB4Iksdhoe0Ri2Cg1T74SEuGWLBbsRyNOG4DJw%2F%2FjG%2BU7YaaHIU8udM6it4FXV9E1% 2Bl7z74IyllOna2eHCQqm153dta03RKIcg5e86sICZNxgf3Y6iZen148Llp8%3D [2018-05-01 10:44:47,140] 调试 {org.wso2.carbon.identity.sso.saml.builders.SignKeyDataHolder} - 使用系统密钥存储初始化超级租户的密钥数据01018-052 10:44:47,163] 调试 {org.wso2.carbon.identity.sso.saml.servlet.SAMLSSOProviderServlet} - 查询字符串:slo=true&SAMLRequest=nVFLS8QwEP4rJfdtm272NbRdF4pQWD2oePAioc26hTRTM1P15xu7KyyCHoS5ZOZ7ZSbffvQ2ejOeOnSFkHEqtmVOurcD7PEFR74zr6MhjgLOEUyTQozeAWrqCJzuDQE3cL%2B72UMWpzB4ZGzQigvK3wxNZDyHACKqq0I8b5aLTaN0dlCtXmmZzlu1FtHjd8hACUCi0dSOWDsOrVSuZ%2BlilsoHqUCFWsUyWz6JqArZO6d5Yh6ZB4Iksdhoe0Ri2Cg1T74SEuGWLBbsRyNOG4DJw%2F%2FjG%2BU7YaaHIU8udM6it4FXV9E1%2Bl7z74IyllOna2eHCQqm153dta03RKIcg5e86sICZNxgf3Y6iZen148Llp8%3D [2018-05-01 10:44:47,169] DEBUG {org.wso2.carbon.identity.sso.saml.logout.LogoutRequestSender} - A logoutReqSenderTask分配给线程池 [2018-05-01 10:44:47,169] DEBUG {org.wso2.carbon.identity.sso.saml.session.SSOSessionPersistenceManager} - 从会话 ID 中检索到会话索引,会话索引为 6ffb8168-3022- 4d77-9ecc-3bc168d6c5da [2018-05-01 10:44:47,345] 调试 {org.wso2.carbon.identity.sso.saml.logout。LogoutRequestSender} - 单个注销请求发送到: http://localhost:3000/wso2app/logout/callback返回时已临时移动 [2018-05-01 10:44:47,348] DEBUG {org.wso2.carbon.identity.sso.saml.util.SAMLSSOUtil} - 请求消息��u������)ඈm�� ����,�}4��.qǬ��Z�� [致命错误] :1:1: prolog 中不允许内容。[2018-05-01 10:44:47,368] 错误 {org.wso2.carbon.identity.sso.saml.util.SAMLSSOUtil} - 从编码的字符串 org.xml.sax.SAXParseException 构造 AuthRequest 时出错:内容不是在序言中允许。在 org.apache.xerces.parsers.DOMParser.parse(Unknown Source) 在 org.apache.xerces.jaxp.DocumentBuilderImpl.parse(Unknown Source) 在 javax.xml.parsers.DocumentBuilder.parse(Unknown Source) 在 org.wso2 .carbon.identity.sso.saml.util.SAMLSSOUtil.unmarshall(SAMLSSOUtil.java:298) 在 org.wso2.carbon.identity.sso.saml.logout.LogoutRequestSender$LogoutReqSenderTask。 http://localhost:3000/wso2app/logout/callback 在 java.lang.Thread.run(Thread.java:748) 处运行(ThreadPoolExecutor.java:624) 原因:org.xml.sax.SAXParseException:prolog 中不允许内容。在 org.apache.xerces.parsers.DOMParser.parse(Unknown Source) 在 org.apache.xerces.jaxp.DocumentBuilderImpl.parse(Unknown Source) 在 javax.xml.parsers.DocumentBuilder.parse(Unknown Source) 在 org.wso2 .carbon.identity.sso.saml.util.SAMLSSOUtil.unmarshall(SAMLSSOUtil.java:298) ... 还有 7 个

节点应用。注意 user1 被 IdP 认证后会立即重定向到 /wso2app/logout,这只是为了测试。

const express = require('express');
const session = require('express-session');
const bodyParser = require('body-parser');
const passport = require('passport');
const SamlStrategy = require('passport-saml').Strategy;
const fs = require('fs');

const app = express();

// logger middleware
function logger(req,res,next){
    console.log(req.method, req.url);
    next();
}
app.use(logger);

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
    extended: false
}));

// express Session
app.use(session({
    secret: 'secret',
    saveUninitialized: true,
    resave: true
}));
app.use(passport.initialize());
app.use(passport.session());

// passport middleware
passport.serializeUser(function(user, done) {
    done(null, user);
});

passport.deserializeUser(function(user, done) {
    done(null, user);
});

const samlStrategy = new SamlStrategy({
    issuer: 'wso2app',

    // login endpoints
    entryPoint: 'https://localhost:9443/samlsso',   
    callbackUrl: 'http://localhost:3000/wso2app/login/callback',

    // logout endpoints
    logoutUrl: 'https://localhost:9443/samlsso?slo=true',
    logoutCallbackUrl: 'http://localhost:3000/success', 
    },
    function(profile, done){        

        return done(null,{      

            // subject claim, SAML:1.1 dialect  
            Email: profile.nameID,

            // requested claims, WSO2 internal dialect
            FirstName: profile['http://wso2.org/claims/givenname'], 
            LastName: profile['http://wso2.org/claims/lastname'],           
            Organization: profile['http://wso2.org/claims/organization'],
            Role: profile['http://wso2.org/claims/role'],

            // slo attributes
            nameID: profile.nameID,
            nameIDFormat: profile.nameIDFormat          
        });
    }); 
passport.use(samlStrategy);


app.get('/wso2app/login',   
    passport.authenticate('saml', {
        successRedirect: '/wso2app',
        failureRedirect: '/wso2app/login'       
    })
);  

app.post('/wso2app/login/callback', passport.authenticate('saml', { 
    failureRedirect: '/wso2app/login',
    failureFlash: true, 
    }),
    function(req, res){
        res.redirect('/wso2app')        
    }
);

app.get('/wso2app', function(req, res) {

    if (!req.isAuthenticated()) {       
        res.redirect('/wso2app/login');     
    }

    //parse domain from the user's email attribute in SAML response
    const domain = req.user.Email.substring(req.user.Email.lastIndexOf("@") + 1);
    console.log(domain);https://localhost:9443/samlsso?slo=true

    //use domain as index router, send specific users to their respective url
    switch(domain){
        case 'inst1.com':
            console.log(JSON.stringify(req.user));
            //res.redirect('/wso2app/inst1/');
            res.redirect('/wso2app/logout');
            break;
        case 'unv2.edu':
            console.log(JSON.stringify(req.user));
            res.redirect('/wso2app/unv2/');
            break;
        case 'inst3.com':
            console.log(JSON.stringify(req.user));
            res.redirect('/wso2app/inst3/');
            break;
        default:
            res.send("You're Authenticated but Unaffiliated, Your Credentials: " + JSON.stringify(req.user));
    }       
});

app.get('/wso2app/logout', function(req, res){

    console.log("req.user.nameID: " + req.user.nameID);
    console.log("req.user.nameIDFormat: " + req.user.nameIDFormat); 
    if (req.isAuthenticated()) {
        console.log("Still Auth in Node 1");        
    }

    samlStrategy.logout(req, function(err, request){
        if (!err) {
            res.redirect(request);
            console.log('request: ' + request);
        }       
    }); 
});

app.post('/wso2app/logout/callback', function(req,res){ 
    if (!req.isAuthenticated()) {       
        console.log("Already logged out");
    }
    req.logout();   
    res.redirect('http://localhost:3000/success');
});

app.listen(3000, function(){
    console.log('Server Started on Port 3000');
});

SLO 请求注意:这是节点应用程序向 IdP 发送的正确 SLO 请求,由 SAML 浏览器工具捕获。从 IdP 发送回应用程序的神秘 slo 请求仅记录在 IdP 终端错误中。

我查看了多个帖子,但找不到与我的问题相关的帖子。发送到http://localhost:3000/wso2app/logout/callback的注销请求的神秘性质令人担忧,但我还是不明白为什么要发送它。这似乎不遵循此处描述的官方 SLO 流程我读到“[致命错误]:1:1:序言中不允许内容。” 与过多的空白字符有关,我不确定如何或为什么会生成此请求?任何帮助深表感谢。

标签: wso2ispassport-saml

解决方案


推荐阅读