5
4

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お勉強メモ#2 - アクセストークン

Last updated at Posted at 2019-06-29

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

#1ではOwinを使ったクライアント・クレデンシャルズフローを学習しましたが、以下の疑問点が残りました。

  • AuthorizationServerは自身が発行していたアクセストークンをどこに保存しているのか?
  • AuthorizationServerで生成しているidentityって何?
  • ResourceServerでユーザー名をどこからともなくGETしてきているが、どこからGETしているのか?
  • ResourceServerはどうやってアクセストークンの有効期限をチェックしているのか?

#アクセストークン
疑問点はアクセストークンがどうなっているのかを理解すれば解決します。

  • AuthorizationServerは自身が発行していたアクセストークンをどこに保存しているのか?
    • 保存していない。必要な情報はすべてアクセストークンに入れているのでストアする必要がない。
  • AuthorizationServerで生成しているidentityって何?
    • アクセストークンそのもの。identityオブジェクトを暗号化してアクセストークンにします。
  • ResourceServerでユーザー名をどこからともなくGETしてきているが、どこからGETしているのか?
    • アクセストークンの中からGETしている。
  • ResourceServerはどうやってアクセストークンの有効期限をチェックしているのか?
    • アクセストークンの中に有効期限が入っている。

ちなみに、
OAuthではアクセストークンの形式は定義していません。OAuthが定義するフローが実現できればアクセストークンのフォーマットは自由、ということみたいです。
なのでここに書いてあるアクセストークンの仕様はOwin独自のもの(と思われ)ます。

#1で見たようにアクセストークンは普通に見てもこのような情報が含まれているかどうかわかりません。暗号化されているのです。
アクセストークンの暗号化と復号はOwinが内部で勝手にやっているので我々は知る必要がないのですが、気になります。

例えばこんなトークン
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&scope=gebo foo"

{
  "access_token":"npgSf2J_zRD_TiIncJ-9ecw5HnW-YHD3j-EgL9rJEGF8HmP08GXGD90S9r9aZ7ua2yCsZH5aKtiXPh9vQZxjPKzCkb5i3TppLVogSRzOwdDpLJE6xyP1icPzqy6TztDl2P9zzUE-YNinNK4l25S8w5wMGKSpF2P9u_ccGPmoWdwdH0KOYMVe6cqBXMmVmyA0Cpra04jg8czWWuQgdODzUhayOJRyul3r9Q3rtcuazZmfDZRt",
  "token_type":"bearer",
  "expires_in":1199
}

#アクセストークンの復号
というわけで、復号してみましょう。
以下APIで復号できます。

// Web.configのmachineKeyで指定された暗号化キーでデータを復号する
System.Web.Security.MachineKey.Unprotect()

