10
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Owinを使ったOAuth2.0お勉強メモ#1 - 環境構築&クライアント・クレデンシャルズフロー

Last updated at Posted at 2019-06-29

#はじめに
WindowsのOwinというミドルウェアを使ってOAuthを学習するメモです。

#環境
Windows10のPC1台でやります。

  • Windows10 Pro 1903 (64bit)
  • Visual Studio 2019
    • C#
    • .Net Framework 4.5
    • ASP.net Web アプリケーション
    • コンソールアプリ
  • curl 7.65.1 for Windows

#OAuthとは
以下のデータをインプットすれば完全に理解できます。

#Owinとは
Open Web Interface for .NET
なんだがよくわからないのですが、.NETで使うミドルウェアです。nugetで落とします。
OAuthとかWindowsの頭文字ではないようです・・・

  • http://owin.org/
  • OAuth2.0の機能だけでなくいろいろあるっぽい
  • OAuth使う場合はnugetから以下をインストールしてください
    • Microsoft.Owin.Host.SystemWeb
    • Microsoft.Owin.Security.OAuth
    • Microsoft.AspNet.WebApi.Owin
    • Microsoft.AspNet.Identity.Owin

#環境構築
サンプルソースを作りました。

マイクロソフトの全く理解できない解説もあります。(参考までに)

##サンプルソリューション構成

  • Clients(クライアントアプリ)
    • AuthorizationCodeGrant
    • ClientCredentialGrant
    • ImplicitGrant
    • ResourceOwnerPasswordCredentialGrant
  • AuthorizationServer(認可サーバ)
    • ※ASP.net Web アプリケーション
  • Constants(共通クラス)
    • ※クラスライブラリ(dll)
  • ResourceServer(リソースサーバ)
    • ※ASP.net Web アプリケーション

#クライアント・クレデンシャルズフロー
一番簡単そうなクライアント・クレデンシャルズフローをやってみます。

##とりあえず実行してみる

  1. AuthorizationServer(Webアプリ)を起動
  2. ResourceServer(Webアプリ)を起動
  3. ClientCredentialGrant.exe(コンソールアプリ)を実行
    でClientCredentialGrant.exeの実行結果が以下のように出てきます。
ClientCredentialGrant.exeを実行
C:\work\Sample\C#\ClientCredentialGrant\bin\Debug>ClientCredentialGrant.exe
Requesting Token...
Access Token: bC1ajTMu4q38RUEdYjyRpxkIgKn7voHZdBj_8bZtgkX83Wz7c0zwXsEOWl05k9bOHQsNEVEGITYBQmvazz5-la1k3ljifuw8pnASBTmZsHjg5wqdVF5BXE1lpSdJ0-2ykeed_bxj-FNIbKTWKfgSvQB1Qg44BQRA-At7zqtdb5nBKK1fd5mnJU63o1oLZ_pLQqZBoJElGbJgwb7wNF7YbKpTuh-4Pl6ukfLtn8bntVVCABpR
Access Protected Resource
"123456"

Access Token:のところでAuthorizationServerから取得したアクセストークンが出力され、最後の"12345"がResourceServerからGetしたデータです。

##フローで見てみる
実行しただけでは何が何なのかさっぱりわかりません。
フロー図にしてみます。

Flow_ClientCredentialGrant.png

####ClientCredentialGrant.exe

  • クライアント。
  • 保護対象リソースを参照したい人です。
  • クライアント・クレデンシャルズフローはバック・チャネル・コミュニケーションというブラウザを経由しない通信でいけるんで、このサンプルはコンソールアプリで作られています。
  • ClientId=123456,ClientSecret=abcdefが割り当てられている。

####AuthorizationServer

  • 認可サーバ。
  • アクセストークンを発行する人。
  • http://localhost:11625/OAuth/Tokenでトークンを発行する。

####ResourceServer

  • 保護対象リソース。
  • アクセストークンがあれば、リソースを出してあげる人。
  • http://localhost:38385/api/Meでリソースを出す。
  • このサンプルでは保護しているリソースを返すのではなく、接続してきたUserNameを返す仕様になっています。なのでClientIdの123456を返しています。

##Owinを使ったOAuth
Owinはアクセストークンの生成、検証を勝手にやってくれるので、かなり簡単に実装できます。
とはいえ、何をどうすればいいのか、解りやすいマニュアルはないので、サンプルプログラムを読んで、ググって、理解していく必要があります。
ここから少し詳しくサンプルプログラムの中身を見ていきます。

  • AuthorizationServer
  • ResourceServer
  • ClientCredentialGrant.exe
    の順に見ていきます。

###AuthorizationServer(認可サーバの実装)
クライアント・クレデンシャルズフローでのAuthorizationServerはクライアントの提示するClientIdとClientSecretをチェックして、パスすればアクセストークンを発行します。
Owinを利用してプログラムを作成する人はClientIdとClientSecretをチェックするロジックを書けば、アクセストークンの発行はOwinが勝手にやってくれます、簡単です。
OwinのロジックはStartupクラスに全部まとめて書きます。

