はじめに
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