【问题标题】:CAS Custom Authentication Handler Principal JSON ProblemCAS 自定义身份验证处理程序主体 JSON 问题
【发布时间】:2020-01-07 08:27:46
【问题描述】:

我在自定义身份验证处理程序的覆盖的 authenticateUsernamePasswordInternal 函数中返回自定义属性时遇到 JSON 问题:

return createHandlerResult( credential,
 this.getPrincipalFactory( ).createPrincipal( credential.getId( ), attributes) );

哪个 createPrincipal 方法接受Map<String, Object>

Principal createPrincipal(String id, Map<String, Object> attributes);

当我将Map&lt;String, List&gt; 放入属性中时,CAS 返回列表的 toString 表示,而不是它的 JSON 表示。简而言之,如何从这个函数返回正确的 JSON 序列化属性?

注意事项:

  1. 使用的 CAS 版本:5.3.8
  2. 通过 AbstractUsernamePasswordAuthenticationHandler 扩展的自定义身份验证
  3. JWT 是使用 CAS 协议实现的

这是我目前尝试过的:

1) CAS在验证服务时将List of HashMap转换为String(可能是根本原因)

当我将 Principal 创建为 Map&lt;String, new ArrayList&lt;new HashMap&lt;&gt;&gt; 时,我的 HashMap 将转换为 HashMap 的 toString 表示。所以它的类型信息现在从 HashMap -> String 转换,这使得 CAS 向我的客户端返回不正确的 JSON,因为 String 像 JSON 一样被序列化。发生在这里 ->

AbstractUrlBasedTicketValidator -> validate() -> final String serverResponse = retrieveResponseFromServer(new URL(validationUrl), ticket);

这里 serverResponse 包含:

<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
    <cas:authenticationSuccess>
        <cas:user>test</cas:user>
        <cas:attributes>
            <cas:roles>(Test,[ADMIN])</cas:roles>
         </cas:attributes>
      </cas:authenticationSuccess>
</cas:serviceResponse>

我的期望:

<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
    <cas:authenticationSuccess>
        <cas:user>test</cas:user>
        <cas:attributes>
            <cas:roles>
               <cas:Test>ADMIN</cas:Test>
            </cas:roles>
         </cas:attributes>
      </cas:authenticationSuccess>
</cas:serviceResponse>

2) 在 Map&lt;String, Object&gt; 的对象中返回带有虚拟映射的新 Principal

当我将 HashMap 添加到 Map&lt;String, Object&gt; 的 Object 部分时,它会以 {"left": "key", "right": "value"} 的形式返回给客户端,用于 ["key":"value"] 映射。 调试了这么久,看到CAS在请求/tickets URL时是如何使用json-smart-2.3库的。我看到,当我在 Map 的 Object 中为属性发送 Map 时,json-smart 库使用它的 BeansWriter 序列化 Map,它获取类的字段并用作键。所以我发送 HashMap -> CAS 将其转换为 Java Pair(在下一步中描述) -> Pair 具有属性“left”和“right”,因此它将左右字段添加到我不想要的 JSON 正文中。

3) 调试 CAS 以了解它如何将属性序列化为 JSON 以进行 API 调用(令牌 url)

  • 我查找 CAS 以了解它如何处理 Principal,它将所有内容合并为 Pair 的 LinkedList。因此,无论我在 Map 对象部分添加什么,它都会以 JSON 表示形式的数组形式返回,如 []。这意味着当我添加attributes.put("name", "ExampleName") 时,它返回为"{"name":["ExampleName"]} 因为CAS 调用DefaultAuthenticationResultBuilder 类的mergeAttributes 函数,所以在我们在Principal 对象创建Map&lt;String, Object&gt; 中发送的那个函数中,Principal 中的所有内容都作为List 返回。所以这意味着每个属性都作为列表返回?还下载了 CAS 源代码,看到他们的测试断言如 principal.getAttributes()[0] 暗示这是默认行为?我在任何地方都看不到任何文档,但没有任何意义。

4) 在 Map&lt;String, Object&gt; 的对象中返回带有 JSON 表示的新 Principal(几乎是一个解决方案)

我还尝试在属性的 Object 部分中直接返回 JSON 表示:

