我们使用OAuth使用自定义TokenValidationHandler成功验证了ADFS 3.0。
public class TokenValidationHandler : DelegatingHandler
{
private const string JwtAccessTokenCookieName = "jwt_access_token";
private static readonly string adfsUrl = ConfigurationManager.AppSettings["oauth2.adfsUrl"];
private static readonly string clientId = ConfigurationManager.AppSettings["oauth2.clientId"];
private static readonly string redirectUrl = ConfigurationManager.AppSettings["oauth2.redirectUrl"];
private static readonly string rptIdentifier = ConfigurationManager.AppSettings["oauth2.relyingPartyTrustIdentifier"];
private AdfsMetadata adfsMetaData;
public TokenValidationHandler()
{
string stsMetadataAddress = string.Format(CultureInfo.InvariantCulture, $"{adfsUrl}/federationmetadata/2007-06/federationmetadata.xml");
adfsMetaData = new AdfsMetadata(stsMetadataAddress);
}
// SendAsync is used to validate incoming requests contain a valid access token, and sets the current user identity
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
using (HttpResponseMessage responseMessage = new HttpResponseMessage())
{
string jwtToken;
if (HasNoJWTAccessToken(request, out jwtToken))
{
string authorizationCode;
if (HasNoAuthorizationCode(request, out authorizationCode))
{
return RedirectToADFSLoginScreen(request);
}
var responseTokenAsJson = await GetAccessToken(cancellationToken, authorizationCode);
return RedirectToAppWithAccessTokenInCookie(request, responseTokenAsJson);
}
try
{
var tokenHandler = new JwtSecurityTokenHandler { TokenLifetimeInMinutes = 60 };
var validationParameters = new TokenValidationParameters
{
ValidIssuer = adfsMetaData.Issuer,
IssuerSigningKeys = adfsMetaData.SigningTokens.Select(token => new X509SecurityKey(token.Certificate)),
ValidateAudience = false,
SaveSigninToken = true
};
try
{
Microsoft.IdentityModel.Tokens.SecurityToken valdidationtoken;
// Validate token
ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwtToken, validationParameters, out valdidationtoken);
//set the ClaimsPrincipal on the current thread.
Thread.CurrentPrincipal = claimsPrincipal;
if (HttpContext.Current != null)
{
HttpContext.Current.Items["jwtTokenAsString"] = jwtToken;
HttpContext.Current.Items["jwtTokenAsSecurityToken"] = valdidationtoken;
HttpContext.Current.User = claimsPrincipal;
}
return await base.SendAsync(request, cancellationToken);
}
catch (Exception exception)
{
responseMessage.StatusCode = HttpStatusCode.Unauthorized;
return new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
Content = new StringContent(exception.Message)
};
}
}
catch (Exception w)
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent(w.Message)
};
}
}
}
private static async Task<JObject> GetAccessToken(CancellationToken cancellationToken, string authorizationCode)
{
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
HttpClient httpClient = new HttpClient();
var httpResponseMessage = await httpClient.PostAsync(new Uri($"{adfsUrl}/adfs/oauth2/token"), GenerateTokenRequestContent(authorizationCode), cancellationToken);
var responseContent = await httpResponseMessage.Content.ReadAsStringAsync();
JObject responseToken = JObject.Parse(responseContent);
return responseToken;
}
private static HttpResponseMessage RedirectToADFSLoginScreen(HttpRequestMessage request)
{
var requestUriAsString = request.RequestUri.ToString();
var redirectResponse = new HttpResponseMessage(HttpStatusCode.Moved);
redirectResponse.Headers.Location =
new Uri($"{adfsUrl}/adfs/oauth2/authorize?response_type=code&client_id={clientId}&redirect_uri={HttpUtility.UrlEncode(redirectUrl)}&resource={HttpUtility.UrlEncode(rptIdentifier)}&state={GZipUtils.Compress(requestUriAsString)}");
return redirectResponse;
}
private static HttpResponseMessage RedirectToAppWithAccessTokenInCookie(HttpRequestMessage request, JObject responseTokenAsJson)
{
var cookie = CreateCookieWithAccessToken(request, responseTokenAsJson);
var urlToRedirectTo = GZipUtils.Decompress(request.GetQueryNameValuePairs().FirstOrDefault(param => param.Key == "state").Value);
var redirectResponse = new HttpResponseMessage(HttpStatusCode.Redirect);
redirectResponse.Headers.Location = new Uri(urlToRedirectTo);
redirectResponse.Headers.AddCookies(new[] { cookie });
return redirectResponse;
}
private static CookieHeaderValue CreateCookieWithAccessToken(HttpRequestMessage request, JObject responseTokenAsJson)
{
var compressedToken = GZipUtils.Compress(responseTokenAsJson["access_token"].ToString());
var cookie = new CookieHeaderValue(JwtAccessTokenCookieName, compressedToken)
{
Expires = DateTimeOffset.Now.AddSeconds(Int16.Parse(responseTokenAsJson["expires_in"].ToString())),
Domain = request.RequestUri.Host,
Path = "/"
};
return cookie;
}
private static FormUrlEncodedContent GenerateTokenRequestContent(string authorizationCode)
{
return new FormUrlEncodedContent(
new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("grant_type","authorization_code"),
new KeyValuePair<string, string>("client_id", clientId),
new KeyValuePair<string, string>("code", authorizationCode),
new KeyValuePair<string, string>("redirect_uri", redirectUrl),
});
}
private bool HasNoAuthorizationCode(HttpRequestMessage request, out string authorizationCode)
{
authorizationCode = request.GetQueryNameValuePairs().FirstOrDefault(param => param.Key == "code").Value;
return string.IsNullOrEmpty(authorizationCode);
}
// Reads the token from the authorization header on the incoming request
static bool HasNoJWTAccessToken(HttpRequestMessage request, out string token)
{
if (HasNoJWTAccessTokenInAuthorizationHeader(request, out token) && HasNoJWTAccessTokenInSecureCookie(request, out token))
{
return true;
}
return false;
}
private static bool HasNoJWTAccessTokenInSecureCookie(HttpRequestMessage request, out string token)
{
token = null;
if (!request.Headers.GetCookies(JwtAccessTokenCookieName).Any())
{
return true;
}
var cookieHeaderValue = request.Headers.GetCookies(JwtAccessTokenCookieName).FirstOrDefault();
if (cookieHeaderValue != null)
{
token = GZipUtils.Decompress(cookieHeaderValue[JwtAccessTokenCookieName].Value);
}
if (token == null)
{
return true;
}
return false;
}
private static bool HasNoJWTAccessTokenInAuthorizationHeader(HttpRequestMessage request, out string token)
{
token = null;
if (!request.Headers.Contains("Authorization"))
{
return true;
}
string authHeader = request.Headers.GetValues("Authorization").FirstOrDefault();
// Verify Authorization header contains 'Bearer' scheme
token = authHeader.StartsWith("Bearer ", StringComparison.Ordinal) ? authHeader.Split(' ')[1] : null;
if (token == null)
{
return true;
}
return false;
}
}
注意:这仍在进行中(这就是我们禁用ssl验证的原因)。
现在我们需要将此JWT令牌转换为某些WCF服务的SAML令牌。重要提示:我们无法对WCF服务进行任何更改,因为它们不受我们控制。这意味着此解决方案不适用于我们:How to use JWT tokens with WCF and WIF?
我可以通过bootstrapcontext访问原始的JWT令牌。
ClaimsPrincipal principal = (ClaimsPrincipal) Thread.CurrentPrincipal;
var bootstrapContext = principal.Identities.First().BootstrapContext; //=> contains original JWT token.
System.IdentityModel.Tokens.SecurityToken token;
var rstr = RequestSecurityToken(out token); // => need help here
var channelFactory = new ChannelFactory<T>(endpointConfigurationName);
return channelFactory.CreateChannelWithActAsToken(token);
这样做的最佳方法是什么?
转到WCF的当前配置(我们从另一方收到并且不受我们控制)如下:
<security authenticationMode="IssuedTokenOverTransport" messageSecurityVersion="WSSecurity11WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10">
<issuedTokenParameters keyType="SymmetricKey" tokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0">
<issuer address="https://fs.contoso-int.be/adfs/services/trust/13/kerberosmixed" binding="customBinding" bindingConfiguration="Contoso.Federation.Bindings.Http.KerberosMixed">
<identity>
<servicePrincipalName value="host/fs.contoso-int.be" />
</identity>
</issuer>
<issuerMetadata address="https://fs.contoso-int.be/adfs/services/trust/mex" />
<claimTypeRequirements>
<add claimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" />
<add claimType="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" isOptional="true" />
</claimTypeRequirements>
<additionalRequestParameters>
<trust:TokenType xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0</trust:TokenType>
<trust:KeyType xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey</trust:KeyType>
<trust:KeySize xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">256</trust:KeySize>
<trust:KeyWrapAlgorithm xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p</trust:KeyWrapAlgorithm>
<trust:EncryptWith xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2001/04/xmlenc#aes256-cbc</trust:EncryptWith>
<trust:SignWith xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2000/09/xmldsig#hmac-sha1</trust:SignWith>
<trust:CanonicalizationAlgorithm xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2001/10/xml-exc-c14n#</trust:CanonicalizationAlgorithm>
<trust:EncryptionAlgorithm xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2001/04/xmlenc#aes256-cbc</trust:EncryptionAlgorithm>
</additionalRequestParameters>
</issuedTokenParameters>
<localClientSettings detectReplays="false" />
<localServiceSettings detectReplays="false" />
</security>
我已经尝试通过RequestSecurityToken创建SAML令牌,但是当我添加ActAs SecurityTokenElement时,我从ADFS收到InvalidSecurityToken。
请求SAML令牌的Soap-enveloppe如下:
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<s:Header>
<a:Action s:mustUnderstand="1">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</a:Action>
<a:MessageID>urn:uuid:64f34b8a-92bf-4da0-9571-d436ab24d5d1</a:MessageID>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand="1">https://fs.contoso-int.be/adfs/services/trust/13/kerberosmixed</a:To>
<o:Security xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" s:mustUnderstand="1">
<u:Timestamp u:Id="_0">
<u:Created>2016-12-23T15:11:28.885Z</u:Created>
<u:Expires>2016-12-23T15:16:28.885Z</u:Expires>
</u:Timestamp>
<o:BinarySecurityToken u:Id="uuid-abcb8b3a-61e0-4c9d-a6f3-71ad407b838d-1" ValueType="http://docs.oasis-open.org/wss/oasis-wss-kerberos-token-profile-1.1#GSS_Kerberosv5_AP_REQ" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">YIIGmgYJKoZIhvcSAQICAQB...</o:BinarySecurityToken>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"/>
<Reference URI="#_0">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>1qIxIurrORfpzYMl3AHVmVNGJ9Y=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>bCacOSkpjauc+QpMbUqCQ/aQE20=</SignatureValue>
<KeyInfo>
<o:SecurityTokenReference>
<o:Reference URI="#uuid-abcb8b3a-61e0-4c9d-a6f3-71ad407b838d-1"/>
</o:SecurityTokenReference>
</KeyInfo>
</Signature>
</o:Security>
</s:Header>
<s:Body>
<trust:RequestSecurityToken xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:Address>urn:co:feat</wsa:Address>
</wsa:EndpointReference>
</wsp:AppliesTo>
<trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey</trust:KeyType>
<tr:ActAs xmlns:tr="http://docs.oasis-open.org/ws-sx/ws-trust/200802">
<wsse:BinarySecurityToken xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" ValueType="urn:ietf:params:oauth:token-type:jwt" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">ZXlKMGVYQWlPaUpLVjFRaUxDSmhi...</wsse:BinarySecurityToken>
</tr:ActAs>
<trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType>
</trust:RequestSecurityToken>
</s:Body>
</s:Envelope>
答案 0 :(得分:2)
关键是您可以使用安全令牌处理程序将令牌转换为claimprincipal并返回。因此,您需要将您的jwt令牌转换为声明主体。你通常会这样做
var handler = new JwtSecurityTokenHandler();
SecurityToken token;
var principal = handler.ValidateToken("your.jwt.part3", new TokenValidationParameters
{
ValidateAudience = false,
/* be creative with the parameters here */
}, out token);
var identity = principal.Identity as ClaimsIdentity;
获得身份后,即可创建SecurityTokenDescriptor。这是这样的:
SecurityTokenDescriptor descriptor = new SecurityTokenDescriptor
{
AppliesToAddress = "realm",
TokenIssuerName = "DoNotTrustThisIssuer",
EncryptingCredentials = null,
Subject = identity,
Lifetime = new Lifetime(DateTime.UtcNow, DateTime.UtcNow.AddDays(1))
};
有问题的部分是你需要获得的SigninKey。通常你没有它,因为它属于STS。最后,您现在可以使用任何所需的securitytokenhandler将此描述符转换为您想要的任何标记:
var handler2 = new Saml2SecurityTokenHandler();
var saml2Token = handler2.CreateToken(descriptor);
这会将jwt转换为saml2。但是,正如我所说,如果您拥有adfs使用的私钥,则只能生成有效签名。