(参考)これまでのポスト
- [AD FS] Windows Server Technical PreviewのAD FSを試す
http://idmlab.eidentity.jp/2014/10/ad-fs-windows-server-technical.html
- [AD FS]Windows Server Technical Previewで追加された機能~PowerShell編
http://idmlab.eidentity.jp/2015/03/ad-fswindows-server-technical.html
今回はその中でもOAuth2.0への対応について現状をまとめておきます。
(OpenID Connectにも対応しているのですが、そちらは次回にでも)
ポイントは、以下の3点です。
①Confidential Clientの作成が出来るようになった
②Implicit/Client Credentialsに対応した
※ちなみにResource Owner Password Credentialsは未サポートです
③Client AuthenticationにJWTが使えるようになった
■ポイント①:Confidential Clientを作成できるようになった
Windows Server 2012R2まではPublic Clientしか作成することが出来ず、client_secretが必要なフロー(client_credentialsなど)には対応していませんでしたが、Add-AdfsClientコマンドレットの拡張によりConfidential Clientを作成することが出来るようになりました。
こちらはこれまでと同じく、Public Clientの作成です。
PS> Add-AdfsClient -ClientId 6c831710-cd6c-11e4-8830-0800200c9a66 -Name TestPublicClient -RedirectUri http://localhost -ClientType Public
次に、新たに追加されたConfidential Clientの作成です。-ClientTypeオプションに[Confidential]を指定し、-GenerateClientSecretオプションを付けることによりclient_secretを生成できます。
※-ClientTypeオプションの値がPublicだと-GenerateClientSecretオプションは使えません。
PS> Add-AdfsClient -ClientId bf7fb880-cd6f-11e4-8830-0800200c9a66 -Name TestConfidentialClient -RedirectUri http://localhost2 -ClientType Confidential -GenerateClientSecret RedirectUri : {http://localhost2/} Name : TestConfidentialClient Description : ClientId : bf7fb880-cd6f-11e4-8830-0800200c9a66 BuiltIn : False Enabled : True ClientType : Confidential ADUserPrincipalName : ClientSecret : buEgBXgYZO5Y7bdk6kjPE9oDLAA1ZRtvSCwm6orc JWTSigningCertificateRevocationCheck : CheckChainExcludeRoot JWTSigningCertificate : {}
ちなみに生成されたClientSecretはこの作成結果画面でしか見れませんので、必ずメモしておきましょう。
(もちろん再生成することも出来ます)
■ポイント②:Implicit/Client Credentialsに対応した
Windows Server 2012R2ではCode Flow一択でしたが、今回のビルドからImplicitおよびClientCredentialsにも対応しています。
おまけですが、まずはこれまでの同じくCode Flowです。
※相変わらずResourceパラメータが必要なので、AD FSに登録したRelying PartyのIdentifierを指定します。これは他のgrant_typeでも同様です。
◆Code Flow
①認可コードを要求します。
https://adfsserver.example.com/adfs/oauth2/authorize?response_type=code&client_id=6c831710-cd6c-11e4-8830-0800200c9a66&redirect_uri=http%3A%2F%2Flocalhost&resource=google.com%2Fa%2Fhoge.example.net
②ユーザ認証後、redirect_uriにリダイレクトされ、GETパラメータで認可コードが取得できます。
http://localhost/?code=QAILg...snip...3r6dSQ
③取得した認可コードをtokenエンドポイントへPOSTします。
https://adfsserver.example.com/adfs/oauth2/token grant_type authorization_code code redirect_uri http://localhost client_id 6c831710-cd6c-11e4-8830-0800200c9a66
④access_tokenが取得できます。
ついでにid_tokenまで返ってきてしまいます。。。
{ access_token: "eyJ0eXAiOiJKV...snip...m1Pu0pRHEqQNr_uilmeMZ_Z1i2lM3hHDFGLmwg" token_type: "bearer" expires_in: 3600 id_token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJ...snip...P662gJhkaquYh3vvW9lvxZAqbThO6Oql8hRw" }
取得できたaccess_token、id_tokenをデコードするとこんな感じです。
- access_token
{ "aud": "microsoft:identityserver:google.com/a/hoge.exmample.net", "iss": "http://adfsserver.example.com/adfs/services/trust", "iat": 1426683515, "exp": 1426687115, "sub": "admin@example.com", "apptype": "Public", "appid": "6c831710-cd6c-11e4-8830-0800200c9a66", "authmethod": "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", "auth_time": "2015-03-18T12:58:33.642Z", "ver": "1.0" }
- id_token
{ "aud": "6c831710-cd6c-11e4-8830-0800200c9a66", "iss": "http://adfsserver.example.com/adfs/services/trust", "iat": 1426683515, "exp": 1426687115, "auth_time": "2015-03-18T12:58:33.642Z", "sub": "yMpiFIT0ydzHFXmiKhjSPqiqFDvSHnYlGctCv3NyAas=", "ver": "1.0" }
ちなみにSet-AdfsRelyingPartyTrustコマンドレットを使ってResource(Relying Party)の設定のIssueOAuthRefreshTokensToに[AllDevices]を設定することでrefresh_tokenを発行することも出来ます。
{ access_token: "eyJ0eXAiOiJKV1...snip...2Qd0FZ5J_zORnxOyvj1MxQsVCwMMmlNg" token_type: "bearer" expires_in: 3600 refresh_token: "NLGBlhSJjs7_zvjTkKtnnGY...snip...OSlKvCA" id_token: "eyJ0eXAiOiJKV1QiLCJh...snip...0tr3iS_RIxFiNZBxOfvcBlzV9u3HA" }
これでgrant_typeにrefresh_tokenをセットしてtokenエンドポイントにrefresh_tokenをPOSTすることで再度ユーザ認証を求められることなくaccess_tokenを取得することが出来ます。
◆implicit flow
いよいよ新しくサポートされたImplicit Flowです。
①Authorizationエンドポイントにresponse_type=tokenを付けてaccess_tokenをリクエストします。
https://adfsserver.example.com/adfs/oauth2/authorize?response_type=token&client_id=6c831710-cd6c-11e4-8830-0800200c9a66&redirect_uri=http%3A%2F%2Flocalhost&resource=google.com%2Fa%2Fhoge.example.net
②ユーザ認証が行われるため、ログオンするとredirect_uriにリダイレクトされ、フラグメントにaccess_tokenが返ってきます。
http://localhost/#access_token=eyJ0eXAiO...snip...MP_bQsjj7Jvp81j-qztASSrzzAypA&token_type=bearer&expires_in=3600
CodeFlowと同じく取得したaccess_tokenをデコードするとこんな感じになります。当然同じようなものになりますが。。。
{ "aud": "microsoft:identityserver:google.com/a/hoge.example.net", "iss": "http://adfsserver.example.com/adfs/services/trust", "iat": 1426683944, "exp": 1426687544, "sub": "admin@example.com", "apptype": "Public", "appid": "6c831710-cd6c-11e4-8830-0800200c9a66", "authmethod": "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", "auth_time": "2015-03-18T13:05:42.250Z", "ver": "1.0" }
◆Client Credentials Flow
次にClient Credentialsです。こちらも新しくサポートされました。
①tokenエンドポイントにPOSTします。
その際、client_id/client_secretを一緒にPOSTすることでクライアント認証を行います。
このあたりの認証方法が各社バラバラなのが困りますね。。。
POST https://adfsserver.example.com/adfs/oauth2/token grant_type client_credentials resource urn:dummyapi client_id bf7fb880-cd6f-11e4-8830-0800200c9a66 client_secret bu....snip....rc
・・・。unauthorized_clientエラーが返ってきました。。。
{ error: "unauthorized_client" error_description: "MSIS9605: The client is not allowed to access the requested resource." }
Resource(Relying Party)の設定をGet-AdfsRelyingPartyTrustで確認すると、
AllowedClientTypes : Public
となっています。
②今回使うClientはclient_secretを使うのでConfidential Clientなので、Resourceの設定を変更する必要があります。Set-AdfsRelyingPartyTrustでAllowClientTypesにConfidentialを設定して、再度tokenエンドポイントにPOSTします。
今度はちゃんとaccess_tokenが取得できました。
◆Resource Owner Password Credentials Flow
最後に一応Resource Owner Password Credentialsです。
①tokenエンドポイントにgrant_type:passwordでPOSTします。
POST https://adfsserver.example.com/adfs/oauth2/token grant_type password username nfujie@example.com password P@ssw0rd resource urn:dummyapi client_id bf7fb880-cd6f-11e4-8830-0800200c9a66 client_secret buEgB...snip..6orc
②残念ながら[unsupport_grant_type]といって怒られてしまいます。
{ error: "unsupported_grant_type" error_description: "MSIS9611: The authorization server does not support the requested 'grant_type'. The authorization server only supports 'authorization_code' or 'refresh_token' as the grant type." }
■ポイント③:Client AuthenticationにJWTが使えるようになった
JWT(JSON Web Token) Profile for OAuth 2.0 Client Authentication and Authorization Grants(http://self-issued.info/docs/draft-ietf-oauth-jwt-bearer-06.html)への対応ですね。
ちなみにまだAuthorization Grantについては試していませんが、多分対応している気がします(マイクたん的に)
先ほどClient Credentials FlowでのClient認証を行うためにclient_idとclient_secretをリクエストに入れましたが、client_secretを毎回通信に入れるのが嫌な場合に秘密鍵で署名したJWTを使ってクライアント認証をする仕組みなので、Set-AdfsClientコマンドレットでクライアントに公開鍵を設定しておき、リクエストのJWT(client_assertion)に対応する秘密鍵で署名します。
①クライアントに公開鍵を設定する
以下の様にしてあらかじめ用意しておいたcerファイルをクライアントに設定します。
PS> $cert=New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("c:\temp\public.cer") PS> Set-AdfsClient -TargetClientId bf7fb880-cd6f-11e4-8830-0800200c9a66 -JWTSigningCertificate $cert
設定した結果をGet-AdfsClientで確認すると、JWTSigningCertificateパラメータに証明書の情報が表示されます。
PS> Get-AdfsClient -Name TestConfidentialClient RedirectUri : {http://localhost2/} Name : TestConfidentialClient Description : ClientId : bf7fb880-cd6f-11e4-8830-0800200c9a66 BuiltIn : False Enabled : True ClientType : Confidential ADUserPrincipalName : ClientSecret : ******** JWTSigningCertificateRevocationCheck : CheckChainExcludeRoot JWTSigningCertificate : {[Subject] CN=adfsserver.example.com, OU=hoge, OU=hoge [Issuer] CN=hoge, hoge, L=hoge, S=hoge, C=JP [Serial Number] 00D6...snip...6116767C [Not Before] 2/26/2015 12:00:00 AM [Not After] 2/25/2018 11:59:59 PM [Thumbprint] 51A5...snip....3F3A23DA }
②リクエストにセットするclient_assertionを作成し、秘密鍵で署名する
ADALを使えば割と楽に作れますが、今回はロジックをわかりやすくするため、生でコードを書いています。
こんなコードでclient_assertionを取得します。
static string Get_Client_Assertion(){ // 秘密鍵ファイルとパスフレーズをセット var certificate = new X509Certificate2( "c:\temp\private.p12", "secret", X509KeyStorageFlags.Exportable); // Credentialの生成 var credentials = new X509SigningCredentials( certificate, new SecurityKeyIdentifier( new NamedKeySecurityKeyIdentifierClause( "kid", "51...snip...A23DA(thumbprint)"))); // token lifetime var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); var issueTime = DateTime.Now; var iat = (long)issueTime.ToUniversalTime().Subtract(utc0).TotalSeconds; var exp = (long)issueTime.ToUniversalTime().AddMinutes(55).Subtract(utc0).TotalSeconds; var issuedTime = DateTime.UtcNow; var expiresTime = issuedTime.AddMinutes(5); var epoch = new DateTime(1970, 01, 01, 0, 0, 0); // JWT Headerの生成 var header = new { alg = "RS256", typ = "JWT", x5t = "Ua...snip...9o(thumbprint)" }; var headerSerialized = JsonConvert.SerializeObject(header, Formatting.None); var headerBytes = Encoding.UTF8.GetBytes(headerSerialized); var headerEncoded = Base64UrlEncode(headerBytes); // JWT Payloadの生成 var payload = new { sub = "bf7fb880-cd6f-11e4-8830-0800200c9a66(client_id)", iss = "bf7fb880-cd6f-11e4-8830-0800200c9a66(client_id)", aud = "https://adfsserver.example.com/adfs/oauth2/token", jti = "admin@example.com", exp = exp, iat = iat }; var payloadSerialized = JsonConvert.SerializeObject(payload,Formatting.None); var payloadBytes = Encoding.UTF8.GetBytes(payloadSerialized); var payloadEncoded = Base64UrlEncode(payloadBytes); // 署名の生成 var x509Key = new X509AsymmetricSecurityKey(certificate); RSACryptoServiceProvider rsa = x509Key.GetAsymmetricAlgorithm(SecurityAlgorithms.RsaSha256Signature, true) as RSACryptoServiceProvider; RSACryptoServiceProvider newRsa = null; newRsa = GetCryptoProviderForSha256(rsa); using (SHA256Cng sha = new SHA256Cng()) { return headerEncoded + "." + payloadEncoded + "." + Base64UrlEncode( newRsa.SignData(Encoding.UTF8.GetBytes(headerEncoded + "." + payloadEncoded), sha)); } }
③生成したclient_assertionを使ってaccess_tokenを要求する
tokenエンドポイントにclient_assertionを含むパラメータをPOSTします。
POST https://adfsserver.example.com/adfs/oauth2/token grant_type client_credentials resource urn:dummyapi client_assertion_type urn:ietf:params:oauth:client-assertion-type:jwt-bearer client_assertion eyJhbGciO...snip...6r3LZa-H2avPokc4sp4A
client_secretを使わないので少し気分的に楽になります。
④access_tokenが返ってくる
ここは先に解説したClient Credentials Flowの結果と変わりません。
とりあえずここまでです。
他にもOAuth2.0 JWT Bearer TokenフローやOpenID Connect対応についてもある程度試してはいるので、また書きたいと思います、
後は、次のビルドが出たらもう少し試してみたいと思います。
0 件のコメント:
コメントを投稿