汎用認証サイトのファーストステップガイド (4)
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
開始行:
「[[Open棟梁 wiki>https://opentouryo.osscons.jp]]」は、「...
-[[戻る>汎用認証サイトのファーストステップガイド]]
--[[ファーストステップ (1)>汎用認証サイトのファーストステ...
--[[ファーストステップ (2)>汎用認証サイトのファーストステ...
--[[ファーストステップ (3)>汎用認証サイトのファーストステ...
--ファーストステップ (4)
--[[ファーストステップ (5)>汎用認証サイトのファーストステ...
--[[ファーストステップ (6)>汎用認証サイトのファーストステ...
--[[ファーストステップ (7)>汎用認証サイトのファーストステ...
*目次 [#hddf7023]
#contents
*概要 [#lbdaf1e3]
[[汎用認証サイト(Multi-purpose Authentication Site)]]~
の導入前の評価を行うためのファーストステップガイド。
(4) では、「認証連携コードの実装」を行う。
*サンプル・プロジェクト [#nadadb6c]
ココで実装するコードは、
下記に添付した、
-ASP.NET MVC版サンプル~
&ref(WebApplication1.zip);
-同様に作成した、ASP.NET WebForms版サンプル~
&ref(WebApplication2.zip);
-同様に作成した、[[SpringMVC(Java)版サンプル>#i5d3d94b]]~
からダウンロード可能です。
*汎用認証サイトをセットアップする。 [#m6718c37]
-適当なところに汎用認証サイトをセットアップする。
-[[3 分割テスト>汎用認証サイトのファーストステップガイド ...
-ここでは、別サーバーのIIS上にデプロイ、セットアップした...
-ローカル開発環境でテストする場合は、必要に応じて、汎用認...
*クライアントのプロジェクトを作成する。 [#l8d51863]
ここでは、クライアントのプロジェクトとして、ASP.NET MVCを...
**ASP.NET MVCのプロジェクトを新規作成する。 [#v7dfdc59]
ASP.NET MVCの(ASP.NET Web Application)プロジェクトを新...
[メニュー] ---> [新規作成] --->
>[プロジェクト] ---> [ASP.NET Web Application(.NET Framew...
***新しいプロジェクト [#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; char...
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, i...
<title>@ViewBag.Title - マイ ASP.NET アプリケーショ...
@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エンドポイントを作成する。 [#we465...
**サンプルコードをコピーする。 [#j5235722]
[[汎用認証サイトのAccountController>https://github.com/Op...
***Authorization Codeグラント種別 [#f687dd62]
[[AccountController.OAuthAuthorizationCodeGrantClientアク...
***Implicitグラント種別 [#a9669a55]
[[AccountController.OAuthImplicitGrantClientアクション・...
**サンプルコードを貼り付ける。 [#u2eec88f]
***Authorization Codeグラント種別 [#eae7d995]
-HomeControllerにコピーしたOAuthAuthorizationCodeGrantCli...
-最初のうちは、ほぼほぼコンパイル エラーになるので、以下...
-最後に、「return View("");」を加えて、
[HttpGet]
[AllowAnonymous]
public async Task<ActionResult> OAuthAuthorizationCodeGr...
{
// ・・・コメントアウト・・・
return View("");
}
-Viewモジュールを~/Home/OAuthAuthorizationCodeGrantClient...
***Implicitグラント種別 [#z4b87f68]
-HomeControllerにコピーしたOAuthImplicitGrantClientアクシ...
-最初のうちは、ほぼほぼコンパイル エラーになるので、以下...
-最後に、「return View("");」を加えて、
[HttpGet]
[AllowAnonymous]
public async Task<ActionResult> OAuthImplicitGrantClient...
{
// ・・・コメントアウト・・・
return View("");
}
-Viewモジュールを~/Home/OAuthImplicitGrantClient.cshtmlと...
*汎用認証サイトにRedirectエンドポイントのURLを設定する。 ...
-[[以下のclient_id部分に、以下のように、Redirectエンドポ...
"21c7769f16634dabaf14282602b9a5fc": {
"client_secret": "xrRczIidMMZcMxvYWpIkvSZX1oRj2CLzVFSO...
"redirect_uri_code": "http://(MVCクライアント・サイト...
"redirect_uri_token": "http://(MVCクライアント・サイ...
"client_name": "WebApplication1(MVC)"
},
"a0d280a6da034eb8ba821a651da829fc": {
"client_secret": "eufLXjWaaQgiBXiiGZ-36N-bb4hOHy8H1TIE...
"redirect_uri_code": "http://(WebFormクライアント・サ...
"redirect_uri_token": "http://(WebFormクライアント・...
"client_name": "WebApplication2(WebForms)"
},
-なお、このセクションは「CreateClientsIdentity.exe」使用...
*認可リクエスト・認可レスポンス [#x3db3b0f]
**クライアントにスターター(認可リクエスト)のリンクを設...
***スターターのリンクを取得 [#jd4f0e5f]
-スターターは汎用認証サイトを実行したトップ画面画から取得...
--Authorization Codeグラント種別
http://(汎用認証サイトのアドレス:ポート)/MultiPurposeA...
--Implicitグラント種別
http://(汎用認証サイトのアドレス:ポート)/MultiPurposeA...
#ref(0.png,left,nowrap,0)
***スターターのリンクを設置 [#fe72f177]
-スターターの「ホスト:ポート」部分を書き換えてIndex.cshtm...
--Authorization Codeグラント種別
http://(汎用認証サイトのアドレス:ポート)/MultiPurposeA...
--Implicitグラント種別
http://(汎用認証サイトのアドレス:ポート)/MultiPurposeA...
-次に、client_idを書き換える。
--具体的には、「67d328bfe8604aae83fb15fa44780d8b」から
---MVC「21c7769f16634dabaf14282602b9a5fc」に書き換える。
---Web Forms「a0d280a6da034eb8ba821a651da829fc」に書き換...
--Authorization Codeグラント種別
---MVC
<a href="http://(汎用認証サイトのアドレス:ポート)/Mult...
---Web Forms
<a href="http://localhost:63359/MultiPurposeAuthSite/Acc...
--Implicitグラント種別
---MVC
<a href="http://(汎用認証サイトのアドレス:ポート)/Mult...
---Web Forms
<a href="http://localhost:63359/MultiPurposeAuthSite/Acc...
-ポイント:~
サーバーに指定したRedirectエンドポイントを使用するため、~
ここでは、認可リクエストにredirect_uriパラメタを追加しな...
**クライアントをデバッグ実行し、認可レスポンスを確認する...
***Authorization Codeグラント種別 [#hfcbba9f]
-ブレークポイントを仕掛ける。~
RedirectエンドポイントであるOAuthAuthorizationCodeGrantCl...
-クライアントをデバッグ実行してAuthorization Codeグラント...
この状態でクライアント・サイトをデバッグ実行して、スター...
-プレークポイントにブレーク(中断)するのを確認する。~
--汎用認証サイトの認証、(認可エンドポイントでの)認可プ...
--クライアント・サイトのRedirectエンドポイントである~
OAuthAuthorizationCodeGrantClientアクション・メソッドでブ...
--ここで、以下のように、仲介コードを確認することができる。~
以降コレ(仲介コード)を、アクセストークンとリフレッシュ...
>
#ref(1.png,left,nowrap,1)
***Implicitグラント種別 [#a746d77f]
-ブレークポイントを仕掛ける。~
RedirectエンドポイントであるOAuthImplicitGrantClientアク...
-クライアントをデバッグ実行してImplicitグラント種別のスタ...
この状態でクライアント・サイトをデバッグ実行して、スター...
-プレークポイントにブレーク(中断)するのを確認する。~
--汎用認証サイトの認証、(認可エンドポイントでの)認可プ...
--クライアント・サイトのRedirectエンドポイントである~
OAuthImplicitGrantClientアクション・メソッドでブレーク(...
--Implicitグラント種別では、「URLフラグメント」を使用して...
サーバー側では、何も確認できないので、F5押下で画面表示し...
>
#ref(1a.png,left,nowrap,1a,80%)
*アクセストークン・リクエスト、レスポンス(Authorization ...
-アクセストークン・リクエスト
-アクセストークン・レスポンス~
によって、仲介コードを
-アクセストークンと
-リフレッシュトークンに
変換する。
なお、この処理は、Authorization Codeでのみ必要になる。
**必要な情報の収集 [#q97ffaa9]
以下を必要とする。
***アクセストークン・リクエストの仕方。 [#k1d931a7]
-アクセストークン・リクエストについては、[[コチラ>https:/...
-ザックリと、以下のようなリクエストを送信する。
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...
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=[取得した仲介コード...
***TokenエンドポイントのURL [#vaa26fc4]
-[[app.configにTokenエンドポイントのパスが設定されている...
-汎用認証サイト(AuthorizationServerエンドポイント)のURL...
http://(汎用認証サイトのアドレス:ポート)/MultiPurposeA...
***client_Idとclient_secret [#qc267a07]
-[[app.configにclient_Idとclient_secretが設定されている。...
-ここでは、client_Idとclient_secretの値として、それぞれ、~
--client_Id : 21c7769f16634dabaf14282602b9a5fc
--client_secret : xrRczIidMMZcMxvYWpIkvSZX1oRj2CLzVFSOkl7...
>を利用する。
***RedirectエンドポイントのURL [#vaa26fc4]
-[[Redirectエンドポイントは前述の値>#dba08db1]]。
-ここでは、以下のようになる。
http://(クライアント・サイトのアドレス:ポート)/Home/OA...
**[[HttpClient>https://techinfoofmicrosofttech.osscons.jp...
***コード [#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> OAuthAuthorizati...
{
if (state == "vj9NCxij4L") // CSRF(XSRF)対策...
{
HttpClient httpClient = new HttpClient();
HttpRequestMessage httpRequestMessage = ...
HttpResponseMessage httpResponseMessage ...
// HttpRequestMessage (Method & RequestU...
httpRequestMessage = new HttpRequestMess...
{
Method = HttpMethod.Post,
RequestUri = new Uri("http://(汎用...
};
// HttpRequestMessage (Headers & Content)
httpRequestMessage.Headers.Authorization...
"Basic",
Convert.ToBase64String(System.Text.E...
string.Format("{0}:{1}",
"21c7769f16634dabaf14282602b...
"xrRczIidMMZcMxvYWpIkvSZX1oR...
httpRequestMessage.Content = new FormUrl...
new Dictionary<string, string>
{
{ "grant_type", "authorization_c...
{ "code", code },
{ "redirect_uri", System.Web.Htt...
});
// HttpResponseMessage
httpResponseMessage = await httpClient.S...
string response = await httpResponseMess...
// 汎用認証サイトのOAuth2.0のレスポンス...
Base64UrlTextEncoder base64UrlEncoder = ...
Dictionary<string, string> dic = JsonCon...
string jwtPayload = Encoding.UTF8.GetStr...
// id_tokenライクなJWTなので、中からsub...
JObject jobj = ((JObject)JsonConvert.Des...
string sub = (string)jobj["sub"];
}
return View("");
}
}
}
***実行結果 [#i62ef920]
上記の、「dic["access_token"]」の部分が、アクセストークン...
汎用認証サイトでは、[[設定によって>汎用認証サイトのコンフ...
#ref(2.png,left,nowrap,2)
***署名検証、内容検証 [#va8ac3c1]
アクセストークンのformatgが、[[JWT>https://techinfoofmicr...
必要に応じて、[[JWT>https://techinfoofmicrosofttech.ossco...
-署名検証
--汎用認証サイトから公開鍵を入手する。
--Open棟梁では、JWTの署名検証ライブラリを提供している。
--JWTの署名検証ライブラリの使用方法については以下が参考に...
https://github.com/OpenTouryoProject/MultiPurposeAuthSite...
-内容検証~
まず、"sub"が取得できれば、アクセストークンの取得に成功し...
そして、最低限、"iss"・"aud"、"nonce"クレームの内容検証を...
---"iss"クレームには、当該トークンを発行したSTSを表すUri...
---"aud"クレームには、起動パラメタの"client_id"値が格納さ...
---"nonce"クレームには、起動パラメタの"state"値が格納され...
-この[[JWT>https://techinfoofmicrosofttech.osscons.jp/ind...
--詳しくは[[コチラ>汎用認証サイトのファーストステップガイ...
*アクセストークンを使用しResource ServerのWebAPIへのアク...
**Authorization Codeグラント種別 [#ka9fbeb3]
***必要な情報の収集 [#sb982345]
-/userinfoエンドポイントのURL
--[[app.configに/userinfoエンドポイントのパスが設定されて...
--汎用認証サイト(Resource Serverエンドポイント)のURLと...
http://(汎用認証サイトのアドレス:ポート)/MultiPurposeA...
***/userinfoエンドポイントにアクセスして、ユーザ属性を取...
-コード~
[[先程のコード>#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> OAuthAuthorizati...
{
if (state == "vj9NCxij4L") // CSRF(XSRF)対策...
{
HttpClient httpClient = new HttpClient();
HttpRequestMessage httpRequestMessage = ...
HttpResponseMessage httpResponseMessage ...
// HttpRequestMessage (Method & RequestU...
httpRequestMessage = new HttpRequestMess...
{
Method = HttpMethod.Post,
RequestUri = new Uri("http://(汎用...
};
// HttpRequestMessage (Headers & Content)
httpRequestMessage.Headers.Authorization...
"Basic",
Convert.ToBase64String(System.Text.E...
string.Format("{0}:{1}",
"21c7769f16634dabaf14282602b...
"xrRczIidMMZcMxvYWpIkvSZX1oR...
httpRequestMessage.Content = new FormUrl...
new Dictionary<string, string>
{
{ "grant_type", "authorization_c...
{ "code", code },
{ "redirect_uri", System.Web.Htt...
});
// HttpResponseMessage
httpResponseMessage = await httpClient.S...
string response = await httpResponseMess...
// 汎用認証サイトのOAuth2.0のレスポンス...
Base64UrlTextEncoder base64UrlEncoder = ...
Dictionary<string, string> dic = JsonCon...
string jwtPayload = Encoding.UTF8.GetStr...
// id_tokenライクなJWTなので、中からsub...
JObject jobj = ((JObject)JsonConvert.Des...
string sub = (string)jobj["sub"];
>↓↓↓ 下を追加 ↓↓↓
// Userエンドポイントに問い合わせを行う。
// HttpRequestMessage (Method & RequestU...
httpRequestMessage = new HttpRequestMess...
{
Method = HttpMethod.Get,
RequestUri = new Uri("http://(汎用...
};
// HttpRequestMessage (Headers)
httpRequestMessage.Headers.Authorization...
// HttpResponseMessage
httpResponseMessage = await httpClient.S...
string userinfo = await httpResponseMess...
jobj = ((JObject)JsonConvert.Deserialize...
sub = (string)jobj["sub"];
}
// 基本的に、仲介コードやアクセストークンは...
// このまま画面に表示させるとQuerystringに仲...
// 必要な情報位を取得した後に、一段、Redirec...
>↑↑↑ 上を追加 ↑↑↑
return View("");
}
}
}
-実行結果
>
#ref(3.png,left,nowrap,3)
***注意事項 [#z796a2ba]
-仲介コードやアクセストークンは画面に露見させないこと。
-このまま画面に表示させるとQuerystringに仲介コードが露見...
必要な情報位を取得した後に、一段、Redirect処理などを経由...
**Implicitグラント種別 [#b7c19466]
前述の[[Authorization Codeグラント種別>#ka9fbeb3]]と異な...
この後の処理をクライアントサイドのJavaScriptで行う。
***必要な情報の収集 [#ydacf245]
[[前述>#sb982345]]と同じ。
***/userinfoエンドポイントにアクセスして、ユーザ属性を取...
以下のモジュールから、
-OAuthImplicitGrantClient.cshtml~
https://github.com/OpenTouryoProject/MultiPurposeAuthSite...
-oauthimplicit.js~
https://github.com/OpenTouryoProject/MultiPurposeAuthSite...
以下のようにOAuthImplicitGrantClient.cshtmlにコードを移植...
-コード
<a href="#" id="OAuthGetUserClaimsWebAPI" url="http://(...
@section scripts{
<script type="text/javascript">
var fragment = "";
var token = "";
function CallOAuthAPI(url, httpMethod, postdata) {
alert(
"<httpMethod>" + "\n" + httpMethod + "\n...
"<url>" + "\n" + url + "\n" +
"<token>" + "\n" + token);
$.ajax({
type: httpMethod,
url: url,
crossDomain: true,
headers: {
'Authorization': 'Bearer ' + token
},
data: postdata,
xhrFields: {
withCredentials: true
},
success: function (responseData, textSta...
alert(textStatus + ', ' + JSON.strin...
},
error: function (responseData, textStatu...
alert(textStatus + ', ' + errorThrow...
}
});
}
$(function () {
// フラグメントを取得し、
fragment = getFragment();
// フラグメントに access_token (Bearer Token...
if (fragment.access_token) {
// access_token (Bearer Token) を使用して
// ResourceServerのWebAPIにアクセスする。
token = fragment.access_token;
// 「access_token」(Bearer Token)が
// "露見"しないようwindow.location.hash...
// ~~~~~~
window.location.hash = fragment.state ||...
// OAuthGetUserClaimsWebAPI
$('#OAuthGetUserClaimsWebAPI').on('click...
CallOAuthAPI($('#OAuthGetUserClaimsW...
});
}
});
// ---------------------------------------------...
// フラグメント(#~の部分)を取得する。
// ---------------------------------------------...
function getFragment() {
// URLの「#」記号の後の部分を取得し、
if (window.location.hash.indexOf("#") === 0) {
// # が1文字目にある場合
// 2文字目以降をobjectにparse。
return parseQueryString(window.location....
} else {
// そうではない場合。
return {}; // 空
}
};
// ---------------------------------------------...
// QueryStringをobjectにparseする。
// ---------------------------------------------...
function parseQueryString(queryString) {
//alert(queryString);
var data = {},
pairs, pair, separatorIndex, escapedKey,...
if (queryString === null) {
return data; // 空で返す。
}
// 分解して、
pairs = queryString.split("&");
// 詰めて、
for (var i = 0; i < pairs.length; i++) {
pair = pairs[i];
separatorIndex = pair.indexOf("=");
if (separatorIndex === -1) {
escapedKey = pair;
escapedValue = null;
} else {
escapedKey = pair.substr(0, separato...
escapedValue = pair.substr(separator...
}
key = decodeURIComponent(escapedKey);
value = decodeURIComponent(escapedValue);
// インデクサで。
data[key] = value;
}
// 返す。
return data;
}
</script>
}
-実行結果
>
#ref(3a.png,left,nowrap,3a,80%)
***注意事項 [#ma7eb138]
URLフラグメントを使用するため、Authorization Codeグラント...
非常に、アクセストークンが画面に露見し易いので注意する。
*OpenID Connect [#xf70352f]
**Authorization Code Flow [#i3cda345]
OpenID ConnectのAuthorization Code Flowをテストする。
***スターター [#t97b9eb3]
上記の[[スターター>#fe72f177]]のScopeにopenidを加えるだけ...
OpenID ConnectのAuthorization Code Flowをテストできる。
<a href="http://(汎用認証サイトのアドレス:ポート)/Mult...
***コード [#mc27d8dc]
特に、id_tokenの検証に関して、以下のコードが参考になる。
-WebFormのredirectエンドポイント~
https://github.com/OpenTouryoProject/OpenTouryo/blob/deve...
-MVCのredirectエンドポイント~
https://github.com/OpenTouryoProject/OpenTouryo/blob/deve...
-id_tokenの検証には、JwtToken.Verifyメソッド、DigitalSign...
--JwtToken.Verifyメソッド~
https://github.com/OpenTouryoProject/OpenTouryo/blob/deve...
--DigitalSignX509.Verifyメソッド~
https://github.com/OpenTouryoProject/OpenTouryo/blob/deve...
**Implicit Flow [#odb23e5a]
OpenID ConnectのImplicit Flowをテストする。
***スターター [#geda9bfa]
Implicit Flowでは、Scopeにopenidを加えるだけでなく、~
response_typeを、response_type=id_token token, id_tokenに...
<a href="http://(汎用認証サイトのアドレス:ポート)/Mult...
***コード [#lca0948f]
JavaScriptでid_tokenの検証をするには...。
WebCrypto APIなるものがあるもよう。
-WebCrypto APIでJSON Web Tokenの検証を試してみる - Qiita~
http://qiita.com/tomoyukilabs/items/faa66805a440f4b30cfb
-自堕落な技術者の日記 : W3C Web Cryptography APIとの~
果てしなき戦い(第2回 RSA署名生成と検証) - livedoor Blog(...
http://blog.livedoor.jp/k_urushima/archives/1759093.html
>公開鍵のインポートと署名の検証
*SpringMVC(Java)でやる。 [#i5d3d94b]
***SpringMVC(Java) [#q2c66de7]
[[コチラ>https://dotnetdevelopmentinfrastructure.osscons....
-Javaを書いてみる。 - .NET 開発基盤部会 Wiki~
https://dotnetdevelopmentinfrastructure.osscons.jp/index....
***コード [#hfcd67a5]
OAuth2.0、OpenID Connect のサンプル・コード(Redirectエン...
package com.example.test1;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMa...
import org.springframework.web.bind.annotation.RequestMe...
import org.springframework.web.bind.annotation.RequestPa...
import org.springframework.web.servlet.ModelAndView;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
@Controller
public class HeloController {
@RequestMapping(value="/", method=RequestMethod.GET)
public ModelAndView index(ModelAndView mav) {
mav.setViewName("index");
return mav;
}
@RequestMapping(value="/", method=RequestMethod.POST)
public ModelAndView send(@RequestParam("text1")String s...
mav.setViewName(
"redirect:http://localhost:63359/MultiPurposeAuthSit...
+ "?client_id=084b7157a4d7427794012ee8a8e6d415"
+ "&response_type=code"
+ "&scope=profile%20email%20phone%20address%20openid"
+ "&state=q58aqOg3"
+ "&nonce=CiC1wEsurMkmMRBh");
return mav;
}
@RequestMapping(value="/code", method=RequestMethod.GET)
public ModelAndView code(@RequestParam("code") String c...
if(state.equals("q58aqOg3")) // CSRF(XSRF)対策のstate...
{
try {
// Requestの準備
String host = "localhost";
String path1 = "/MultiPurposeAuthSite/OAuthBearerTok...
String path2 = "/MultiPurposeAuthSite/userinfo";
String username = "084b7157a4d7427794012ee8a8e6d415";
String password = "JxBaXLNFyK4lCawEY9_HPA2zzjyiLgIiV...
HttpHost httpHost = new HttpHost(host, 63359, "http");
HttpPost httpPost = new HttpPost(path1);
HttpClient client = HttpClientBuilder.create().build...
String auth = username + ":" + password;
byte[] encodedAuth = Base64.getEncoder().encode(auth...
String authHeader = "Basic " + new String(encodedAut...
httpPost.setHeader(HttpHeaders.AUTHORIZATION, authHe...
List<NameValuePair> requestParams = new ArrayList<>();
requestParams.add(new BasicNameValuePair("grant_type...
requestParams.add(new BasicNameValuePair("code",code...
requestParams.add(new BasicNameValuePair("redirect_u...
HttpResponse response = null;
// Requestの発行
httpPost.setEntity(new UrlEncodedFormEntity(requestP...
response = client.execute(httpHost, httpPost);
// HttpStatusの確認
int status = response.getStatusLine().getStatusCode();
if (status == HttpStatus.SC_OK){
// responseの取得
String responseData = EntityUtils.toString(
response.getEntity(), StandardCharsets.UTF_8);
// JSONのParse
Gson gson = new Gson();
Type type = new TypeToken<Map<String, String>>(){}....
Map<String, String> map = gson.fromJson(responseDat...
String access_token = map.get("access_token");
//String token_type = map.get("token_type");
//String expires_in = map.get("expires_in");
//String refresh_token = map.get("refresh_token");
String id_token = map.get("id_token");
// id_tokenの検証
String[] jwt = id_token.split("\\.");
Decoder base64urlDecoder = Base64.getUrlDecoder();
//String jwtHeaderString = new String(base64urlDeco...
String jwtClaimString = new String(base64urlDecode...
//String jwtSignitureString = new String(base64url...
byte[] message = (jwt[0] + "." + jwt[1]).getBytes("...
byte[] sign = base64urlDecoder.decode(jwt[2]);
// JWT(RS-256の検証)
// RSAによる署名と、その検証方法の例。
// 1. RSAキーペアを生成し、公開キー・非公開キーをお...
// 2. PKCS12を読み取り、秘密キーでメッセージを署名...
// 3. DERを読み取り、公開キーでメッセージの署名をベ...
// 手順1の、X.509の生成に、sunの非公開関数を用いて...
// 代替ライブラリとしてはbouncycastleなどがある。 ...
// https://gist.github.com/seraphy/5359542
// X509証明書から、RSA公開キーを復元する.
RSAPublicKey publicKey2;
try (InputStream is = new FileInputStream("C:\\root...
X509Certificate x509 = null;
try {
x509 = this.loadX509(is);
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
publicKey2 = (RSAPublicKey) x509.getPublicKey();
}
// RSA公開キーで署名器を生成する.
Signature verifier = null;
boolean result = false;
verifier = Signature.getInstance("SHA256withRSA");
verifier.initVerify(publicKey2);
verifier.update(message);
result = verifier.verify(sign);
if(result)
{
map = gson.fromJson(jwtClaimString, type);
String iss = map.get("iss");
String aud = map.get("aud");
String nonce = map.get("nonce");
String sub = map.get("sub");
String iat = map.get("iat");
String exp = map.get("exp"); //1505490503;
if(
iss.equals("http://jwtssoauth.opentouryo.com")
&& aud.equals("084b7157a4d7427794012ee8a8e6d415")
&& nonce.equals("CiC1wEsurMkmMRBh")
&& (System.currentTimeMillis() / 1000L) <= Long....
{
// /userinfoへアクセス。
HttpGet httpGet = new HttpGet(path2);
client = HttpClientBuilder.create().build();
authHeader = "Bearer " + access_token;
httpGet.setHeader(HttpHeaders.AUTHORIZATION, auth...
// Requestの発行
response = client.execute(httpHost, httpGet);
// HttpStatusの確認
status = response.getStatusLine().getStatusCode();
if (status == HttpStatus.SC_OK){
// responseの取得
responseData = EntityUtils.toString(response.get...
// JSONのParse
gson = new Gson();
type = new TypeToken<Map<String, String>>(){}.ge...
map = gson.fromJson(responseData, type);
sub = map.get("sub");
}
}
}
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
mav.setViewName("index");
return mav;
}
public X509Certificate loadX509(InputStream is) throws ...
CertificateFactory certFactory = CertificateFactory.ge...
return (X509Certificate) certFactory.generateCertifica...
}
}
終了行:
「[[Open棟梁 wiki>https://opentouryo.osscons.jp]]」は、「...
-[[戻る>汎用認証サイトのファーストステップガイド]]
--[[ファーストステップ (1)>汎用認証サイトのファーストステ...
--[[ファーストステップ (2)>汎用認証サイトのファーストステ...
--[[ファーストステップ (3)>汎用認証サイトのファーストステ...
--ファーストステップ (4)
--[[ファーストステップ (5)>汎用認証サイトのファーストステ...
--[[ファーストステップ (6)>汎用認証サイトのファーストステ...
--[[ファーストステップ (7)>汎用認証サイトのファーストステ...
*目次 [#hddf7023]
#contents
*概要 [#lbdaf1e3]
[[汎用認証サイト(Multi-purpose Authentication Site)]]~
の導入前の評価を行うためのファーストステップガイド。
(4) では、「認証連携コードの実装」を行う。
*サンプル・プロジェクト [#nadadb6c]
ココで実装するコードは、
下記に添付した、
-ASP.NET MVC版サンプル~
&ref(WebApplication1.zip);
-同様に作成した、ASP.NET WebForms版サンプル~
&ref(WebApplication2.zip);
-同様に作成した、[[SpringMVC(Java)版サンプル>#i5d3d94b]]~
からダウンロード可能です。
*汎用認証サイトをセットアップする。 [#m6718c37]
-適当なところに汎用認証サイトをセットアップする。
-[[3 分割テスト>汎用認証サイトのファーストステップガイド ...
-ここでは、別サーバーのIIS上にデプロイ、セットアップした...
-ローカル開発環境でテストする場合は、必要に応じて、汎用認...
*クライアントのプロジェクトを作成する。 [#l8d51863]
ここでは、クライアントのプロジェクトとして、ASP.NET MVCを...
**ASP.NET MVCのプロジェクトを新規作成する。 [#v7dfdc59]
ASP.NET MVCの(ASP.NET Web Application)プロジェクトを新...
[メニュー] ---> [新規作成] --->
>[プロジェクト] ---> [ASP.NET Web Application(.NET Framew...
***新しいプロジェクト [#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; char...
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, i...
<title>@ViewBag.Title - マイ ASP.NET アプリケーショ...
@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エンドポイントを作成する。 [#we465...
**サンプルコードをコピーする。 [#j5235722]
[[汎用認証サイトのAccountController>https://github.com/Op...
***Authorization Codeグラント種別 [#f687dd62]
[[AccountController.OAuthAuthorizationCodeGrantClientアク...
***Implicitグラント種別 [#a9669a55]
[[AccountController.OAuthImplicitGrantClientアクション・...
**サンプルコードを貼り付ける。 [#u2eec88f]
***Authorization Codeグラント種別 [#eae7d995]
-HomeControllerにコピーしたOAuthAuthorizationCodeGrantCli...
-最初のうちは、ほぼほぼコンパイル エラーになるので、以下...
-最後に、「return View("");」を加えて、
[HttpGet]
[AllowAnonymous]
public async Task<ActionResult> OAuthAuthorizationCodeGr...
{
// ・・・コメントアウト・・・
return View("");
}
-Viewモジュールを~/Home/OAuthAuthorizationCodeGrantClient...
***Implicitグラント種別 [#z4b87f68]
-HomeControllerにコピーしたOAuthImplicitGrantClientアクシ...
-最初のうちは、ほぼほぼコンパイル エラーになるので、以下...
-最後に、「return View("");」を加えて、
[HttpGet]
[AllowAnonymous]
public async Task<ActionResult> OAuthImplicitGrantClient...
{
// ・・・コメントアウト・・・
return View("");
}
-Viewモジュールを~/Home/OAuthImplicitGrantClient.cshtmlと...
*汎用認証サイトにRedirectエンドポイントのURLを設定する。 ...
-[[以下のclient_id部分に、以下のように、Redirectエンドポ...
"21c7769f16634dabaf14282602b9a5fc": {
"client_secret": "xrRczIidMMZcMxvYWpIkvSZX1oRj2CLzVFSO...
"redirect_uri_code": "http://(MVCクライアント・サイト...
"redirect_uri_token": "http://(MVCクライアント・サイ...
"client_name": "WebApplication1(MVC)"
},
"a0d280a6da034eb8ba821a651da829fc": {
"client_secret": "eufLXjWaaQgiBXiiGZ-36N-bb4hOHy8H1TIE...
"redirect_uri_code": "http://(WebFormクライアント・サ...
"redirect_uri_token": "http://(WebFormクライアント・...
"client_name": "WebApplication2(WebForms)"
},
-なお、このセクションは「CreateClientsIdentity.exe」使用...
*認可リクエスト・認可レスポンス [#x3db3b0f]
**クライアントにスターター(認可リクエスト)のリンクを設...
***スターターのリンクを取得 [#jd4f0e5f]
-スターターは汎用認証サイトを実行したトップ画面画から取得...
--Authorization Codeグラント種別
http://(汎用認証サイトのアドレス:ポート)/MultiPurposeA...
--Implicitグラント種別
http://(汎用認証サイトのアドレス:ポート)/MultiPurposeA...
#ref(0.png,left,nowrap,0)
***スターターのリンクを設置 [#fe72f177]
-スターターの「ホスト:ポート」部分を書き換えてIndex.cshtm...
--Authorization Codeグラント種別
http://(汎用認証サイトのアドレス:ポート)/MultiPurposeA...
--Implicitグラント種別
http://(汎用認証サイトのアドレス:ポート)/MultiPurposeA...
-次に、client_idを書き換える。
--具体的には、「67d328bfe8604aae83fb15fa44780d8b」から
---MVC「21c7769f16634dabaf14282602b9a5fc」に書き換える。
---Web Forms「a0d280a6da034eb8ba821a651da829fc」に書き換...
--Authorization Codeグラント種別
---MVC
<a href="http://(汎用認証サイトのアドレス:ポート)/Mult...
---Web Forms
<a href="http://localhost:63359/MultiPurposeAuthSite/Acc...
--Implicitグラント種別
---MVC
<a href="http://(汎用認証サイトのアドレス:ポート)/Mult...
---Web Forms
<a href="http://localhost:63359/MultiPurposeAuthSite/Acc...
-ポイント:~
サーバーに指定したRedirectエンドポイントを使用するため、~
ここでは、認可リクエストにredirect_uriパラメタを追加しな...
**クライアントをデバッグ実行し、認可レスポンスを確認する...
***Authorization Codeグラント種別 [#hfcbba9f]
-ブレークポイントを仕掛ける。~
RedirectエンドポイントであるOAuthAuthorizationCodeGrantCl...
-クライアントをデバッグ実行してAuthorization Codeグラント...
この状態でクライアント・サイトをデバッグ実行して、スター...
-プレークポイントにブレーク(中断)するのを確認する。~
--汎用認証サイトの認証、(認可エンドポイントでの)認可プ...
--クライアント・サイトのRedirectエンドポイントである~
OAuthAuthorizationCodeGrantClientアクション・メソッドでブ...
--ここで、以下のように、仲介コードを確認することができる。~
以降コレ(仲介コード)を、アクセストークンとリフレッシュ...
>
#ref(1.png,left,nowrap,1)
***Implicitグラント種別 [#a746d77f]
-ブレークポイントを仕掛ける。~
RedirectエンドポイントであるOAuthImplicitGrantClientアク...
-クライアントをデバッグ実行してImplicitグラント種別のスタ...
この状態でクライアント・サイトをデバッグ実行して、スター...
-プレークポイントにブレーク(中断)するのを確認する。~
--汎用認証サイトの認証、(認可エンドポイントでの)認可プ...
--クライアント・サイトのRedirectエンドポイントである~
OAuthImplicitGrantClientアクション・メソッドでブレーク(...
--Implicitグラント種別では、「URLフラグメント」を使用して...
サーバー側では、何も確認できないので、F5押下で画面表示し...
>
#ref(1a.png,left,nowrap,1a,80%)
*アクセストークン・リクエスト、レスポンス(Authorization ...
-アクセストークン・リクエスト
-アクセストークン・レスポンス~
によって、仲介コードを
-アクセストークンと
-リフレッシュトークンに
変換する。
なお、この処理は、Authorization Codeでのみ必要になる。
**必要な情報の収集 [#q97ffaa9]
以下を必要とする。
***アクセストークン・リクエストの仕方。 [#k1d931a7]
-アクセストークン・リクエストについては、[[コチラ>https:/...
-ザックリと、以下のようなリクエストを送信する。
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...
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=[取得した仲介コード...
***TokenエンドポイントのURL [#vaa26fc4]
-[[app.configにTokenエンドポイントのパスが設定されている...
-汎用認証サイト(AuthorizationServerエンドポイント)のURL...
http://(汎用認証サイトのアドレス:ポート)/MultiPurposeA...
***client_Idとclient_secret [#qc267a07]
-[[app.configにclient_Idとclient_secretが設定されている。...
-ここでは、client_Idとclient_secretの値として、それぞれ、~
--client_Id : 21c7769f16634dabaf14282602b9a5fc
--client_secret : xrRczIidMMZcMxvYWpIkvSZX1oRj2CLzVFSOkl7...
>を利用する。
***RedirectエンドポイントのURL [#vaa26fc4]
-[[Redirectエンドポイントは前述の値>#dba08db1]]。
-ここでは、以下のようになる。
http://(クライアント・サイトのアドレス:ポート)/Home/OA...
**[[HttpClient>https://techinfoofmicrosofttech.osscons.jp...
***コード [#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> OAuthAuthorizati...
{
if (state == "vj9NCxij4L") // CSRF(XSRF)対策...
{
HttpClient httpClient = new HttpClient();
HttpRequestMessage httpRequestMessage = ...
HttpResponseMessage httpResponseMessage ...
// HttpRequestMessage (Method & RequestU...
httpRequestMessage = new HttpRequestMess...
{
Method = HttpMethod.Post,
RequestUri = new Uri("http://(汎用...
};
// HttpRequestMessage (Headers & Content)
httpRequestMessage.Headers.Authorization...
"Basic",
Convert.ToBase64String(System.Text.E...
string.Format("{0}:{1}",
"21c7769f16634dabaf14282602b...
"xrRczIidMMZcMxvYWpIkvSZX1oR...
httpRequestMessage.Content = new FormUrl...
new Dictionary<string, string>
{
{ "grant_type", "authorization_c...
{ "code", code },
{ "redirect_uri", System.Web.Htt...
});
// HttpResponseMessage
httpResponseMessage = await httpClient.S...
string response = await httpResponseMess...
// 汎用認証サイトのOAuth2.0のレスポンス...
Base64UrlTextEncoder base64UrlEncoder = ...
Dictionary<string, string> dic = JsonCon...
string jwtPayload = Encoding.UTF8.GetStr...
// id_tokenライクなJWTなので、中からsub...
JObject jobj = ((JObject)JsonConvert.Des...
string sub = (string)jobj["sub"];
}
return View("");
}
}
}
***実行結果 [#i62ef920]
上記の、「dic["access_token"]」の部分が、アクセストークン...
汎用認証サイトでは、[[設定によって>汎用認証サイトのコンフ...
#ref(2.png,left,nowrap,2)
***署名検証、内容検証 [#va8ac3c1]
アクセストークンのformatgが、[[JWT>https://techinfoofmicr...
必要に応じて、[[JWT>https://techinfoofmicrosofttech.ossco...
-署名検証
--汎用認証サイトから公開鍵を入手する。
--Open棟梁では、JWTの署名検証ライブラリを提供している。
--JWTの署名検証ライブラリの使用方法については以下が参考に...
https://github.com/OpenTouryoProject/MultiPurposeAuthSite...
-内容検証~
まず、"sub"が取得できれば、アクセストークンの取得に成功し...
そして、最低限、"iss"・"aud"、"nonce"クレームの内容検証を...
---"iss"クレームには、当該トークンを発行したSTSを表すUri...
---"aud"クレームには、起動パラメタの"client_id"値が格納さ...
---"nonce"クレームには、起動パラメタの"state"値が格納され...
-この[[JWT>https://techinfoofmicrosofttech.osscons.jp/ind...
--詳しくは[[コチラ>汎用認証サイトのファーストステップガイ...
*アクセストークンを使用しResource ServerのWebAPIへのアク...
**Authorization Codeグラント種別 [#ka9fbeb3]
***必要な情報の収集 [#sb982345]
-/userinfoエンドポイントのURL
--[[app.configに/userinfoエンドポイントのパスが設定されて...
--汎用認証サイト(Resource Serverエンドポイント)のURLと...
http://(汎用認証サイトのアドレス:ポート)/MultiPurposeA...
***/userinfoエンドポイントにアクセスして、ユーザ属性を取...
-コード~
[[先程のコード>#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> OAuthAuthorizati...
{
if (state == "vj9NCxij4L") // CSRF(XSRF)対策...
{
HttpClient httpClient = new HttpClient();
HttpRequestMessage httpRequestMessage = ...
HttpResponseMessage httpResponseMessage ...
// HttpRequestMessage (Method & RequestU...
httpRequestMessage = new HttpRequestMess...
{
Method = HttpMethod.Post,
RequestUri = new Uri("http://(汎用...
};
// HttpRequestMessage (Headers & Content)
httpRequestMessage.Headers.Authorization...
"Basic",
Convert.ToBase64String(System.Text.E...
string.Format("{0}:{1}",
"21c7769f16634dabaf14282602b...
"xrRczIidMMZcMxvYWpIkvSZX1oR...
httpRequestMessage.Content = new FormUrl...
new Dictionary<string, string>
{
{ "grant_type", "authorization_c...
{ "code", code },
{ "redirect_uri", System.Web.Htt...
});
// HttpResponseMessage
httpResponseMessage = await httpClient.S...
string response = await httpResponseMess...
// 汎用認証サイトのOAuth2.0のレスポンス...
Base64UrlTextEncoder base64UrlEncoder = ...
Dictionary<string, string> dic = JsonCon...
string jwtPayload = Encoding.UTF8.GetStr...
// id_tokenライクなJWTなので、中からsub...
JObject jobj = ((JObject)JsonConvert.Des...
string sub = (string)jobj["sub"];
>↓↓↓ 下を追加 ↓↓↓
// Userエンドポイントに問い合わせを行う。
// HttpRequestMessage (Method & RequestU...
httpRequestMessage = new HttpRequestMess...
{
Method = HttpMethod.Get,
RequestUri = new Uri("http://(汎用...
};
// HttpRequestMessage (Headers)
httpRequestMessage.Headers.Authorization...
// HttpResponseMessage
httpResponseMessage = await httpClient.S...
string userinfo = await httpResponseMess...
jobj = ((JObject)JsonConvert.Deserialize...
sub = (string)jobj["sub"];
}
// 基本的に、仲介コードやアクセストークンは...
// このまま画面に表示させるとQuerystringに仲...
// 必要な情報位を取得した後に、一段、Redirec...
>↑↑↑ 上を追加 ↑↑↑
return View("");
}
}
}
-実行結果
>
#ref(3.png,left,nowrap,3)
***注意事項 [#z796a2ba]
-仲介コードやアクセストークンは画面に露見させないこと。
-このまま画面に表示させるとQuerystringに仲介コードが露見...
必要な情報位を取得した後に、一段、Redirect処理などを経由...
**Implicitグラント種別 [#b7c19466]
前述の[[Authorization Codeグラント種別>#ka9fbeb3]]と異な...
この後の処理をクライアントサイドのJavaScriptで行う。
***必要な情報の収集 [#ydacf245]
[[前述>#sb982345]]と同じ。
***/userinfoエンドポイントにアクセスして、ユーザ属性を取...
以下のモジュールから、
-OAuthImplicitGrantClient.cshtml~
https://github.com/OpenTouryoProject/MultiPurposeAuthSite...
-oauthimplicit.js~
https://github.com/OpenTouryoProject/MultiPurposeAuthSite...
以下のようにOAuthImplicitGrantClient.cshtmlにコードを移植...
-コード
<a href="#" id="OAuthGetUserClaimsWebAPI" url="http://(...
@section scripts{
<script type="text/javascript">
var fragment = "";
var token = "";
function CallOAuthAPI(url, httpMethod, postdata) {
alert(
"<httpMethod>" + "\n" + httpMethod + "\n...
"<url>" + "\n" + url + "\n" +
"<token>" + "\n" + token);
$.ajax({
type: httpMethod,
url: url,
crossDomain: true,
headers: {
'Authorization': 'Bearer ' + token
},
data: postdata,
xhrFields: {
withCredentials: true
},
success: function (responseData, textSta...
alert(textStatus + ', ' + JSON.strin...
},
error: function (responseData, textStatu...
alert(textStatus + ', ' + errorThrow...
}
});
}
$(function () {
// フラグメントを取得し、
fragment = getFragment();
// フラグメントに access_token (Bearer Token...
if (fragment.access_token) {
// access_token (Bearer Token) を使用して
// ResourceServerのWebAPIにアクセスする。
token = fragment.access_token;
// 「access_token」(Bearer Token)が
// "露見"しないようwindow.location.hash...
// ~~~~~~
window.location.hash = fragment.state ||...
// OAuthGetUserClaimsWebAPI
$('#OAuthGetUserClaimsWebAPI').on('click...
CallOAuthAPI($('#OAuthGetUserClaimsW...
});
}
});
// ---------------------------------------------...
// フラグメント(#~の部分)を取得する。
// ---------------------------------------------...
function getFragment() {
// URLの「#」記号の後の部分を取得し、
if (window.location.hash.indexOf("#") === 0) {
// # が1文字目にある場合
// 2文字目以降をobjectにparse。
return parseQueryString(window.location....
} else {
// そうではない場合。
return {}; // 空
}
};
// ---------------------------------------------...
// QueryStringをobjectにparseする。
// ---------------------------------------------...
function parseQueryString(queryString) {
//alert(queryString);
var data = {},
pairs, pair, separatorIndex, escapedKey,...
if (queryString === null) {
return data; // 空で返す。
}
// 分解して、
pairs = queryString.split("&");
// 詰めて、
for (var i = 0; i < pairs.length; i++) {
pair = pairs[i];
separatorIndex = pair.indexOf("=");
if (separatorIndex === -1) {
escapedKey = pair;
escapedValue = null;
} else {
escapedKey = pair.substr(0, separato...
escapedValue = pair.substr(separator...
}
key = decodeURIComponent(escapedKey);
value = decodeURIComponent(escapedValue);
// インデクサで。
data[key] = value;
}
// 返す。
return data;
}
</script>
}
-実行結果
>
#ref(3a.png,left,nowrap,3a,80%)
***注意事項 [#ma7eb138]
URLフラグメントを使用するため、Authorization Codeグラント...
非常に、アクセストークンが画面に露見し易いので注意する。
*OpenID Connect [#xf70352f]
**Authorization Code Flow [#i3cda345]
OpenID ConnectのAuthorization Code Flowをテストする。
***スターター [#t97b9eb3]
上記の[[スターター>#fe72f177]]のScopeにopenidを加えるだけ...
OpenID ConnectのAuthorization Code Flowをテストできる。
<a href="http://(汎用認証サイトのアドレス:ポート)/Mult...
***コード [#mc27d8dc]
特に、id_tokenの検証に関して、以下のコードが参考になる。
-WebFormのredirectエンドポイント~
https://github.com/OpenTouryoProject/OpenTouryo/blob/deve...
-MVCのredirectエンドポイント~
https://github.com/OpenTouryoProject/OpenTouryo/blob/deve...
-id_tokenの検証には、JwtToken.Verifyメソッド、DigitalSign...
--JwtToken.Verifyメソッド~
https://github.com/OpenTouryoProject/OpenTouryo/blob/deve...
--DigitalSignX509.Verifyメソッド~
https://github.com/OpenTouryoProject/OpenTouryo/blob/deve...
**Implicit Flow [#odb23e5a]
OpenID ConnectのImplicit Flowをテストする。
***スターター [#geda9bfa]
Implicit Flowでは、Scopeにopenidを加えるだけでなく、~
response_typeを、response_type=id_token token, id_tokenに...
<a href="http://(汎用認証サイトのアドレス:ポート)/Mult...
***コード [#lca0948f]
JavaScriptでid_tokenの検証をするには...。
WebCrypto APIなるものがあるもよう。
-WebCrypto APIでJSON Web Tokenの検証を試してみる - Qiita~
http://qiita.com/tomoyukilabs/items/faa66805a440f4b30cfb
-自堕落な技術者の日記 : W3C Web Cryptography APIとの~
果てしなき戦い(第2回 RSA署名生成と検証) - livedoor Blog(...
http://blog.livedoor.jp/k_urushima/archives/1759093.html
>公開鍵のインポートと署名の検証
*SpringMVC(Java)でやる。 [#i5d3d94b]
***SpringMVC(Java) [#q2c66de7]
[[コチラ>https://dotnetdevelopmentinfrastructure.osscons....
-Javaを書いてみる。 - .NET 開発基盤部会 Wiki~
https://dotnetdevelopmentinfrastructure.osscons.jp/index....
***コード [#hfcd67a5]
OAuth2.0、OpenID Connect のサンプル・コード(Redirectエン...
package com.example.test1;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMa...
import org.springframework.web.bind.annotation.RequestMe...
import org.springframework.web.bind.annotation.RequestPa...
import org.springframework.web.servlet.ModelAndView;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
@Controller
public class HeloController {
@RequestMapping(value="/", method=RequestMethod.GET)
public ModelAndView index(ModelAndView mav) {
mav.setViewName("index");
return mav;
}
@RequestMapping(value="/", method=RequestMethod.POST)
public ModelAndView send(@RequestParam("text1")String s...
mav.setViewName(
"redirect:http://localhost:63359/MultiPurposeAuthSit...
+ "?client_id=084b7157a4d7427794012ee8a8e6d415"
+ "&response_type=code"
+ "&scope=profile%20email%20phone%20address%20openid"
+ "&state=q58aqOg3"
+ "&nonce=CiC1wEsurMkmMRBh");
return mav;
}
@RequestMapping(value="/code", method=RequestMethod.GET)
public ModelAndView code(@RequestParam("code") String c...
if(state.equals("q58aqOg3")) // CSRF(XSRF)対策のstate...
{
try {
// Requestの準備
String host = "localhost";
String path1 = "/MultiPurposeAuthSite/OAuthBearerTok...
String path2 = "/MultiPurposeAuthSite/userinfo";
String username = "084b7157a4d7427794012ee8a8e6d415";
String password = "JxBaXLNFyK4lCawEY9_HPA2zzjyiLgIiV...
HttpHost httpHost = new HttpHost(host, 63359, "http");
HttpPost httpPost = new HttpPost(path1);
HttpClient client = HttpClientBuilder.create().build...
String auth = username + ":" + password;
byte[] encodedAuth = Base64.getEncoder().encode(auth...
String authHeader = "Basic " + new String(encodedAut...
httpPost.setHeader(HttpHeaders.AUTHORIZATION, authHe...
List<NameValuePair> requestParams = new ArrayList<>();
requestParams.add(new BasicNameValuePair("grant_type...
requestParams.add(new BasicNameValuePair("code",code...
requestParams.add(new BasicNameValuePair("redirect_u...
HttpResponse response = null;
// Requestの発行
httpPost.setEntity(new UrlEncodedFormEntity(requestP...
response = client.execute(httpHost, httpPost);
// HttpStatusの確認
int status = response.getStatusLine().getStatusCode();
if (status == HttpStatus.SC_OK){
// responseの取得
String responseData = EntityUtils.toString(
response.getEntity(), StandardCharsets.UTF_8);
// JSONのParse
Gson gson = new Gson();
Type type = new TypeToken<Map<String, String>>(){}....
Map<String, String> map = gson.fromJson(responseDat...
String access_token = map.get("access_token");
//String token_type = map.get("token_type");
//String expires_in = map.get("expires_in");
//String refresh_token = map.get("refresh_token");
String id_token = map.get("id_token");
// id_tokenの検証
String[] jwt = id_token.split("\\.");
Decoder base64urlDecoder = Base64.getUrlDecoder();
//String jwtHeaderString = new String(base64urlDeco...
String jwtClaimString = new String(base64urlDecode...
//String jwtSignitureString = new String(base64url...
byte[] message = (jwt[0] + "." + jwt[1]).getBytes("...
byte[] sign = base64urlDecoder.decode(jwt[2]);
// JWT(RS-256の検証)
// RSAによる署名と、その検証方法の例。
// 1. RSAキーペアを生成し、公開キー・非公開キーをお...
// 2. PKCS12を読み取り、秘密キーでメッセージを署名...
// 3. DERを読み取り、公開キーでメッセージの署名をベ...
// 手順1の、X.509の生成に、sunの非公開関数を用いて...
// 代替ライブラリとしてはbouncycastleなどがある。 ...
// https://gist.github.com/seraphy/5359542
// X509証明書から、RSA公開キーを復元する.
RSAPublicKey publicKey2;
try (InputStream is = new FileInputStream("C:\\root...
X509Certificate x509 = null;
try {
x509 = this.loadX509(is);
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
publicKey2 = (RSAPublicKey) x509.getPublicKey();
}
// RSA公開キーで署名器を生成する.
Signature verifier = null;
boolean result = false;
verifier = Signature.getInstance("SHA256withRSA");
verifier.initVerify(publicKey2);
verifier.update(message);
result = verifier.verify(sign);
if(result)
{
map = gson.fromJson(jwtClaimString, type);
String iss = map.get("iss");
String aud = map.get("aud");
String nonce = map.get("nonce");
String sub = map.get("sub");
String iat = map.get("iat");
String exp = map.get("exp"); //1505490503;
if(
iss.equals("http://jwtssoauth.opentouryo.com")
&& aud.equals("084b7157a4d7427794012ee8a8e6d415")
&& nonce.equals("CiC1wEsurMkmMRBh")
&& (System.currentTimeMillis() / 1000L) <= Long....
{
// /userinfoへアクセス。
HttpGet httpGet = new HttpGet(path2);
client = HttpClientBuilder.create().build();
authHeader = "Bearer " + access_token;
httpGet.setHeader(HttpHeaders.AUTHORIZATION, auth...
// Requestの発行
response = client.execute(httpHost, httpGet);
// HttpStatusの確認
status = response.getStatusLine().getStatusCode();
if (status == HttpStatus.SC_OK){
// responseの取得
responseData = EntityUtils.toString(response.get...
// JSONのParse
gson = new Gson();
type = new TypeToken<Map<String, String>>(){}.ge...
map = gson.fromJson(responseData, type);
sub = map.get("sub");
}
}
}
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
mav.setViewName("index");
return mav;
}
public X509Certificate loadX509(InputStream is) throws ...
CertificateFactory certFactory = CertificateFactory.ge...
return (X509Certificate) certFactory.generateCertifica...
}
}
ページ名: