正在对来自 UI 的 AJAX API 调用进行身份验证,后者已通过 SAML 身份验证
Authenticating AJAX API calls from the UI which is SAML authenticated
我们的应用程序有 2 个部分。
- 用 Java 8 编写的后端使用 Jersey 2.0 公开不同的 REST 端点。
- UI 这是一个使用 React 和其他节点模块构建的单页应用程序。
Web 界面使用 Okta 支持的 SAML 2.0 身份验证作为身份提供者。后端创建 HTTP 会话并在 cookie 中发送 JSESSIONID。
现在 UI 调用 REST 端点来显示数据。我们需要向我们的 REST API 添加一个身份验证层,我已经在此处 .
提出了一个单独的问题
我在这里的具体问题是,我可以从 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');
}
只要 REST API 是 front-end 用于 Spring 安全身份验证的同一应用程序的一部分,它就可以访问 JSESSIONID,因此可以访问上下文Spring 安全性,其中包含有关经过身份验证的用户的所有信息。因此不需要任何额外的身份验证机制。
如果您在处理 Jersey 调用时执行过滤器 SecurityContextPersistenceFilter
,您将能够使用以下方法访问安全上下文:
SecurityContextHolder.getContext().getAuthentication();
SecurityContextPersistenceFilter
使用其配置的存储库获取身份验证对象并将其存储在 SecurityContextHolder 中。看看它默认使用的 HttpContextRepository 。在那里你会发现security context是存储在keySPRING_SECURITY_CONTEXT
下的HttpSession,所以你也可以直接fetch它
当然,您也可以使用 Spring 安全性在您的 REST API 上强制执行身份验证和授权,就像您在 front-end 上所做的那样 - 然后一切都会为您处理.
我们的应用程序有 2 个部分。
- 用 Java 8 编写的后端使用 Jersey 2.0 公开不同的 REST 端点。
- UI 这是一个使用 React 和其他节点模块构建的单页应用程序。
Web 界面使用 Okta 支持的 SAML 2.0 身份验证作为身份提供者。后端创建 HTTP 会话并在 cookie 中发送 JSESSIONID。
现在 UI 调用 REST 端点来显示数据。我们需要向我们的 REST API 添加一个身份验证层,我已经在此处
我在这里的具体问题是,我可以从 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');
}
只要 REST API 是 front-end 用于 Spring 安全身份验证的同一应用程序的一部分,它就可以访问 JSESSIONID,因此可以访问上下文Spring 安全性,其中包含有关经过身份验证的用户的所有信息。因此不需要任何额外的身份验证机制。
如果您在处理 Jersey 调用时执行过滤器 SecurityContextPersistenceFilter
,您将能够使用以下方法访问安全上下文:
SecurityContextHolder.getContext().getAuthentication();
SecurityContextPersistenceFilter
使用其配置的存储库获取身份验证对象并将其存储在 SecurityContextHolder 中。看看它默认使用的 HttpContextRepository 。在那里你会发现security context是存储在keySPRING_SECURITY_CONTEXT
下的HttpSession,所以你也可以直接fetch它
当然,您也可以使用 Spring 安全性在您的 REST API 上强制执行身份验证和授权,就像您在 front-end 上所做的那样 - 然后一切都会为您处理.