2016-12-23 24 views
1

カスタムTokenValidationHandlerを使用してOAuthを使用してADFS 3.0に対して正常に認証されました。WCF用のJWTトークンをSAMLトークンに変換する方法

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検証を無効にする理由です)。

これで、一部のWCFサービスでこのJWTトークンを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トークンを要求するソープ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> 

答えて

1

重要なことは、あなたがclaimsprincipalと背中にトークンを変換するためにセキュリティトークンハンドラを使用することができるということです。したがって、あなたの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; 

IDを取得したら、SecurityTokenDescriptorを作成します。これは次のようになります。

SecurityTokenDescriptor descriptor = new SecurityTokenDescriptor 
      { 
       AppliesToAddress = "realm", 
       TokenIssuerName = "DoNotTrustThisIssuer", 
       EncryptingCredentials = null, 
       Subject = identity, 
       Lifetime = new Lifetime(DateTime.UtcNow, DateTime.UtcNow.AddDays(1)) 
      }; 

問題のある部分は、取得する必要があるSigninKeyです。 STSに属しているので、通常は持っていません。これは、SAML2にJWTを変換

var handler2 = new Saml2SecurityTokenHandler(); 
var saml2Token = handler2.CreateToken(descriptor); 

:最後に、あなたは今、あなたが好きなsecuritytokenhandlerを使用したい任意のトークンにこの記述子を変換することができます。しかし、私が言ったように、あなたの秘密鍵があなたの広告で使用されている場合にのみ、有効な署名を生成することができます。

関連する問題