私は私のWeb APIアプリケーションでJWTベアラトークン(JSON Web Token)をサポートしようとしていますが、迷子になります。
.NET CoreとOWINアプリケーションがサポートされています。
現在IISでアプリケーションをホストしています。
アプリケーションでこの認証モジュールを実現するにはどうすればいいですか。フォーム/ Windows認証を使用するのと同じように、<authentication>
構成を使用する方法はありますか?
私はこの質問に答えました: 4年前にHMACを使用してASP.NET Web APIをセキュアにする方法 。
現在、セキュリティに関して多くのことが変更され、特にJWTが普及しています。ここでは、私ができる最も簡単で基本的な方法でJWTを使用する方法を説明しようとするので、OWIN、Oauth2、ASP.NET Identityなどのジャングルから迷子になることはありません。
JWTトークンがわからない場合は、少し見てみる必要があります。
https://tools.ietf.org/html/rfc7519
基本的に、JWTトークンは以下のようになります。
<base64-encoded header>.<base64-encoded claims>.<base64-encoded signature>
例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1NzI0LCJleHAiOjE0Nzc1NjY5MjQsImlhdCI6MTQ3NzU2NTcyNH0.6MzD1VwA5AcOcajkFyKhLYybr3h13iZjDyHm9zysDFQ
JWTトークンには3つのセクションがあります。
上記のトークンを使用してWebサイト jwt.io を使用すると、以下のようにデコードしてトークンを見ることができます。
技術的には、JWTはヘッダーから署名された署名を使用し、ヘッダーで指定されたセキュリティアルゴリズムを使用します(例:HMACSHA256)。したがって、クレームに機密情報を格納している場合は、JWTをHTTP経由で転送する必要があります。
今、JWT認証を使用するために、あなたがレガシーWeb Apiシステムを持っているなら、あなたは本当にOWINミドルウェアを必要としません。単純な概念は、要求が来たときにJWTトークンを提供する方法とトークンを検証する方法です。それでおしまい。
デモに戻ると、JWTトークンを軽量に保つために、JWTにはusername
とexpiration time
のみを格納しています。しかし、このようにして、ロールの承認をしたい場合は、新たなローカルID(プリンシパル)を再構築して、ロールのような情報を追加する必要があります。しかし、JWTにもっと情報を追加したいのであれば、それはあなた次第です、非常に柔軟です。
OWINミドルウェアを使用する代わりに、コントローラーからのアクションを使用してJWTトークン・エンドポイントを単に提供することができます。
public class TokenController : ApiController
{
// This is naive endpoint for demo, it should use Basic authentication to provide token or POST request
[AllowAnonymous]
public string Get(string username, string password)
{
if (CheckUser(username, password))
{
return JwtManager.GenerateToken(username);
}
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
public bool CheckUser(string username, string password)
{
// should check in the database
return true;
}
}
本番環境では、JWTトークンを提供するためにPOST requestまたは基本認証エンドポイントを使用する必要があります。
username
に基づいてトークンを生成する方法
トークンを生成するのにMSからのSystem.IdentityModel.Tokens.Jwt
と呼ばれるNuGetパッケージ、あるいはあなたが望むなら別のパッケージさえも使うことができます。デモでは、SymmetricKey
と共にHMACSHA256
を使用します。
/// <summary>
/// Use the below code to generate symmetric Secret Key
/// var hmac = new HMACSHA256();
/// var key = Convert.ToBase64String(hmac.Key);
/// </summary>
private const string Secret = "db3OIsj+BXE9NZDy0t8W3TcNekrF+2d/1sFnWG4HnV8TZY30iTOdtVWJG8abWvB1GlOgJuQZdcF2Luqm/hccMw==";
public static string GenerateToken(string username, int expireMinutes = 20)
{
var symmetricKey = Convert.FromBase64String(Secret);
var tokenHandler = new JwtSecurityTokenHandler();
var now = DateTime.UtcNow;
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, username)
}),
Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(symmetricKey), SecurityAlgorithms.HmacSha256Signature)
};
var stoken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(stoken);
return token;
}
JWTトークンを提供するためのエンドポイントは、リクエストが来たときにJWTを検証する方法です。デモでは、JwtAuthenticationAttribute
から継承するIAuthenticationFilter
を構築しました。認証フィルターの詳細については、 here を参照してください。
この属性を使用すると、任意のアクションを認証できます。そのアクションにこの属性を追加するだけです。
public class ValueController : ApiController
{
[JwtAuthentication]
public string Get()
{
return "value";
}
}
WebApiへのすべての着信要求を検証したい場合は、OWINミドルウェアまたはDelegateHanderを使用することもできます(コントローラーまたはアクションに限定されない)。
以下は認証フィルタからのコアメソッドです:
private static bool ValidateToken(string token, out string username)
{
username = null;
var simplePrinciple = JwtManager.GetPrincipal(token);
var identity = simplePrinciple.Identity as ClaimsIdentity;
if (identity == null)
return false;
if (!identity.IsAuthenticated)
return false;
var usernameClaim = identity.FindFirst(ClaimTypes.Name);
username = usernameClaim?.Value;
if (string.IsNullOrEmpty(username))
return false;
// More validate to check whether username exists in system
return true;
}
protected Task<IPrincipal> AuthenticateJwtToken(string token)
{
string username;
if (ValidateToken(token, out username))
{
// based on username to get more information from database in order to build local identity
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username)
// Add more claims if needed: Roles, ...
};
var identity = new ClaimsIdentity(claims, "Jwt");
IPrincipal user = new ClaimsPrincipal(identity);
return Task.FromResult(user);
}
return Task.FromResult<IPrincipal>(null);
}
ワークフローは、JWTライブラリ(上記のNuGetパッケージ)を使用してJWTトークンを検証してからClaimsPrincipal
を返すことです。ユーザーがシステムに存在するかどうかを確認し、必要に応じて他のカスタム検証を追加するなど、さらに検証を実行できます。 JWTトークンを検証してプリンシパルを取り戻すためのコード。
public static ClaimsPrincipal GetPrincipal(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;
if (jwtToken == null)
return null;
var symmetricKey = Convert.FromBase64String(Secret);
var validationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
};
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
return principal;
}
catch (Exception)
{
//should write log
return null;
}
}
JWTトークンが検証され、プリンシパルが返却された場合は、新しいローカルIDを作成し、それにロール許可を確認するための詳細情報を追加する必要があります。
リソースへの匿名の要求を防ぐために、グローバルスコープでconfig.Filters.Add(new AuthorizeAttribute());
(デフォルトの承認)を追加することを忘れないでください。
Postmanを使ってデモをテストすることができます。
トークンを要求します(上記のとおり、単純にデモ用です)。
GET http://localhost:{port}/api/token?username=cuong&password=1
許可された要求のヘッダーにJWTトークンを入れます。例えば、
GET http://localhost:{port}/api/value
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1MjU4LCJleHAiOjE0Nzc1NjY0NTgsImlhdCI6MTQ3NzU2NTI1OH0.dSwwufd4-gztkLpttZsZ1255oEzpWCJkayR_4yvNL1s
デモはここに置かれています: https://github.com/cuongle/WebApi.Jwt
私は、最小限の努力でそれを達成することができました(ASP.NET Coreと同じくらい簡単です)。
そのためにはOWINのStartup.cs
ファイルとMicrosoft.Owin.Security.Jwt
ライブラリを使います。
アプリがStartup.cs
をヒットするためには、Web.config
を修正する必要があります。
<configuration>
<appSettings>
<add key="owin:AutomaticAppStartup" value="true" />
...
これがStartup.cs
の外観です。
using MyApp.Helpers;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Jwt;
using Owin;
[Assembly: OwinStartup(typeof(MyApp.App_Start.Startup))]
namespace MyApp.App_Start
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = new TokenValidationParameters()
{
ValidAudience = ConfigHelper.GetAudience(),
ValidIssuer = ConfigHelper.GetIssuer(),
IssuerSigningKey = ConfigHelper.GetSymmetricSecurityKey(),
ValidateLifetime = true,
ValidateIssuerSigningKey = true
}
});
}
}
}
皆さんの多くは、今日ASP.NET Coreを使用していますので、ご覧のとおり、それは私たちが持っているものとそれほど違いはありません。
最初は私を困惑させました。私はカスタムプロバイダなどを実装しようとしていましたが、それほど単純であるとは思っていませんでした。 OWIN
はただ揺れ動きます!
1つだけ言及する必要があります - 私がOWIN Startup NSWag
ライブラリを有効にした後で動作しなくなりました(たとえば、Angular app用にTypeScript HTTPプロキシを自動生成したい場合があります)。
解決策も非常に簡単でした - 私はNSWag
をSwashbuckle
に置き換えましたが、それ以上の問題はありませんでした。
さて、今ConfigHelper
コードを共有しています:
public class ConfigHelper
{
public static string GetIssuer()
{
string result = System.Configuration.ConfigurationManager.AppSettings["Issuer"];
return result;
}
public static string GetAudience()
{
string result = System.Configuration.ConfigurationManager.AppSettings["Audience"];
return result;
}
public static SigningCredentials GetSigningCredentials()
{
var result = new SigningCredentials(GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256);
return result;
}
public static string GetSecurityKey()
{
string result = System.Configuration.ConfigurationManager.AppSettings["SecurityKey"];
return result;
}
public static byte[] GetSymmetricSecurityKeyAsBytes()
{
var issuerSigningKey = GetSecurityKey();
byte[] data = Encoding.UTF8.GetBytes(issuerSigningKey);
return data;
}
public static SymmetricSecurityKey GetSymmetricSecurityKey()
{
byte[] data = GetSymmetricSecurityKeyAsBytes();
var result = new SymmetricSecurityKey(data);
return result;
}
public static string GetCorsOrigins()
{
string result = System.Configuration.ConfigurationManager.AppSettings["CorsOrigins"];
return result;
}
}
もう一つの重要な側面 - 私は Authorization headerを通してJWT Tokenを送ったので、TypeScriptコードは私を次のように探します。
(以下のコードは NSWag によって生成されます)
@Injectable()
export class TeamsServiceProxy {
private http: HttpClient;
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
constructor(@Inject(HttpClient) http: HttpClient, @Optional() @Inject(API_BASE_URL) baseUrl?: string) {
this.http = http;
this.baseUrl = baseUrl ? baseUrl : "https://localhost:44384";
}
add(input: TeamDto | null): Observable<boolean> {
let url_ = this.baseUrl + "/api/Teams/Add";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(input);
let options_ : any = {
body: content_,
observe: "response",
responseType: "blob",
headers: new HttpHeaders({
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": "Bearer " + localStorage.getItem('token')
})
};
ヘッダー部分を参照 - "Authorization": "Bearer " + localStorage.getItem('token')
私はあなたがJWTトークンをサポートするためにあなたが何らかのパーティーサーバを使用するべきであると思います、そしてWEB API 2には箱から出してすぐに使えるJWTサポートはありません。
しかし、何らかの形式の署名付きトークン(JWTではない)をサポートするためのOWINプロジェクトがあります。それはWebサイトのための認証の簡単な形式を提供するために縮小OAuthプロトコルとして機能します。
あなたはそれについてもっと読むことができます例えば ここに 。
それはかなり長いですが、ほとんどの部分はあなたがまったく必要としないかもしれないコントローラとASP.NETアイデンティティの詳細です。最も重要なのは
ステップ9:OAuthベアラトークン生成のサポートを追加する
ステップ12:バックエンドAPIのテスト
フロントエンドからアクセスできるエンドポイント(例: "/ token")の設定方法(およびリクエストのフォーマットの詳細)を読むことができます。
他の手順では、そのエンドポイントをデータベースなどに接続する方法について詳しく説明しています。必要な部分を選択できます。
これは、ASP.NET Core Web APIでJWTトークンを使用したクレームベース認証の非常に最小限で安全な実装です。
まず第一に、ユーザーに割り当てられたクレームを含むJWTトークンを返すエンドポイントを公開する必要があります。
/// <summary>
/// Login provides API to verify user and returns authentication token.
/// API Path: api/account/login
/// </summary>
/// <param name="paramUser">Username and Password</param>
/// <returns>{Token: [Token] }</returns>
[HttpPost("login")]
[AllowAnonymous]
public async Task<IActionResult> Login([FromBody] UserRequestVM paramUser, CancellationToken ct)
{
var result = await UserApplication.PasswordSignInAsync(paramUser.Email, paramUser.Password, false, lockoutOnFailure: false);
if (result.Succeeded)
{
UserRequestVM request = new UserRequestVM();
request.Email = paramUser.Email;
ApplicationUser UserDetails = await this.GetUserByEmail(request);
List<ApplicationClaim> UserClaims = await this.ClaimApplication.GetListByUser(UserDetails);
var Claims = new ClaimsIdentity(new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, paramUser.Email.ToString()),
new Claim(UserId, UserDetails.UserId.ToString())
});
//Adding UserClaims to JWT claims
foreach (var item in UserClaims)
{
Claims.AddClaim(new Claim(item.ClaimCode, string.Empty));
}
var tokenHandler = new JwtSecurityTokenHandler();
// this information will be retrived from you Configuration
//I have injected Configuration provider service into my controller
var encryptionkey = Configuration["Jwt:Encryptionkey"];
var key = Encoding.ASCII.GetBytes(encryptionkey);
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = Configuration["Jwt:Issuer"],
Subject = Claims,
// this information will be retrived from you Configuration
//I have injected Configuration provider service into my controller
Expires = DateTime.UtcNow.AddMinutes(Convert.ToDouble(Configuration["Jwt:ExpiryTimeInMinutes"])),
//algorithm to sign the token
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
return Ok(new
{
token = tokenString
});
}
return BadRequest("Wrong Username or password");
}
このようにデフォルトの認証サービスとしてJWT認証を追加するには、 startup.cs 内のConfigureServices
でサービスに認証を追加する必要があります。
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
//ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JWT:Encryptionkey"])),
ValidateAudience = false,
ValidateLifetime = true,
ValidIssuer = configuration["Jwt:Issuer"],
//ValidAudience = Configuration["Jwt:Audience"],
//IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Key"])),
};
});
これで、認可サービスにポリシーを追加できます。
services.AddAuthorization(options =>
{
options.AddPolicy("YourPolicyNameHere",
policy => policy.RequireClaim("YourClaimNameHere"));
});
_あるいは_ 、これはあなたのアプリケーションの起動時に一度だけ実行され、次のようなポリシーに追加されるのであなたはあなたのデータベースからあなたのすべての要求を投入することもできます。
services.AddAuthorization(async options =>
{
var ClaimList = await claimApplication.GetList(applicationClaim);
foreach (var item in ClaimList)
{
options.AddPolicy(item.ClaimCode, policy => policy.RequireClaim(item.ClaimCode));
}
});
これで、許可したいメソッドのいずれかにPolicyフィルターを設定できます。
[HttpPost("update")]
[Authorize(Policy = "ACC_UP")]
public async Task<IActionResult> Update([FromBody] UserRequestVM requestVm, CancellationToken ct)
{
//your logic goes here
}
お役に立てれば