Map<String, Object> attributes = new HashMap<>();
String roles = "{"TestModule":["Name1"]}"; (didn't add escape quotes for simplicity)
attributes.put("roles", roles);

它按预期返回 JSON 用于对 /ticket URL 的 API 调用,因为序列化库试图序列化我发送的 String 所以这是一种令人困惑的解决方案,但仍然存在一些问题。如果我通过 /login 页面登录,CAS 会再次用 [] 包装每个属性。当我调试时,我看到这次 CAS 没有使用它在我调用 /ticket URL 时使用的序列化程序。我尝试进行更多调试,但在 CAS 开始使用 cas-server-core-webflow-api

时卡在了某个地方

我不想要这个:

{"rolesPerModule":["{\"TestModuleForBouncer_LIVE\":[\"ADMIN\"]}"]}

或者这个:

{"name":[ExampleName]} *(yes, no "" returned here)*

我想要喜欢:

{"rolesPerModule":{"{\"TestModuleForBouncer_LIVE\":[\"ADMIN\"]}"}}

或者这个

{"name":"ExampleName"}

【问题讨论】:

    标签: java cas apiomat


    【解决方案1】:

    终于,我找到了根本原因。如果您在这里寻找您的 Principal 属性具有 {"left": "key", "right": "value"} instead of["key":"value"] 的原因,我将首先尝试显示根本原因和我的解决方案:

    为什么对 /v1/tickets 的请求的响应 JSON 中有“左”和“右”属性?

    1) 你返回 new SimplePrincipal(id, new HashMap)

    2) CAS 将所有属性合并到一个集合中。你可以找到它:

    DefaultAuthenticationResultBuilder -> mergeAttributes()
    

    然后它调用

    CollectionUtils.toCollection(entry.getValue(), ArrayList.class)
    

    3) 在函数内部查看这些行:

    else if (obj instanceof Collection) {
                c.addAll((Collection<Object>) obj);
                LOGGER.trace("Converting multi-valued attribute [{}]", obj);
            } else if (obj instanceof Map) {
                final Set<Map.Entry> set = ((Map) obj).entrySet();
                c.addAll(set.stream().map(e -> Pair.of(e.getKey(), e.getValue())).collect(Collectors.toSet()));
            } 
    

    如果您的属性是 Map,它们的值将作为 Pair 流式传输。因此,您的 hashmaps 值类型现在更改为 Pair

    4) 然后 CAS 开始创建您的 JSON。看着 JWTTokenTicketBuilder -&gt; buildJwt 函数(在 CAS 6.X 版本中由另一个类 JwtBuilder 处理,但问题依旧)

    5) CAS 使用 nimbus-jose-jwt (v5.10) 创建 JWTClaims。

    6) nimbus-jose-jwt 使用 json-smart (v2.3) 返回 JWTObject。

    7) CAS 调用 object.toJSONString()(JWTObject 的函数)将您的属性序列化为 JSON。这是它发生的部分,但它也与我之前详细编写的步骤有关。

    8) json-smart 库不处理 Pair 类型,它使用默认编写器来处理它们不处理的类型,这是 BeansWriterASM 的情况。该编写器获取类的所有属性并将它们用作 JSON 的键和它们的值。

    9) 因此,在这种情况下,您的值 "name":"test" -> 在第 3 步被 CAS 转换为 "left":"name", "right":"test" Pairs。由于 json-smart 不处理 Pair 类,它返回这个 JSON。

    是的,说来话长,但我想清楚地分享我的经历。 json-smart 库已经很久没有更新了,nimbus-jose-jwt 库计划在他们的下一个版本中更改 json-smart 库 (https://bitbucket.org/connect2id/nimbus-jose-jwt/pull-requests/50/wip-allow-replacing-json-smart-with/diff),然后 CAS 也可能会更改它,但对于两者来说似乎很长的路要走.

    解决方法/解决方案

    1) 不要在 SimplePrincipal 中返回 Map 实例。相反,在属性的根上使用集合。因为正如上面的第 3 步,如果您的值是 Collection 的实例,CAS 不会用 Pair 包装您的值。例如,我的工作示例是:

        final Map<String, Object> test= new HashMap<>( );
        test.put( "faultyJSON", yourAttributes); // don't do this
        test.put( "properJSON", Collections.singleton( yourAttributes ) ); // make this
    
        return createHandlerResult( credential,
            this.getPrincipalFactory( ).createPrincipal( credential.getId( ), test) );
    

    这将使您的 JSON 在根目录上具有无意义的数组,但如前所述,这是目前的解决方法。

    2) 使用 JSONAware 类包装您的属性,该 json-smart 库允许您返回自己的 JSONString 表示。这不是安全的解决方案,因为如果您更改 CAS 版本并且如果 CAS 更改了任何库实现,那么此解决方案可能会让您头疼,但无论如何我也会为此分享我的工作示例:

    public class JsonWrapper<T> implements JSONAware, Serializable
    {
        @JsonValue
        public T attributes;
    
        public JsonWrapper( T attributes )
        {
            this.attributes = attributes;
        }
    
        @Override public String toJSONString( )
        {
            String json = "{}";
            try
            {
                json = new ObjectMapper( ).writeValueAsString( attributes );
            }
            catch ( JsonProcessingException e )
            {
                LoggerFactory.getLogger( getClass( ) )
                    .error( "Couldn't map attributes: {}. Returning default: {}", attributes, json );
            }
            return json;
        }
    }
    

    当 json-smart 的序列化开始时,这个类将返回它自己的 JSON 表示。您还需要使用此类包装所有属性,例如:

    yourAttributes.forEach( ( k, v ) -> yourAttributes.put( k, new JsonWrapper<> (v) ) )
    return createHandlerResult( credential,
                this.getPrincipalFactory( ).createPrincipal( credential.getId( ), yourAttributes) );
    

    3) 你可以像JsonPairWriter一样实现自己的Writer,并注册到JsonWriter的writerList中。我试过这个,它也有效,但与上述相比,它可能是最愚蠢的解决方案,因为有很多维护和错误的副作用,请记住。

    最后但同样重要的是,当您调用 CAS 的 /login 端点时不会发生这种情况,这意味着通过浏览器获取令牌。据我所知,它有不同的工作流程来返回属性和 json,而不是我上面描述的流程。不确定,但服务和所有属性等信息是通过 REST 调用获取的,并获得一些 XML 响应,因此将其解析给客户端。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-03
      • 2018-06-25
      相关资源
      最近更新 更多