LoginSignup
0
0

More than 3 years have passed since last update.

つくるオーオース WEBAPI編

Last updated at Posted at 2020-03-23

はじめに

MVCが出来たのでWEBAPIを実装する事で、
https://qiita.com/namikitakeo/items/0de598b8e43eb5b1ff94

Client Credentials Grantと、
https://qiita.com/namikitakeo/items/0c283b2e5da55670c542

Resource Owner Password Credentials Grantを再現しようと思います。
https://qiita.com/namikitakeo/items/ea23adbc0b5c941ff0ed

つくる

Tokenコントローラーにコメントします。
http://localhost:5000/op/token

Controllers/TokenController.cs
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using myop.Models;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
using System.Security.Cryptography;
using System.IdentityModel.Tokens.Jwt;
using System.Text;

namespace myop.Controllers
{
    public class AccessToken
    {
        public string access_token { get; set; }
        public int? expires_in { get; set; }
        public string token_type { get; set; }
        public string scope { get; set; }
        public string error { get; set; }
        public string error_description { get; set; }
    }

    [Route("op/[controller]")]
    [ApiController]
    public class TokenController : ControllerBase
    {
        private readonly ApplicationDbContext _context;
        string CLIENT_ID;
        string CLIENT_SECRET;
        string GRANT_TYPE;
        string SCOPE;
        string USERNAME;
        string PASSWORD;
        string CODE;
        public TokenController(ApplicationDbContext context)
        {
            _context = context;
        }
        // POST: op/token
        [HttpPost]
        public async Task<ActionResult<AccessToken>> doPost()
        {
            // POSTパラメータ取得
            string body = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
            string[] p =  body.Split('&');
            for (int i=0; i<p.Length; i++){
                string[] values =  p[i].Split('=');
                switch(values[0])
                {
                    case "client_id":CLIENT_ID=values[1];break;
                    case "client_secret":CLIENT_SECRET=values[1];break;
                    case "grant_type":GRANT_TYPE=values[1];break;
                    case "scope":SCOPE=values[1];break;
                    case "username":USERNAME=values[1];break;
                    case "password":PASSWORD=values[1];break;
                    case "code":CODE=values[1];break;
                }
            }
            // POSTパラメータclient_idチェック
            var client = await _context.Clients.FindAsync(CLIENT_ID);
            if (client == null) {
                return new AccessToken {error = "unauthorized_client", error_description="client authentication failed."};
            }
            // access_token発番(authorization_code/client_credentials/password/refresh_token)
            string random = Guid.NewGuid().ToString("N").ToUpper();
            // refresh_token発番(authorization_code/password/refresh_token)
            string refresh = Guid.NewGuid().ToString("N").ToUpper();
            // 要求のgrant_typeをチェック
            if (client.GrantTypes != GRANT_TYPE) {
                return new AccessToken {error = "unsupported_response_type", error_description="the response_type value is not supported."};
            }
            // 要求がclient_credentialsの場合
            if (client.GrantTypes == "client_credentials") {
                // usernameはadmin固定
                USERNAME = "admin";
                // refresh_tokenは発行しない
                refresh = null;
            }
            // 要求がpasswordの場合
            if (client.GrantTypes == "password") {
                // 要求のusernameをチェック
                var user = _context.Users.FirstOrDefault(u => u.UserName == USERNAME);
                if (user == null ) {
                    return new AccessToken {error = "access_denied", error_description="user authentication failed."};
                }
                // 要求のpasswordを共通関数でチェック
                if (!Util.PasswordEqual(user.PasswordHash, PASSWORD)) {
                    return new AccessToken {error = "access_denied", error_description="user authentication failed."};
                }
            }
            // scopeはopenidを最小限とする
            string t="openid";
            if (SCOPE != null) {
                string[] s =  SCOPE.Split(' ');
                for (int j=0; j<s.Length; j++){
                    if (s[j]!="openid" && client.AllowedScope.Contains(s[j])) t=t+" "+s[j];
                }
            }
            // 要求scopeと許可scopeのANDをとる
            SCOPE=t;
            // 機密クライアントの場合
            if (client.AccessType == "confidential") {
                // 要求のclient_secretチェック
                if (client.ClientSecret != CLIENT_SECRET) {
                    return new AccessToken {error = "invalid_request", error_description="client authentication failed."};
                }
            // 公開クライアントの場合
            } else if (client.AccessType == "public") {
                // 要求がclient_credentialsではないかチェック
                if (client.GrantTypes == "client_credentials") {
                    return new AccessToken {error = "invalid_request", error_description="client authentication failed."};
                }
                // 要求にclient_secretは不要
                if (CLIENT_SECRET != null) {
                    return new AccessToken {error = "invalid_request", error_description="client authentication failed."};
                }
            // 不明の場合
            } else {
                return new AccessToken {error = "invalid_request", error_description="client authentication failed."};
            }
            // 削除タイミングがないためaccess_tokenは1ユーザーにつき1つの制限
            var token = await _context.Tokens.FindAsync(USERNAME);
            if (token != null) {
                _context.Tokens.Remove(token);
                await _context.SaveChangesAsync();
            }
            // 有効期限60秒固定のaccess_tokenを発行
            token = new Token {UserId = USERNAME, AccessToken = random, ClientId = CLIENT_ID, RefreshToken=refresh, Scope = SCOPE, Iat=DateTime.Now};
            _context.Add(token);
            await _context.SaveChangesAsync();
            // access_tokenを返す
            return new AccessToken {access_token = random, expires_in=60, token_type="bearer", scope = SCOPE};
        }
    }
}

Client Credentials Grant動いているっぽい。

$ curl -d "client_id=client1&client_secret=client1&grant_type=client_credentials&scope=openid address"
 http://localhost:5000/op/token

{"access_token":"F556BFE8B59B4FF5A8BD199095DB4E45","expires_in":60,"token_type":"bearer","scope":"openid"}

Resource Owner Password Credentials Grant動いているっぽい。

$ curl -d "client_id=client2&grant_type=password&username=user01&password=user01&scope=openid profile"
 http://localhost:5000/op/token

{"access_token":"55BB5D92D07F46FA87227FA1717CD5CC","expires_in":60,"token_type":"bearer","scope":"openid"}

制限事項

  • Client Credentials Grantは特定のユーザーに紐づかないためadminユーザーにてaccess_tokenを発行しています。
  • またaccess_tokenの有効期限は60秒固定で、1ユーザーにつき1つの制限があります。

次回

つくるオーオース Introspect編
https://qiita.com/namikitakeo/items/dca9e901a1d7f8236dec

0
0
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
0
0