Pingfederate opentoken 模块 CORS 请求 returns 302 而不是 200
Pingfederate opentoken module CORS request returns 302 instead of 200
我们正在使用 Ping Federate 来保护两个 Web 服务器(两个都是 IIS,并且都使用 IIS 集成工具包或 Ping 的 opentoken 模块进行保护)。一台服务器托管一个 WEB API 应用程序,另一台服务器托管一个网页。 Web API 应用程序启用了 CORS。
该网页向 API 服务器发出 Ajax post 带有 json 数据的请求。这会导致浏览器启动预检选项请求。在 API 服务器上,Ping 模块拦截了这个不包含凭据的请求(规范说预检选项请求不应该包含凭据)并且 return 在 Web [=19] 之前进行 302 重定向=] 代码可以在它应该 return a 200.
的时候处理它
我目前唯一的猜测是制作一个处理选项请求的自定义模块,并将其安装在 opentoken 模块之前。还有其他 possible/better 解决方案吗?
这是 PING Federate IIS Agent/integration 套件实现的错误吗?
我同意OP。当前的 W3C 推荐 (https://www.w3.org/TR/cors/#preflight-request) 明确指出 在 preflight/OPTIONS 请求中排除 用户凭据。因此,OPTIONS 请求应该允许匿名请求。获取指向身份提供者 (IdP) 的 302 告诉我它没有遵循建议。
(这只对 PING 服务器本身有帮助,集成工具包仍然需要允许匿名 OPTIONS 请求。)
Ping Federate 在码头配置文件中返回更多配置设置:
<security-constraint>
<web-resource-collection>
<web-resource-name>Disable TRACE OPTIONS HEAD</web-resource-name>
<url-pattern>/*</url-pattern>
<http-method>TRACE</http-method>
<http-method>OPTIONS</http-method>
</web-resource-collection>
<auth-constraint/>
</security-constraint>
我没有等待 PING,而是在他们的 .NET 集成之上放置了一个 IAuthorizationFilter Kit/Agent。像这样的自定义过滤器的好处是您可以更好地控制 API 端点的安全要求。
在编写过滤器时,我使用了以下参考资料:
- http://www.asp.net/web-api/overview/security/authentication-filters
https://msdn.microsoft.com/en-us/magazine/dn781361.aspx
使用开放令牌;
使用 PF.SAML.Result;
使用系统;
使用 System.Collections.Generic;
使用 System.Configuration;
使用 System.Linq;
使用 System.Net.Http;
使用 System.Net.Http.Headers;
使用 System.Security.Claims;
使用 System.Text;
使用 System.Threading;
使用 System.Threading. 任务;
使用 System.Web.Http.Filters;
命名空间PF.SAML.Filters
{
public class PingAuthenticationAttribute:IAuthenticationFilter
{
public bool AllowMultiple { get { return false; } }
// http://www.asp.net/web-api/overview/security/authentication-filters
// https://msdn.microsoft.com/en-us/magazine/dn781361.aspx
public async Task AuthenticateAsync( HttpAuthenticationContext context, CancellationToken cancellationToken )
{
await Task.Run( () => {
/*
* Look for credentials in the request.
* If there are no credentials, do nothing and return (no-op).
* If there are credentials but the filter does not recognize the authentication scheme, do nothing and return (no-op). Another filter in the pipeline might understand the scheme.
* If there are credentials that the filter understands, try to authenticate them.
* If the credentials are bad, return 401 by setting context.ErrorResult.
* If the credentials are valid, create an IPrincipal and set context.Principal.
*/
var opentoken = context.Request.Headers.GetCookies()
.SelectMany( c => c.Cookies )
.Where( c => c.Name == "opentoken" )
.FirstOrDefault();
if( opentoken == null ) return;
var userInfo = getOpenToken( opentoken.Value );
if( userInfo == null ) {
context.ErrorResult = new AuthenticationFailureResult( "Invalid Token", context.Request );
return;
}
var claims = new List<Claim>();
foreach( var item in userInfo ) {
foreach( var value in userInfo[item.Key] ) {
claims.Add( new Claim( item.Key, value ) );
}
}
var id = new ClaimsIdentity( claims, "opentoken" );
var principle = new ClaimsPrincipal( new[] { id } );
context.Principal = principle;
} );
}
public async Task ChallengeAsync( HttpAuthenticationChallengeContext context, CancellationToken cancellationToken )
{
await Task.Run( () => {
var challenge = new AuthenticationHeaderValue( "SAML" );
context.Result = new AddChallengeOnUnauthorizedResult( challenge, context.Result );
} );
}
private MultiStringDictionary getOpenToken( string token )
{
MultiStringDictionary attributes = null;
Configuration.Agent agentConfig = (Configuration.Agent) ConfigurationManager.GetSection( "pfConfigurationGroup/agentConfiguration" );
AgentConfiguration config = new AgentConfiguration
{
CookieDomain = agentConfig.CookieDomain,
CookiePath = agentConfig.CookiePath,
NotBeforeTolerance = agentConfig.NotBeforeTolerance,
ObfuscatePassword = agentConfig.ObfuscatePassword,
RenewUntilLifetime = agentConfig.RenewUntilLifetime,
SecureCookie = agentConfig.SecureCookie,
SessionCookie = agentConfig.SessionCookie,
TokenLifetime = agentConfig.TokenLifetime,
TokenName = agentConfig.TokenName,
UseCookie = agentConfig.UseCookie,
UseSunJCE = agentConfig.UseSunJCE,
UseVerboseErrorMessages = agentConfig.UseVerboseErrorMessages
};
var str = ( config.ObfuscatePassword
? Encoding.UTF8.GetString( Obfuscator.Deobfuscate( agentConfig.Password ) )
: Encoding.ASCII.GetString( Convert.FromBase64String( agentConfig.Password ) ) );
config.SetPassword( str, Token.CipherSuite.AES_128_CBC );
// TODO: Check for token expiration
Agent agent = new Agent( config );
attributes = agent.ReadTokenMultiStringDictionary( token );
return attributes;
}
}
}
我们正在使用 Ping Federate 来保护两个 Web 服务器(两个都是 IIS,并且都使用 IIS 集成工具包或 Ping 的 opentoken 模块进行保护)。一台服务器托管一个 WEB API 应用程序,另一台服务器托管一个网页。 Web API 应用程序启用了 CORS。
该网页向 API 服务器发出 Ajax post 带有 json 数据的请求。这会导致浏览器启动预检选项请求。在 API 服务器上,Ping 模块拦截了这个不包含凭据的请求(规范说预检选项请求不应该包含凭据)并且 return 在 Web [=19] 之前进行 302 重定向=] 代码可以在它应该 return a 200.
的时候处理它我目前唯一的猜测是制作一个处理选项请求的自定义模块,并将其安装在 opentoken 模块之前。还有其他 possible/better 解决方案吗?
这是 PING Federate IIS Agent/integration 套件实现的错误吗?
我同意OP。当前的 W3C 推荐 (https://www.w3.org/TR/cors/#preflight-request) 明确指出 在 preflight/OPTIONS 请求中排除 用户凭据。因此,OPTIONS 请求应该允许匿名请求。获取指向身份提供者 (IdP) 的 302 告诉我它没有遵循建议。
(这只对 PING 服务器本身有帮助,集成工具包仍然需要允许匿名 OPTIONS 请求。) Ping Federate 在码头配置文件中返回更多配置设置:
<security-constraint>
<web-resource-collection>
<web-resource-name>Disable TRACE OPTIONS HEAD</web-resource-name>
<url-pattern>/*</url-pattern>
<http-method>TRACE</http-method>
<http-method>OPTIONS</http-method>
</web-resource-collection>
<auth-constraint/>
</security-constraint>
我没有等待 PING,而是在他们的 .NET 集成之上放置了一个 IAuthorizationFilter Kit/Agent。像这样的自定义过滤器的好处是您可以更好地控制 API 端点的安全要求。
在编写过滤器时,我使用了以下参考资料:
- http://www.asp.net/web-api/overview/security/authentication-filters
https://msdn.microsoft.com/en-us/magazine/dn781361.aspx
使用开放令牌; 使用 PF.SAML.Result; 使用系统; 使用 System.Collections.Generic; 使用 System.Configuration; 使用 System.Linq; 使用 System.Net.Http; 使用 System.Net.Http.Headers; 使用 System.Security.Claims; 使用 System.Text; 使用 System.Threading; 使用 System.Threading. 任务; 使用 System.Web.Http.Filters;
命名空间PF.SAML.Filters { public class PingAuthenticationAttribute:IAuthenticationFilter { public bool AllowMultiple { get { return false; } }
// http://www.asp.net/web-api/overview/security/authentication-filters // https://msdn.microsoft.com/en-us/magazine/dn781361.aspx public async Task AuthenticateAsync( HttpAuthenticationContext context, CancellationToken cancellationToken ) { await Task.Run( () => { /* * Look for credentials in the request. * If there are no credentials, do nothing and return (no-op). * If there are credentials but the filter does not recognize the authentication scheme, do nothing and return (no-op). Another filter in the pipeline might understand the scheme. * If there are credentials that the filter understands, try to authenticate them. * If the credentials are bad, return 401 by setting context.ErrorResult. * If the credentials are valid, create an IPrincipal and set context.Principal. */ var opentoken = context.Request.Headers.GetCookies() .SelectMany( c => c.Cookies ) .Where( c => c.Name == "opentoken" ) .FirstOrDefault(); if( opentoken == null ) return; var userInfo = getOpenToken( opentoken.Value ); if( userInfo == null ) { context.ErrorResult = new AuthenticationFailureResult( "Invalid Token", context.Request ); return; } var claims = new List<Claim>(); foreach( var item in userInfo ) { foreach( var value in userInfo[item.Key] ) { claims.Add( new Claim( item.Key, value ) ); } } var id = new ClaimsIdentity( claims, "opentoken" ); var principle = new ClaimsPrincipal( new[] { id } ); context.Principal = principle; } ); } public async Task ChallengeAsync( HttpAuthenticationChallengeContext context, CancellationToken cancellationToken ) { await Task.Run( () => { var challenge = new AuthenticationHeaderValue( "SAML" ); context.Result = new AddChallengeOnUnauthorizedResult( challenge, context.Result ); } ); } private MultiStringDictionary getOpenToken( string token ) { MultiStringDictionary attributes = null; Configuration.Agent agentConfig = (Configuration.Agent) ConfigurationManager.GetSection( "pfConfigurationGroup/agentConfiguration" ); AgentConfiguration config = new AgentConfiguration { CookieDomain = agentConfig.CookieDomain, CookiePath = agentConfig.CookiePath, NotBeforeTolerance = agentConfig.NotBeforeTolerance, ObfuscatePassword = agentConfig.ObfuscatePassword, RenewUntilLifetime = agentConfig.RenewUntilLifetime, SecureCookie = agentConfig.SecureCookie, SessionCookie = agentConfig.SessionCookie, TokenLifetime = agentConfig.TokenLifetime, TokenName = agentConfig.TokenName, UseCookie = agentConfig.UseCookie, UseSunJCE = agentConfig.UseSunJCE, UseVerboseErrorMessages = agentConfig.UseVerboseErrorMessages }; var str = ( config.ObfuscatePassword ? Encoding.UTF8.GetString( Obfuscator.Deobfuscate( agentConfig.Password ) ) : Encoding.ASCII.GetString( Convert.FromBase64String( agentConfig.Password ) ) ); config.SetPassword( str, Token.CipherSuite.AES_128_CBC ); // TODO: Check for token expiration Agent agent = new Agent( config ); attributes = agent.ReadTokenMultiStringDictionary( token ); return attributes; } }
}