【问题标题】:OpenAM - Getting a Session attribute into an OpenID Connect claimOpenAM - 将会话属性添加到 OpenID Connect 声明中
【发布时间】:2019-08-07 19:24:24
【问题描述】:

我正在使用配置为具有 SAML 信任圈的 OpenAM 13.5,以便将登录到我们的应用程序与第三方 IdP 联合起来。第三方收到的一些 SAML 断言被映射为会话级属性。 SAML 部分工作正常,但我需要连接到 OpenAM 一个可以与 OpenID Connect 通话的应用程序。我创建了一个 OpenID Connect 服务,相应地配置了客户端,我可以使用流程“App -> OpenAM UI -> 3rd party IDP -> OpenAM OIDC -> App”成功登录。

问题是我只能检索映射到数据存储的属性 - 映射的声明中不包含会话属性(例如 AuthLevel、IDP 名称等)。

我尝试编辑 OIDC 声明默认脚本,该脚本有一个似乎包含我需要的会话变​​量,但不幸的是会话变量始终为空。

这是正确的方法吗?为什么会话为空?我需要启用什么才能阅读它吗?

提前感谢您的帮助。

【问题讨论】:

    标签: saml openam openid-connect


    【解决方案1】:

    您无法在 OIDC 声明脚本中检索 SSO 会话属性,因为 OAuth2 客户端未在令牌请求中发送 SSO 跟踪 cookie。

    只有在您使用 AM 专有功能“始终在 ID 令牌中包含声明”时才有可能。

    【讨论】:

    • 嗨,伯恩哈德,我明白你的意思。我之前还尝试在调用 UserInfo 端点时在名为 iPlanetDirectoryPro 的 http 标头中提供 id_token,以便为端点提供有关会话的一些信息,但它没有任何效果。但是您使用“始终在 ID 令牌中包含声明”功能的建议奏效了!有关详细信息,请参阅我的其他答案。谢谢!
    【解决方案2】:

    正如 Bernhard 在 cmets 中所描述的,一旦请求到达 /userinfo 端点,OpenAM 就无法将访问令牌与活动会话协调起来(并且会话也不再存在)。

    但是,当通过激活专有 AM 功能“始终在 ID 令牌中包含声明”访问 ID 令牌中的声明时,会话对象可用,我们可以轮询其属性!

    对于未来的读者,这是我修改 OIDC 脚本的方式:

    /*
    * The contents of this file are subject to the terms of the Common Development and
    * Distribution License (the License). You may not use this file except in compliance with the
    * License.
    *
    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
    * specific language governing permission and limitations under the License.
    *
    * When distributing Covered Software, include this CDDL Header Notice in each file and include
    * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
    * Header, with the fields enclosed by brackets [] replaced by your own identifying
    * information: "Portions copyright [year] [name of copyright owner]".
    *
    * Copyright 2014-2016 ForgeRock AS.
    */
    import com.iplanet.sso.SSOException
    import com.sun.identity.idm.IdRepoException
    import org.forgerock.oauth2.core.UserInfoClaims
    
    /*
    * Defined variables:
    * logger - always presents, the "OAuth2Provider" debug logger instance
    * claims - always present, default server provided claims
    * session - present if the request contains the session cookie, the user's session object
    * identity - always present, the identity of the resource owner
    * scopes - always present, the requested scopes
    * requestedClaims - Map<String, Set<String>>
    *                  always present, not empty if the request contains a claims parameter and server has enabled
    *                  claims_parameter_supported, map of requested claims to possible values, otherwise empty,
    *                  requested claims with no requested values will have a key but no value in the map. A key with
    *                  a single value in its Set indicates this is the only value that should be returned.
    * Required to return a Map of claims to be added to the id_token claims
    *
    * Expected return value structure:
    * UserInfoClaims {
    *    Map<String, Object> values; // The values of the claims for the user information
    *    Map<String, List<String>> compositeScopes; // Mapping of scope name to a list of claim names.
    * }
    */
    
    // user session not guaranteed to be present
    boolean sessionPresent = session != null
    
    def fromSet = { claim, attr ->
        if (attr != null && attr.size() == 1){
            attr.iterator().next()
        } else if (attr != null && attr.size() > 1){
            attr
        } else if (logger.warningEnabled()) {
            logger.warning("OpenAMScopeValidator.getUserInfo(): Got an empty result for claim=$claim");
        }
    }
    
    attributeRetriever = { attribute, claim, identity, session, requested ->
        if (requested == null || requested.isEmpty()) {
            fromSet(claim, identity.getAttribute(attribute))
        } else if (requested.size() == 1) {
            requested.iterator().next()
        } else {
            throw new RuntimeException("No selection logic for $claim defined. Values: $requested")
        }
    }
    
    sessionAttributeRetriever = { attribute, claim, identity, session, requested ->
        if (requested == null || requested.isEmpty()) {
            if (session != null) {
                fromSet(claim, session.getProperty(attribute))
            } else {
                null
            }
        } else if (requested.size() == 1) {
            requested.iterator().next()
        } else {
            throw new RuntimeException("No selection logic for $claim defined. Values: $requested")
        }
    }
    
    // [ {claim}: {attribute retriever}, ... ]
    claimAttributes = [
            "email": attributeRetriever.curry("mail"),
            "address": { claim, identity, session, requested -> [ "formatted" : attributeRetriever("postaladdress", claim, identity, session, requested) ] },
            "phone_number": attributeRetriever.curry("telephonenumber"),
            "given_name": attributeRetriever.curry("givenname"),
            "zoneinfo": attributeRetriever.curry("preferredtimezone"),
            "family_name": attributeRetriever.curry("sn"),
            "locale": attributeRetriever.curry("preferredlocale"),
            "name": attributeRetriever.curry("cn"),
            "spid_uid": attributeRetriever.curry("employeeNumber"),
            "spid_idp": attributeRetriever.curry("idpEntityId"),
            "spid_gender": attributeRetriever.curry("description"),
            "spid_authType": sessionAttributeRetriever.curry("AuthType"),
            "spid_authLevel": sessionAttributeRetriever.curry("AuthLevel"),
    ]
    
    // {scope}: [ {claim}, ... ]
    scopeClaimsMap = [
            "email": [ "email" ],
            "address": [ "address" ],
            "phone": [ "phone_number" ],
            "profile": [ "given_name", "zoneinfo", "family_name", "locale", "name" ],
            "spid": [ "spid_uid", "spid_idp", "spid_authType", "spid_authLevel", "spid_gender" ],
    ]
    
    if (logger.messageEnabled()) {
        scopes.findAll { s -> !("openid".equals(s) || scopeClaimsMap.containsKey(s)) }.each { s ->
            logger.message("OpenAMScopeValidator.getUserInfo()::Message: scope not bound to claims: $s")
        }
    }
    
    def computeClaim = { claim, requestedValues ->
        try {
            [ claim, claimAttributes.get(claim)(claim, identity, session, requestedValues) ]
        } catch (IdRepoException e) {
            if (logger.warningEnabled()) {
                logger.warning("OpenAMScopeValidator.getUserInfo(): Unable to retrieve attribute=$attribute", e);
            }
        } catch (SSOException e) {
            if (logger.warningEnabled()) {
                logger.warning("OpenAMScopeValidator.getUserInfo(): Unable to retrieve attribute=$attribute", e);
            }
        }
    }
    
    def computedClaims = scopes.findAll { s -> !"openid".equals(s) && scopeClaimsMap.containsKey(s) }.inject(claims) { map, s ->
        scopeClaims = scopeClaimsMap.get(s)
        map << scopeClaims.findAll { c -> !requestedClaims.containsKey(c) }.collectEntries([:]) { claim -> computeClaim(claim, null) }
    }.findAll { map -> map.value != null } << requestedClaims.collectEntries([:]) { claim, requestedValue ->
        computeClaim(claim, requestedValue)
    }
    
    def compositeScopes = scopeClaimsMap.findAll { scope ->
        scopes.contains(scope.key)
    }
    
    return new UserInfoClaims((Map)computedClaims, (Map)compositeScopes)
    

    我还必须将 java.util.ArrayList$Itr 类添加到脚本类白名单中。

    感谢您的帮助!

    【讨论】:

      猜你喜欢
      • 2019-05-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-30
      • 2015-06-09
      • 2020-03-31
      • 2016-10-10
      • 1970-01-01
      相关资源
      最近更新 更多