从经过 SAML 验证的 UI 验证 AJAX API 调用

Posted

技术标签:

【中文标题】从经过 SAML 验证的 UI 验证 AJAX API 调用【英文标题】:Authenticating AJAX API calls from the UI which is SAML authenticated 【发布时间】:2017-06-05 22:37:15 【问题描述】:

我们的应用程序有 2 个部分。

用 Java 8 编写的后端,使用 Jersey 2.0 公开不同的 REST 端点。 UI 是使用 React 和其他节点模块构建的单页应用程序。

Web 界面使用 Okta 支持的 SAML 2.0 身份验证作为身份提供者。后端创建 HTTP Session 并在 cookie 中发送 JSESSIONID。

现在 UI 调用 REST 端点来显示数据。我们需要在我们的 REST API 中添加一个身份验证层,我在 Authenticating rest endpoints and the UI using Okta 上提出了一个单独的问题。

我在这里的具体问题是我可以从 UI 传递什么作为对这些 API 调用的身份验证方式,因为 UI 确实经过身份验证并且 HTTP 会话仍然有效。因此,我不需要创建单独的 OAuth 2.0 令牌将其传递给 UI,以便 UI 可以将其传递回后端。 OAuth 2.0 流程对使用我们的 REST 端点的外部客户端有意义。

更新 1

这是我的 securityContext.xml 的摘录,它定义了两种身份验证方案:

<!-- Authenticating REST APIs -->
<security:http pattern="/rest/**" use-expressions="false">
    <security:intercept-url pattern="/nltools/**" access="IS_AUTHENTICATED_FULLY" />
    <security:custom-filter before="BASIC_AUTH_FILTER" ref="authValidationFilter" />
    <security:http-basic/>
</security:http>

<!-- SAML processing endpoints -->
<security:http pattern="/saml/**" entry-point-ref="samlEntryPoint">
    <security:custom-filter before="FIRST" ref="metadataGeneratorFilter" />
    <security:custom-filter before="CSRF_FILTER" ref="samlFilter" />
    <security:custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter" />
</security:http>

<!-- Secured pages with SAML as entry point -->
<security:http entry-point-ref="samlEntryPoint" use-expressions="false">
    <security:csrf />
    <security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
    <security:custom-filter before="FIRST" ref="metadataGeneratorFilter" />
</security:http>

<security:authentication-manager alias="authenticationManager">
    <security:authentication-provider ref="httpBasicAuthenticationProvider" />
    <!-- Register authentication manager for SAML provider -->
    <security:authentication-provider ref="samlAuthenticationProvider"/>
    <!-- Register authentication manager for administration UI --> 
    <security:authentication-provider>
        <security:user-service id="adminInterfaceService">
            <security:user name="admin" password="admin" authorities="ROLE_ADMIN"/>
        </security:user-service>
    </security:authentication-provider>
</security:authentication-manager> 