具体的には以下の通り

1. 起動時に実行されるStartup.Configuration()イベントで動作パラメータを設定する
`Startup.Configuration()`イベント
Startup.cs
using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(AuthorizationServer.Startup))]

namespace AuthorizationServer
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // ここで初期設定
            ConfigureAuth(app);
        }
        public void ConfigureAuth(IAppBuilder app)
        {
            // Setup Authorization Server
            var option = new OAuthAuthorizationServerOptions
            {
                // アクセストークンエンドポイントの設定
                TokenEndpointPath = new PathString(Paths.TokenPath),

                // HTTPを許可する(リリース時はHTTPSにしないといけないですが、デバックのときはこうしておきましょう)
                AllowInsecureHttp = true,

                // イベントコールバックメソッドの設定
                Provider = new OAuthAuthorizationServerProvider
                {
                    // ClientIdとClientSecretの検証
                    OnValidateClientAuthentication = ValidateClientAuthentication,
                    // ClientCredetailsのときの処理
                    OnGrantClientCredentials = GrantClientCredetails
                },
                // AccessTokenExpireTimeSpanはデフォルトで20:00
            };
            app.UseOAuthAuthorizationServer(option);
        }
    }
}
2. TokenEndpointにアクセスされたときに実行されるStartup.ValidateClientAuthentication()イベントでクライアントの正当性をチェックする

context.Validated()すればチェックOKという事になります。

`ValidateClientAuthentication()`イベント
public partial class Startup
{
    private Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        string clientId;
        string clientSecret;

        // ClientIDとClientSecretをヘッダまたはフォームからGetする
        if (context.TryGetBasicCredentials(out clientId, out clientSecret) ||
            context.TryGetFormCredentials(out clientId, out clientSecret))
        {
            if (clientId == Clients.Client1.Id && clientSecret == Clients.Client1.Secret)
            {
                // Client1のとき->OK
                context.Validated();
            }
            else if (clientId == Clients.Client2.Id && clientSecret == Clients.Client2.Secret)
            {
                // Client2のとき->OK
                context.Validated();
            }
        }
        return Task.FromResult(0);
    }
}
3. Startup.GrantClientCredetails()イベントの実装
  • ここでidentityを生成します。
  • context.Validated(identity)すればチェックOKという事になります。
  • そうするとクライアントにアクセストークンが渡されます。
`GrantClientCredetails()`イベント
public partial class Startup
{
    private Task GrantClientCredetails(OAuthGrantClientCredentialsContext context)
    {
        // identityを生成
        var identity = new ClaimsIdentity(new GenericIdentity(context.ClientId, OAuthDefaults.AuthenticationType), context.Scope.Select(x => new Claim("urn:oauth:scope", x)));
        context.Validated(identity);

        return Task.FromResult(0);
    }
}

###ResourceServer(保護対象リソースの実装)
保護対象リソースを引き渡すためのWebAPIで/API/Meを提供しています。
ここにアクセスしてくるクライアントが提示するアクセストークンを検証し、有効なアクセストークンであれば、リソースを返却します。
このサンプルで返却するリソースはアクセスしてきたユーザー名です。厳密にはアクセストークンの中に含まれるUserNameです。

アクセストークンの検証は簡単です。

1. WebAPIの初期設定をする
WebAPIの初期設定
Global.asax.cs
using System.Web.Http;

namespace ResourceServer
{
    public class Global : System.Web.HttpApplication
    {

        protected void Application_Start(object sender, EventArgs e)
        {
            // Microsoft.AspNet.WebApi.WebHostをnugetから追加すること
            GlobalConfiguration.Configure(WebApiConfig.Register);
        }

        public static class WebApiConfig
        {
            public static void Register(HttpConfiguration config)
            {
                // Web API configuration and services

                // Web API routes
                config.MapHttpAttributeRoutes();

                config.Routes.MapHttpRoute(
                    name: "DefaultApi",
                    routeTemplate: "api/{controller}/{id}",
                    defaults: new { id = RouteParameter.Optional }
                );
            }
        }
    }
}
2. 起動時に実行されるStartup.Configuration()イベントで動作パラメータを設定する
`Configuration()`イベント
Startup.cs
[assembly: OwinStartup(typeof(ResourceServer.Startup))]

namespace ResourceServer
{
    // クラスの追加→OWIN Startupクラス
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseOAuthBearerAuthentication(new Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationOptions());
        }
    }
}
3.WebAPIにアクセスされたときに実行されるMeController.Get()イベントを実装する
  • [Authorize]属性を付けます。
  • これをつけるだけでOwinがアクセストークンの検証を勝手にやってくれます。
  • アクセストークンが有効な場合だけMeController.Get()イベントが実行されるので、データを返却します。