使い方はこんな感じ

    protected void Page_Load(object sender, EventArgs e)
    {
        // GETしたアクセストークン
        var token = "npgSf2J_zRD_TiIncJ-9ecw5HnW-YHD3j-EgL9rJEGF8HmP08GXGD90S9r9aZ7ua2yCsZH5aKtiXPh9vQZxjPKzCkb5i3TppLVogSRzOwdDpLJE6xyP1icPzqy6TztDl2P9zzUE-YNinNK4l25S8w5wMGKSpF2P9u_ccGPmoWdwdH0KOYMVe6cqBXMmVmyA0Cpra04jg8czWWuQgdODzUhayOJRyul3r9Q3rtcuazZmfDZRt";

        // Tokenはこれで解析できる
        var dec = new TokenDecrypt();
        if( dec.Decrypt(token) == false )
        {
            // Decrypt Error;
            return;
        }
        string result = "Decrypt Success!\r\n\r\n";
        result = result + "トークン発行日時 : " + dec.DateTimeIssued.ToString() + "\r\n";
        result = result + "トークン失効日時 : " + dec.DateTimeExpires.ToString() + "\r\n";
        result = result + "IdentityName : " + dec.IdentityName.ToString() + "\r\n";

        result = result + $"Roles({dec.Roles.Count}) : \r\n";
        foreach (var r in dec.Roles)
        {
            result = result + "- " + r + "\r\n";
        }

        result = result + $"Claims({dec.Claims.Count}) : \r\n";
        foreach (var c in dec.Claims)
        {
            result = result + $"- Type={c.Type} , Value={c.Value}" + "\r\n";
        }
        TextBox2.Text = result;
    }

    public class TokenDecrypt
    {
        public class Claim
        {
            public string Type;
            public string Value;
        }

        public DateTime DateTimeIssued { get; private set; }
        public DateTime DateTimeExpires { get; private set; }

        public string IdentityName { get; private set; }
        public List<string> Roles { get; private set; }
        public List<TokenDecrypt.Claim> Claims { get; private set; }

        // OWINで生成したアクセストークンを復号する
        public bool Decrypt(string accessToken)
        {
            // Decrypt
            var secureDataFormat = new TicketDataFormat(new MachineKeyProtector());
            AuthenticationTicket ticket = secureDataFormat.Unprotect(accessToken);

            {
                var jstTimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
                DateTimeIssued = System.TimeZoneInfo.ConvertTimeFromUtc(ticket.Properties.IssuedUtc.Value.DateTime, jstTimeZoneInfo);
                DateTimeExpires = System.TimeZoneInfo.ConvertTimeFromUtc(ticket.Properties.ExpiresUtc.Value.DateTime, jstTimeZoneInfo);
            }

            // get identity
            var identity = ticket.Identity as ClaimsIdentity;
            var roleClaims = identity.Claims.Where(x => x.Type == ClaimsIdentity.DefaultRoleClaimType).Select(x => x.Value).ToList();
            var nonRoleClaims = identity.Claims.Where(x => x.Type != ClaimsIdentity.DefaultRoleClaimType).Select(x => new { Type = x.Type, Value = x.Value }).ToList();

            IdentityName = identity.Name;
            Roles = roleClaims;

            Claims = new List<Claim>();
            foreach ( var c in nonRoleClaims)
            {
                var setc = new TokenDecrypt.Claim();
                setc.Type = c.Type;
                setc.Value = c.Value;
                Claims.Add(setc);
            }

            return true;
        }

        private class MachineKeyProtector : IDataProtector
        {
            private readonly string[] _purpose =
            {
                typeof(OAuthAuthorizationServerMiddleware).Namespace,
                "Access_Token",
                "v1"
            };

            public byte[] Protect(byte[] userData)
            {
                throw new NotImplementedException();
            }

            public byte[] Unprotect(byte[] protectedData)
            {
                // 復号する
                // MachineKey=復号するキー(暗号化するときと同じ値を指定する必要がある)
                // Web.config で MachineKey を明示的に指定していない場合には自動生成される
                return System.Web.Security.MachineKey.Unprotect(protectedData, _purpose);
            }
        }

    }

web.configにはこんな感じで暗号化キーを指定します

web.config
  <system.web>
    ・・・
    <machineKey validationKey="6CCD8F2FA7593F3108A607DFAF5A7C579E06F802A9B97BE71FD9DF949ED95CCCB3CAD863ECC07FB2F18BA2A10A241572A65EBB5FCF67AD36669BEFE476AB8F36" decryptionKey="0B3EFF1C0890F814D43FFD56EB88ACEAE8E517FD5D052E8ACE0669CA1D0DE097" validation="SHA1" decryption="AES" />
    ・・・
  </system.web>

暗号化と復号で同じパラメータでないといけません。なので、AuthorizationServerとResourceServerには同じmachineKeyを設定しておく必要があります。
web.configにmachineKeyがない場合は自動的にmachineKeyを生成しているようです。
この場合、どんなタイミングでmachineKeyが変わるのかわからないので、ちゃんと指定したほうがよさそうです。

トークンを復号した結果です

結果
Decrypt Success!

トークン発行日時 : 2019/06/30 16:30:44
トークン失効日時 : 2019/06/30 16:50:44
IdentityName : 123456
Roles(0) : 
Claims(3) : 
- Type=http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name , Value=123456
- Type=urn:oauth:scope , Value=gebo
- Type=urn:oauth:scope , Value=foo

#machineKeyの生成
こんなわけのわからないキー、コピペするしかないですね。
でも、ちゃんとしたプログラムにするんであれば、自サイト専用の値にしたいです。
とはいえ、適当な値ではダメなようです・・・

調べてみると MachineKey Generator で検索すると結構ヒットするので、これらのサイトで生成しましょう。

例:ASP.Net MachineKey Generator
https://www.allkeysgenerator.com/Random/ASP-Net-MachineKey-Generator.aspx

#おつかれさまでした
トークンのことがわかり少しスッキリした。

ここで作成したサンプルプログラムはコチラ
https://github.com/gebogebogebo/OwinOAuthSample/tree/master/AccessToken
https://github.com/gebogebogebo/OwinOAuthSample/tree/master/AccessTokenDesktop

#3につづく

5
4
0

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
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?