我不确定如何执行 SecurityContextPersistenceFilter。我应该为我的/rest/** 模式覆盖并添加其中一个过滤器吗?

更新 2

这是调用后端的 JS 代码(React):

  return Promise.resolve().then(() => 
      return request.post('/rest/v1/projects')
       .send(data)
       .then((success) => 
          console.log('success!', success);
          var projectName = success.body.name;
          var projectId = success.body.id;
          
          self.props.dispatch( projectActions.addNewProject(
            projectId,
            projectName
          ));
        
          self.props.dispatch( appActions.displayGoodRequestMessage( projectName + " Saved") ); 
          self.props.dispatch( projectActions.fetchProject( projectId ) ); 
          self.props.router.push('/projects');

      

现在 JS 代码可以选择发送与该域相关联的所有 cookie,这样后端就可以获取 JSESSION ID cookie,但是,JS 不这样做,我认为它不是正确的做法。

另一方面,如果我在浏览器中执行 https://mydomain/rest/v1/projects 并且只要我登录,我就会得到结果,因为这一次当我的过滤器检查有效的 HTTP 会话时,它可以从请求中获取会话使用request.getSession(false),尽管当 JS 调用 API 时情况并非如此。它变成了一个完全不同的用户代理。

更新 3

根据@Vladimír Schäfer 的建议,我可以稍微更改上面的 JS 代码以将 cookie 发送为 .withCredentials() 并通过后端进行身份验证,而无需执行任何特殊操作

  return Promise.resolve().then(() => 
      return request.post('/rest/v1/projects')
       .withCredentials()
       .send(data)
       .then((success) => 
          console.log('success!', success);
          var projectName = success.body.name;
          var projectId = success.body.id;
          
          self.props.dispatch( projectActions.addNewProject(
            projectId,
            projectName
          ));
        
          self.props.dispatch( appActions.displayGoodRequestMessage( projectName + " Saved") ); 
          self.props.dispatch( projectActions.fetchProject( projectId ) ); 
          self.props.router.push('/projects');

      

【问题讨论】:

【参考方案1】:

只要 REST API 是前端用于使用 Spring Security 进行身份验证的同一应用程序的一部分,它就可以访问 JSESSIONID,因此可以访问 Spring Security 的上下文,其中包含有关已验证用户的所有信息.因此不需要任何额外的身份验证机制。

如果您在处理 Jersey 呼叫时执行过滤器SecurityContextPersistenceFilter,您将能够使用以下方法访问安全上下文:

SecurityContextHolder.getContext().getAuthentication();

SecurityContextPersistenceFilter 使用其配置的存储库来获取 Authentication 对象并将其存储在 SecurityContextHolder 中。看看它默认使用的HttpContextRepository。在那里你会发现存储的安全上下文是密钥SPRING_SECURITY_CONTEXT下的HttpSession,所以你也可以直接获取它。

当然,您也可以使用 Spring Security 在您的 REST API 上强制执行身份验证和授权,就像您在前端所做的那样 - 然后一切都会为您处理。

【讨论】:

我已经用我的 securityContext.xml 的摘录更新了我的问题。我不确定我是否理解您的意思是执行 SecurityContextPeristenceFilter。 我从您的问题中推测您没有使用 Spring Security 对您的 REST 进行身份验证。但你是。您的 REST 调用已经需要身份验证,您的前端将按原样发送 JSESSIONID,您无需再做任何事情 - 除非您想让您自己的应用程序之外的第 3 方访问 REST API(例如 OAuth发挥作用)。 感谢您迄今为止的意见。我再次使用调用 REST API 的 javascript 代码摘录更新了我的问题。就您而言,将来当我想切换到 OAUTH2 时,我确实希望 REST 端点暴露在外面。 只需让 React 发送 cookie。这样做并没有错,这些 API 请求与浏览器在从一个页面切换到另一个页面时所做的正常调用没有什么不同,在这种情况下发送 cookie 会产生安全隐患。 谢谢。不过我还有一个问题。如果您注意到我添加了&lt;security:http-basic/&gt; 作为我的 REST API 调用的安全性。我添加了一个过滤器authValidationFilter 进行身份验证。但是,作为&lt;security:http-basic/&gt; 的一部分,我需要为AuthenticationManager 提供单独的AuthenticationProvider。我真的希望我的过滤器能够进行身份验证。现在如果我不给&lt;security:http-basic/&gt;,那么Spring 会抱怨它找不到入口点。怎么能解决呢?因为明天我想添加 OAuth 而我不想添加 /saml/** 过滤器链。

以上是关于从经过 SAML 验证的 UI 验证 AJAX API 调用的主要内容,如果未能解决你的问题,请参考以下文章

ASP.Net Core SAML 身份验证

如何创建 SAMLAuthenticationToken 并将其发送到 SAML 身份验证提供程序

使用 SAML 对 Spring Boot 应用程序进行身份验证的问题

我如何才能完全信任 SAML IDP?

使用ruby-saml从TestShib中“在Shibboleth SSO身份验证请求中没有给出providerId参数”

使用基于 SAML 的基本身份验证进行身份验证?