`MeController.Get()`イベント
MeController.cs
namespace ResourceServer
{
    // http://localhost:38385/api/Me
    // で実行されるWebAPI
    // [Authorize]属性がついているので、AccessTokenが有効な場合だけ実行される
    // AccessTokenの検証はOwinが勝手にやってくれる
    [Authorize]
    public class MeController : ApiController
    {
        public string Get()
        {
            // User.IdentityはApiControllerクラスのメンバ
            // AccessTokenを復号したクラス
            // Nameを返すと呼び出しAppにNameの内容がResponseとして返る
            return this.User.Identity.Name;
        }
    }
}

###ClientCredentialGrant.exe(クライアントの実装)

サンプルプログラムだと何やっているかいまいちわからないのでcurlで同じことをやります。
(最近のWindowsはcurlが標準で入っています)

1. 認可サーバのトークンエンドポイントにPOSTしてTokenをGETする
TokenをGETする
cURLでToken取得のPOSTする
C:\Users\gebo>curl -XPOST http://localhost:11625/OAuth/Token -H "Authorization:Basic MTIzNDU2OmFiY2RlZg==" -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=client_credentials"
{"access_token":"GlArJSuFC8dqXCJT6dXEKTBeuedcyI_239qsC4HJpdlqJssztctPV9hEQ6jzGAtRveqv5Olo_Tru-QwkZH3ksbZlSh2DHRDI0zOpIYk27kf5cQGaRl6Znx_5LKnqgIJUjQj1Mmb_G246lgE8_Vxapbb5fhzBOenGdMujbREAJpp819hKk8VDXPMnYBndPg0LXERPoRHS48aBKvAy5xO_x8kvXGPFuCT89yCht6CMqh4","token_type":"bearer","expires_in":1199}

// 見やすくすると
// コマンド
curl -XPOST http://localhost:11625/OAuth/Token
// Authorizationヘッダ Basic半角スペースのあとClientIdとClientSecretをコロンでつないでBASE64変換したものを指定する
-H "Authorization:Basic MTIzNDU2OmFiY2RlZg==" 
// Content-Typeヘッダ Bodyあるよという意味でこの指定
-H "Content-Type: application/x-www-form-urlencoded" 
// Body grant_typeの指定はclient_credentialsにすること
-d "grant_type=client_credentials"

// レスポンス
{
  // 取得したアクセストークン
  "access_token":"GlArJSuFC8dqXCJT6dXEKTBeuedcyI_239qsC4HJpdlqJssztctPV9hEQ6jzGAtRveqv5Olo_Tru-QwkZH3ksbZlSh2DHRDI0zOpIYk27kf5cQGaRl6Znx_5LKnqgIJUjQj1Mmb_G246lgE8_Vxapbb5fhzBOenGdMujbREAJpp819hKk8VDXPMnYBndPg0LXERPoRHS48aBKvAy5xO_x8kvXGPFuCT89yCht6CMqh4",
  // トークンタイプ
  "token_type":"bearer",
  // アクセストークンの有効期限(秒) 1199秒≒20分
  "expires_in":1199
}
2. リソースサーバのWebAPIをたたいてリソースをGETする

さっき取得したアクセストークンはここで使います。

リソースをGETする
cURLでResourceServerにアクセスする
C:\Users\gebo>curl http://localhost:38385/api/Me -H "Authorization:Bearer GlArJSuFC8dqXCJT6dXEKTBeuedcyI_239qsC4HJpdlqJssztctPV9hEQ6jzGAtRveqv5Olo_Tru-QwkZH3ksbZlSh2DHRDI0zOpIYk27kf5cQGaRl6Znx_5LKnqgIJUjQj1Mmb_G246lgE8_Vxapbb5fhzBOenGdMujbREAJpp819hKk8VDXPMnYBndPg0LXERPoRHS48aBKvAy5xO_x8kvXGPFuCT89yCht6CMqh4"
"123456"

// 見やすくすると
// コマンド
curl http://localhost:38385/api/Me
// Authorizationヘッダ Bearer半角スペースのあと取得したアクセストークンを指定する
-H "Authorization:Bearer GlArJSuFC8dqXCJT6dXEKTBeuedcyI_239qsC4HJpdlqJssztctPV9hEQ6jzGAtRveqv5Olo_Tru-QwkZH3ksbZlSh2DHRDI0zOpIYk27kf5cQGaRl6Znx_5LKnqgIJUjQj1Mmb_G246lgE8_Vxapbb5fhzBOenGdMujbREAJpp819hKk8VDXPMnYBndPg0LXERPoRHS48aBKvAy5xO_x8kvXGPFuCT89yCht6CMqh4"

// レスポンス
"123456"

#おつかれさまでした

Owin学習コスト高いな・・・

MSのサンプルを参考に作成したソース
https://github.com/gebogebogebo/OwinOAuthSample/tree/master/ClientCredentialGrant

#2につづく

10
10
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?