はじめに
ASP.NET Core で JWT Bearer 認証を使うときに Startup.ConfigureServices
で呼び出す AddJwtBearer
は、
任意の authenticationScheme を指定できる。
となると、authenticationScheme さえ重複しなければ複数回呼び出しても問題ない、はず。
気になったので実験してみた。
AddJwtBearer
では、authenticationScheme だけでなく署名に使うキーも変えておく。
JWT Bearer のデフォルト authenticationScheme は Bearer だけど、今回は分かりやすいように Bearer1 と Bearer2 にしてみた。
using System;
using System.ComponentModel.DataAnnotations;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
namespace JwtBearerSample
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
public class Startup
{
internal static readonly SymmetricSecurityKey Bearer1SecurityKey =
new SymmetricSecurityKey(Guid.Parse("FA99A883-1B90-4515-A841-64BE3322A663").ToByteArray());
internal static readonly SymmetricSecurityKey Bearer2SecurityKey =
new SymmetricSecurityKey(Guid.Parse("AECD611D-B2DC-4909-B921-8465A9C238A8").ToByteArray());
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication("Bearer1")
.AddJwtBearer("Bearer1", options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateActor = false,
ValidateLifetime = true,
IssuerSigningKey = Bearer1SecurityKey
};
})
.AddJwtBearer("Bearer2", options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateActor = false,
ValidateLifetime = true,
IssuerSigningKey = Bearer2SecurityKey
};
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
public class TokenRequest
{
[Required]
public string UserName { get; set; }
}
public class TokenResponse
{
public string Token { get; set; }
}
[Route("api/[controller]")]
[ApiController]
public class AccountController : ControllerBase
{
[HttpPost("bearer1token")]
public ActionResult<TokenResponse> Bearer1Token([FromBody] TokenRequest request)
{
var token = GenerateJwtToken(request.UserName, Startup.Bearer1SecurityKey);
return new TokenResponse
{
Token = token,
};
}
[HttpPost("bearer2token")]
public ActionResult<TokenResponse> Bearer2Token([FromBody] TokenRequest request)
{
var token = GenerateJwtToken(request.UserName, Startup.Bearer2SecurityKey);
return new TokenResponse
{
Token = token,
};
}
string GenerateJwtToken(string userName, SecurityKey securityKey)
{
var claims = new[]
{
new Claim(ClaimTypes.Name, userName),
};
var credentials = new SigningCredentials(
securityKey,
SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
"ExampleServer",
"ExampleClients",
claims,
expires: DateTime.Now.AddSeconds(60),
signingCredentials: credentials);
var tokenHandler = new JwtSecurityTokenHandler();
return tokenHandler.WriteToken(token);
}
}
[Route("api/[controller]")]
[ApiController]
public class GreetingController : ControllerBase
{
[Authorize(AuthenticationSchemes = "Bearer1")]
[HttpGet("hello")]
public string Hello()
{
return $"Hello {User.Identity.Name}";
}
[Authorize(AuthenticationSchemes = "Bearer2")]
[HttpGet("good-morning")]
public string GoodMorning()
{
return $"Good morning {User.Identity.Name}";
}
[Authorize(AuthenticationSchemes = "Bearer1,Bearer2")]
[HttpGet("good-afternoon")]
public string GoodAfternoon()
{
return $"Good afternoon {User.Identity.Name}";
}
}
}
Client
Bearer1 と Bearer2 それぞれのアクセストークンを使って、保護しているすべての API を呼び出してみる。
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace Client
{
class Program
{
static readonly Uri BaseAddress = new Uri("http://localhost:5000");
static async Task Main(string[] args)
{
await Bearer1Test();
Console.WriteLine();
await Bearer2Test();
Console.ReadLine();
}
static async Task Bearer1Test()
{
Console.WriteLine("== Bearer1 ==");
var httpClient = new HttpClient()
{
BaseAddress = BaseAddress,
};
var tokenResponse = await httpClient.PostAsync(
"/api/account/bearer1token",
new StringContent(
@"{
""userName"": ""Kubo""
}",
Encoding.UTF8,
"application/json"));
var json = await tokenResponse.Content.ReadAsStringAsync();
var jDocument = JsonDocument.Parse(json);
var accessToken = jDocument.RootElement.GetProperty("token").GetString();
Console.WriteLine(accessToken);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", accessToken);
var helloResponse = await httpClient.GetAsync("/api/greeting/hello");
Console.WriteLine(await helloResponse.Content.ReadAsStringAsync());
var goodMorningResponse = await httpClient.GetAsync("/api/greeting/good-morning");
Console.WriteLine(goodMorningResponse.StatusCode);
var goodAfternoonResponse = await httpClient.GetAsync("/api/greeting/good-afternoon");
Console.WriteLine(await goodAfternoonResponse.Content.ReadAsStringAsync());
}
static async Task Bearer2Test()
{
Console.WriteLine("== Bearer2 ==");
var httpClient = new HttpClient()
{
BaseAddress = BaseAddress,
};
var tokenResponse = await httpClient.PostAsync(
"/api/account/bearer2token",
new StringContent(
@"{
""userName"": ""Minamino""
}",
Encoding.UTF8,
"application/json"));
var json = await tokenResponse.Content.ReadAsStringAsync();
var jDocument = JsonDocument.Parse(json);
var accessToken = jDocument.RootElement.GetProperty("token").GetString();
Console.WriteLine(accessToken);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", accessToken);
var helloResponse = await httpClient.GetAsync("/api/greeting/hello");
Console.WriteLine(helloResponse.StatusCode);
var goodMorningResponse = await httpClient.GetAsync("/api/greeting/good-morning");
Console.WriteLine(await goodMorningResponse.Content.ReadAsStringAsync());
var goodAfternoonResponse = await httpClient.GetAsync("/api/greeting/good-afternoon");
Console.WriteLine(await goodAfternoonResponse.Content.ReadAsStringAsync());
}
}
}
実行結果
Bearer1 用のアクセストークンを使うと、AuthenticationSchemes に Bearer1 を指定している Hello と GoodAfternoon は呼び出せるが、指定していない GoodMorning は呼び出せない。
Bearer2 用のアクセストークンを使うと、AuthenticationSchemes に Bearer2 を指定している GoodMorning と GoodAfternoon は呼び出せるが、指定していない Hello は呼び出せない。
おわりに
予想通り、authenticationScheme を変えて複数回 AddJwtBearer
を呼び出してもちゃんと動くことを確認できた。
十中八九上手くいくと思っていても、実際に動かしてこの目で確認するまでは安心できないからね。