- 追加された行はこの色です。
- 削除された行はこの色です。
「[[Open棟梁 wiki>https://opentouryo.osscons.jp]]」は、「[[Open棟梁Project>https://github.com/OpenTouryoProject/]]」,「[[OSSコンソーシアム .NET開発基盤部会>https://www.osscons.jp/dotNetDevelopmentInfrastructure/]]」によって運営されています。
-[[戻る>汎用認証サイトのファーストステップガイド]]
--[[汎用認証サイトのファーストステップガイド (1)]]
--[[汎用認証サイトのファーストステップガイド (2)]]
--[[汎用認証サイトのファーストステップガイド (3)]]
--汎用認証サイトのファーストステップガイド (4)
--[[汎用認証サイトのファーストステップガイド (5)]]
*目次 [#hddf7023]
#contents
*概要 [#lbdaf1e3]
[[汎用認証サイト(Multi-purpose Authentication Site)]]~
の導入前の評価を行うためのファーストステップガイド。
(4) では、「認証連携コードの実装」を行う。
*汎用認証サイトをセットアップする。 [#m6718c37]
-適当なところに汎用認証サイトをセットアップする。
-[[3 分割テスト>汎用認証サイトのファーストステップガイド (3)#r68f6218]]で行ったように、ポート番号が違うならlocalhostでも良い。
-ここでは、別サーバーのIIS上にデプロイ、セットアップしたので汎用認証サイトは常に実行可能状態にある。
-ローカル開発環境でテストする場合は、必要に応じて、汎用認証サイトをデバッグ起動して実行可能状態にする。
*クライアントのプロジェクトを作成する。 [#l8d51863]
ここでは、クライアントのプロジェクトとして、ASP.NET MVCを選択する。
**ASP.NET MVCのプロジェクトを新規作成する。 [#v7dfdc59]
ASP.NET MVCの(ASP.NET Web Application)プロジェクトを新規作成する。
[メニュー] ---> [新規作成] --->
>[プロジェクト] ---> [ASP.NET Web Application(.NET Framework)]
***新しいプロジェクト [#le7af872]
-任意のパスを指定
-名前を入力
--例えば[WebApplication1]という既定の名称を使用する。
--ソリューション名も同じ名称のままにする。
-[OK]ボタンを押下。
***テンプレート選択 [#s719107d]
テンプレート選択画面で以下のように選択する。
-テンプレートとして、[MVC]を選択する。
-[認証の変更ボタン]を押下し、[認証なし]に変更する。
-[OK]ボタンを押下してプロジェクトを作成する。
-※ [クラウドにホストする]チェック ボックスのチェックは外す。
**余分なコードを削除する。 [#tbfa323e]
***Controller [#k9aeec88]
HomeControllerから以下のアクション・メソッドを削除
-About
-Contact
***View [#rc37b85d]
-[[上記のアクション・メソッド>#k9aeec88]]に対応するViewも削除する。
--~/Home/About.cshtml
--~/Home/Contact.cshtml
-_Layout.cshtml
--以下のdivは不要なので消す。
<div class="navbar navbar-inverse navbar-fixed-top">
--以下を以下で置き換える。
---以下のdivを
<div class="container body-content">
---以下のステートメントで置き換える。
@RenderBody()
--以下のような状態になる。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - マイ ASP.NET アプリケーション</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
<body>
@RenderBody()
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)
</body>
</html>
-~/Home/Index.cshtml
--以下を残して全て消す。
@{
ViewBag.Title = "Home Page";
}
*クライアントにRedirectエンドポイントを作成する。 [#we465a2c]
**サンプルコードをコピーする。 [#j5235722]
[[汎用認証サイトのAccountController>https://github.com/OpenTouryoProject/MultiPurposeAuthSite/blob/develop/root/programs/MultiPurposeAuthSite/MultiPurposeAuthSite/Controllers/AccountController.cs]]からサンプルのRedirectエンドポイントである~
[[AccountController.OAuthAuthorizationCodeGrantClientアクション・メソッド>https://github.com/OpenTouryoProject/MultiPurposeAuthSite/blob/develop/root/programs/MultiPurposeAuthSite/MultiPurposeAuthSite/Controllers/AccountController.cs#L1421]]をコピーする。
**サンプルコードを貼り付ける。 [#u2eec88f]
-HomeControllerにコピーしたOAuthAuthorizationCodeGrantClientアクション・メソッドを貼り付ける。
-最初のうちは、ほぼほぼコンパイル エラーになるので、以下のようにコメント アウトし、
-最後に、「return View("");」を加えて、
[HttpGet]
[AllowAnonymous]
public async Task<ActionResult> OAuthAuthorizationCodeGrantClient(string code, string state)
{
// ・・・コメントアウト・・・
return View("");
}
-Viewモジュールを~/Home/OAuthAuthorizationCodeGrantClient.cshtmlという名称で作成しておく。
*汎用認証サイトにRedirectエンドポイントのURLを設定する。 [#dba08db1]
-[[以下のclient_id部分に、以下のように、Redirectエンドポイントを設定する>汎用認証サイトのコンフィギュレーション#v637e44e]]。
"f53469c17c5a432f86ce563b7805ab89": {
"client_secret": "cKdwJb6mRKVIJpGxEWjIC94zquQltw_ECfO-55p21YM",
"redirect_uri_code": "http://(クライアント・サイトのアドレス:ポート)/Home/OAuthAuthorizationCodeGrantClient",
"redirect_uri_token": "http://hogehoge0/bbb",
"client_name": "test_icon"
-なお、このセクションは「CreateClientsIdentity.exe」使用して自動生成できる。
*認可リクエスト・認可レスポンス [#x3db3b0f]
**クライアントにスターター(認可リクエスト)のリンクを設置する。 [#q8d7b767]
***スターターのリンクを取得 [#jd4f0e5f]
-スターターは汎用認証サイトを実行したトップ画面画から取得する。
http://(汎用認証サイトのアドレス:ポート)/MultiPurposeAuthSite/Account/OAuthAuthorize?client_id=67d328bfe8604aae83fb15fa44780d8b&response_type=code&scope=profile%20email%20phone%20address%20userid%20aaa%20bbb&state=vj9NCxij4L
#ref(0.png,left,nowrap,0)
***スターターのリンクを設置 [#fe72f177]
-スターターの「ホスト:ポート」部分を書き換えてIndex.cshtmlに貼り付ける。
<a href="http://(クライアント・サイトのアドレス:ポート)/MultiPurposeAuthSite/Account/OAuthAuthorize?client_id=67d328bfe8604aae83fb15fa44780d8b&response_type=code&scope=profile%20email%20phone%20address%20userid%20aaa%20bbb&state=vj9NCxij4L">開始</a>
-次に、client_idを書き換える。具体的には、「67d328bfe8604aae83fb15fa44780d8b」から「f53469c17c5a432f86ce563b7805ab89」に書き換える。
<a href="http://(クライアント・サイトのアドレス:ポート)/MultiPurposeAuthSite/Account/OAuthAuthorize?client_id=f53469c17c5a432f86ce563b7805ab89&response_type=code&scope=profile%20email%20phone%20address%20userid%20aaa%20bbb&state=vj9NCxij4L">開始</a>
-ポイント:~
サーバーに指定したRedirectエンドポイントを使用するため、~
ここでは、認可リクエストにredirect_uriパラメタを追加しない。
**クライアントをデバッグ実行し、仲介コード(認可レスポンス)を確認する。 [#p1b6df91]
***ブレークポイントを仕掛ける。 [#x5c3dcc8]
RedirectエンドポイントであるOAuthAuthorizationCodeGrantClientアクション・メソッドにブレークポイントを仕掛ける。
***クライアントをデバッグ実行してスターターをクリック [#q259f27b]
この状態でクライアント・サイトをデバッグ実行して、スターターをクリックしてみる。
***プレークポイントにブレーク(中断)するのを確認する。 [#rc13061b]
-汎用認証サイトの認証、(認可エンドポイントでの)認可プロセスを経て、
-クライアント・サイトのRedirectエンドポイントである~
OAuthAuthorizationCodeGrantClientアクション・メソッドでブレーク(中断)するのを確認する。
-ここで、以下のように、仲介コードを確認することができる。~
以降コレ(仲介コード)を、アクセストークンとリフレッシュトークンに変換する処理を実装する。
#ref(1.png,left,nowrap,1)
*アクセストークン・リクエスト、アクセストークン・レスポンス [#m5b26214]
アクセストークン・リクエスト、アクセストークン・レスポンスによって、仲介コードをアクセストークンとリフレッシュトークンに変換する。
**必要な情報の収集 [#q97ffaa9]
以下を必要とする。
***アクセストークン・リクエストの仕方。 [#k1d931a7]
-アクセストークン・リクエストについては、[[コチラ>https://techinfoofmicrosofttech.osscons.jp/index.php?OAuth#yfeb403d]]を参照。
-ザックリと、以下のようなリクエストを送信する。
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
-ここでは、以下のようになる。
POST /MultiPurposeAuthSite/OAuthBearerToken
HTTP/1.1
Host: (汎用認証サイトのアドレス:ポート)
Authorization: Basic ["client_Id:client_secret"をbase64 url encodeした値]
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=[取得した仲介コードの値]
***TokenエンドポイントのURL [#vaa26fc4]
-[[app.configにTokenエンドポイントのパスが設定されている。>汎用認証サイトのコンフィギュレーション#se5f4174]]
-汎用認証サイト(AuthorizationServerエンドポイント)のURLと繋げて、以下のようになる。
http://(汎用認証サイトのアドレス:ポート)/MultiPurposeAuthSite/OAuthBearerToken
***client_Idとclient_secret [#qc267a07]
-[[app.configにclient_Idとclient_secretが設定されている。>汎用認証サイトのコンフィギュレーション#v637e44e]]
-ここでは、client_Idとclient_secretの値として、それぞれ、~
--client_Id : f53469c17c5a432f86ce563b7805ab89
--client_secret : cKdwJb6mRKVIJpGxEWjIC94zquQltw_ECfO-55p21YM
>を利用する。
***RedirectエンドポイントのURL [#vaa26fc4]
-[[Redirectエンドポイントは前述の値>#dba08db1]]。
-ここでは、以下のようになる。
http://(クライアント・サイトのアドレス:ポート)/Home/OAuthAuthorizationCodeGrantClient
**[[HttpClient>https://techinfoofmicrosofttech.osscons.jp/index.php?HttpClient%E3%81%AE%E9%A1%9E%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9]]を使用して仲介コードをアクセストークンとリフレッシュトークンに変換する。 [#b8eff20c]
***コード [#l1699430]
だいたい、以下のような感じのコードになる。
using System;
using System.Text;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.Owin.Security.DataHandler.Encoder;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace WebApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpGet]
[AllowAnonymous]
public async Task<ActionResult> OAuthAuthorizationCodeGrantClient(string code, string state)
{
if (state == "vj9NCxij4L") // CSRF(XSRF)対策のstateの検証は重要
{
HttpClient httpClient = new HttpClient();
HttpRequestMessage httpRequestMessage = null;
HttpResponseMessage httpResponseMessage = null;
// HttpRequestMessage (Method & RequestUri)
httpRequestMessage = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri("http://xxx.xxx.xxx.xxx/MultiPurposeAuthSite/OAuthBearerToken"),
};
// HttpRequestMessage (Headers & Content)
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(
string.Format("{0}:{1}",
"f53469c17c5a432f86ce563b7805ab89",
"cKdwJb6mRKVIJpGxEWjIC94zquQltw_ECfO-55p21YM"))));
httpRequestMessage.Content = new FormUrlEncodedContent(
new Dictionary<string, string>
{
{ "grant_type", "authorization_code" },
{ "code", code },
{ "redirect_uri", System.Web.HttpUtility.HtmlEncode("http://localhost:62517/Home/OAuthAuthorizationCodeGrantClient") },
});
// HttpResponseMessage
httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
string response = await httpResponseMessage.Content.ReadAsStringAsync();
// 汎用認証サイトのOAuth2.0のレスポンスに含まれるaccess_tokenは、id_tokenのようなformatをしている。ここからsubを取得可能。
Base64UrlTextEncoder base64UrlEncoder = new Base64UrlTextEncoder();
Dictionary<string, string> dic = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
string jwtPayload = Encoding.UTF8.GetString(base64UrlEncoder.Decode(dic["access_token"].Split('.')[1]));
// id_tokenライクなJWTなので、中からsubなどを取り出すことができる。
JObject jobj = ((JObject)JsonConvert.DeserializeObject(jwtPayload));
string sub = (string)jobj["sub"];
}
return View("");
}
}
}
***実行結果 [#i62ef920]
上記の、「dic["access_token"]」の部分が、アクセストークンである。
汎用認証サイトでは、[[設定によって>汎用認証サイトのコンフィギュレーション#k8279ce6]]アクセストークンのformatを、ASP.NET Identity形式と[[JWT>https://techinfoofmicrosofttech.osscons.jp/index.php?JWT]]形式から選択できる。
#ref(2.png,left,nowrap,2)
***署名検証、内容検証 [#va8ac3c1]
アクセストークンのformatgが、[[JWT>https://techinfoofmicrosofttech.osscons.jp/index.php?JWT]]形式の場合、~
必要に応じて、[[JWT>https://techinfoofmicrosofttech.osscons.jp/index.php?JWT]]の署名検証、内容検証を行えば、セキュリティ的に、より確実と言える。
-署名検証
--汎用認証サイトから公開鍵を入手する。
--Open棟梁では、JWTの署名検証ライブラリを提供している。
--JWTの署名検証ライブラリの使用方法については以下が参考になる。~
https://github.com/OpenTouryoProject/MultiPurposeAuthSite/blob/develop/root/programs/MultiPurposeAuthSite/MultiPurposeAuthSite/Models/ASPNETIdentity/TokenProviders/AccessTokenFormatJwt.cs#L191
-内容検証~
まず、"sub"が取得できれば、アクセストークンの取得に成功していると言える。~
そして、最低限、"iss"・"aud"、"nonce"クレームの内容検証を行うと良い。
---"iss"クレームには、当該トークンを発行したSTSを表すUri値が格納される。
---"aud"クレームには、起動パラメタの"client_id"値が格納される。
---"nonce"クレームには、起動パラメタの"state"値が格納される。
-この[[JWT>https://techinfoofmicrosofttech.osscons.jp/index.php?JWT]]の署名検証、内容検証には「https://jwt.io/」を使用できる。
--詳しくは[[コチラ>汎用認証サイトのファーストステップガイド (1)#b6c3cd0c]]を参照。
*アクセストークンを使用しResource ServerのWebAPIへのアクセス結果を出力する。 [#u9bd4699]
**必要な情報の収集 [#sb982345]
***/userinfoエンドポイントのURL [#ff57968f]
-[[app.configに/userinfoエンドポイントのパスが設定されている。>汎用認証サイトのコンフィギュレーション#se5f4174]]
-汎用認証サイト(Resource Serverエンドポイント)のURLと繋げて、以下のようになる。
http://(汎用認証サイトのアドレス:ポート)/MultiPurposeAuthSite/userinfo
**/userinfoエンドポイントにアクセスして、ユーザ属性を取得する。 [#df413385]
***コード [#q033da03]
[[先程のコード>#l1699430]]に追記を行い、以下のような感じにする。
using System;
using System.Text;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.Owin.Security.DataHandler.Encoder;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace WebApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpGet]
[AllowAnonymous]
public async Task<ActionResult> OAuthAuthorizationCodeGrantClient(string code, string state)
{
if (state == "vj9NCxij4L") // CSRF(XSRF)対策のstateの検証は重要
{
HttpClient httpClient = new HttpClient();
HttpRequestMessage httpRequestMessage = null;
HttpResponseMessage httpResponseMessage = null;
// HttpRequestMessage (Method & RequestUri)
httpRequestMessage = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri("http://xxx.xxx.xxx.xxx/MultiPurposeAuthSite/OAuthBearerToken"),
};
// HttpRequestMessage (Headers & Content)
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(
string.Format("{0}:{1}",
"f53469c17c5a432f86ce563b7805ab89",
"cKdwJb6mRKVIJpGxEWjIC94zquQltw_ECfO-55p21YM"))));
httpRequestMessage.Content = new FormUrlEncodedContent(
new Dictionary<string, string>
{
{ "grant_type", "authorization_code" },
{ "code", code },
{ "redirect_uri", System.Web.HttpUtility.HtmlEncode("http://localhost:62517/Home/OAuthAuthorizationCodeGrantClient") },
});
// HttpResponseMessage
httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
string response = await httpResponseMessage.Content.ReadAsStringAsync();
// 汎用認証サイトのOAuth2.0のレスポンスに含まれるaccess_tokenは、id_tokenのようなformatをしている。ここからsubを取得可能。
Base64UrlTextEncoder base64UrlEncoder = new Base64UrlTextEncoder();
Dictionary<string, string> dic = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
string jwtPayload = Encoding.UTF8.GetString(base64UrlEncoder.Decode(dic["access_token"].Split('.')[1]));
// id_tokenライクなJWTなので、中からsubなどを取り出すことができる。
JObject jobj = ((JObject)JsonConvert.DeserializeObject(jwtPayload));
string sub = (string)jobj["sub"];
↓↓↓ 下を追加 ↓↓↓
// Userエンドポイントに問い合わせを行う。
// HttpRequestMessage (Method & RequestUri)
httpRequestMessage = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri("http://xxx.xxx.xxx.xxx/MultiPurposeAuthSite/userinfo"),
};
// HttpRequestMessage (Headers)
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", dic["access_token"]);
// HttpResponseMessage
httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
string userinfo = await httpResponseMessage.Content.ReadAsStringAsync();
jobj = ((JObject)JsonConvert.DeserializeObject(userinfo));
sub = (string)jobj["sub"];
}
// 基本的に、仲介コードやアクセストークンは画面に露見させないこと。
// このまま画面に表示させるとQuerystringに仲介コードが露見するので、
// 必要な情報位を取得した後に、一段、Redirect処理などを経由させると良い。
↑↑↑ 上を追加 ↑↑↑
return View("");
}
}
}
***実行結果 [#w47ed253]
#ref(3.png,left,nowrap,3)
**注意事項 [#z796a2ba]
-基本的に、仲介コードやアクセストークンは画面に露見させないこと。
-このまま画面に表示させるとQuerystringに仲介コードが露見するので、~
必要な情報位を取得した後に、一段、Redirect処理などを経由させると良い。
*ライブラリを使用してみる。 [#ec6bf749]
**OAuth 2ライブラリ [#vc1c5486]
**OpenID Connectライブラリ [#g1b7f1b5]
*サンプル・プロジェクト [#nadadb6c]
下記添付の「WebApplication1.zip」からダウンロード可能です。