MiniAPI(十)：ポリシーベースの認証と認可

JWTはロールベースでもカスタムポリシーベースでも、実装手順は同じです。カスタムポリシーベースの手順は以下の通りです：

1. appsettings.jsonでJWTのパラメータを設定する
2. 認証と認可のサービスとミドルウェアを追加し、それをポリシーモードとポリシー名で設定する
3. トークン生成メソッドとトークンパラメータの確認メソッドを定義する
4. ログイン時に身元を確認しトークンを配布する
5. `AuthorizationHandler<IAuthorizationRequirement>`を継承し、認証ルールを実装する

次に具体的な実装を見ていきましょう。

**JWT設定**

```json
"JWTConfig": {
    "Secret": "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
    "Issuer": "gsw",
    "Audience": "everone",
    "Expires": 10000
}
```

**カスタムポリシーの実装**

```csharp
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

var builder = WebApplication.CreateBuilder();
// JWT設定をバインドする
var jwtConfig = new JWTConfig();
builder.Configuration.GetSection("JWTConfig").Bind(jwtConfig);
builder.Services.AddSingleton(jwtConfig);
// 認可データを注入（キャッシュに置いて認証時に使用することも可能）
builder.Services.AddSingleton(new List<Permission> { 
    new Permission { RoleName = "admin", Url = "/helloadmin", Method = "get" } 
});
// カスタムポリシー処理タイプを注入
builder.Services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
// 認証と認可を注入し、Policy名をPermissionに設定
builder.Services
    .AddAuthorization(options =>
    {
        var permissionRequirement = new PermissionRequirement();
        options.AddPolicy("Permission", policy => policy.AddRequirements(permissionRequirement));
    })
    .AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, opt =>
    {
        opt.RequireHttpsMetadata = false;
        opt.TokenValidationParameters = JwtToken.CreateTokenValidationParameters(jwtConfig);
    });

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
// 「Permission」ポリシーに基づいて認証する3つのgetリクエストをマップ
app.MapGet("/hellosystem", (ILogger<Program> logger, HttpContext context) =>
{
    var message = $"hello,system,{context.User?.Identity?.Name}";
    logger.LogInformation(message);
    return message;
}).RequireAuthorization("Permission");

app.MapGet("/helloadmin", (ILogger<Program> logger, HttpContext context) =>
{
    var message = $"hello,admin,{context.User?.Identity?.Name}";
    logger.LogInformation(message);
    return message;
}).RequireAuthorization("Permission");

app.MapGet("/helloall", (ILogger<Program> logger, HttpContext context) =>
{
    var message = $"hello,all roles,{context.User?.Identity?.Name}";
    logger.LogInformation(message);
    return message;
}).RequireAuthorization("Permission");

// ログインしてトークンを配布
app.MapPost("/login", [AllowAnonymous] (ILogger<Program> logger, LoginModel login, JWTConfig jwtConfig) =>
{
    logger.LogInformation("login");
    if (login.UserName == "gsw" && login.Password == "111111")
    {
        var now = DateTime.UtcNow;
        var claims = new Claim[] {
                new Claim(ClaimTypes.Role, "admin"),
                new Claim(ClaimTypes.Name, "桂素伟"),
                new Claim(ClaimTypes.Sid, login.UserName),
                new Claim(ClaimTypes.Expiration, now.AddSeconds(jwtConfig.Expires).ToString())
        };
        var token = JwtToken.BuildJwtToken(claims, jwtConfig);
        return token;
    }
    else
    {
        return "username or password is error";
    }
});

app.Run();
```

**実行結果**

1. ログインしていない場合、401を返す
![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRAlojKvgR3l5Hd63JHW74GIeUVfA4YMBCvr8OBU9XhJJo8icH1PyMAxtiaeZjA0oBPlqNTzouu6hmVQ/640?wx_fmt=png)
2. ログインしてトークンを取得
![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRAlojKvgR3l5Hd63JHW74GI0zU1zfj6ia8l4X4XrkLQC4iaLe9M06twmP26xhcortfGPwARDzxsVU3Q/640?wx_fmt=png)
3. 正常にアクセス
![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRAlojKvgR3l5Hd63JHW74GINC8wx4gicQvj3iaV8T8hbEpKALM6HP2nNRoRLdRDxxjayialBoTFliaECw/640?wx_fmt=png)
4. 許可されていないアクセス、403を返す
![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRAlojKvgR3l5Hd63JHW74GI1XPWwWLp4MwgCDfdc34UeYOzDOXEMYR7FLIr7O3gdflHqDmTWmMhBw/640?wx_fmt=png)

(Translated by GPT)

元のリンク：https://mp.weixin.qq.com/s?__biz=MzA3NDM1MzIyMQ==&mid=2247484887&idx=1&sn=52bf2ac9ca7179fb957001b002e74a0f&chksm=9f005afda877d3eb6f68f010e5f10bebda4bc445c961348aed7302abf351f3e1b7a321b05978&token=1806044876&lang=zh_CN#rd