Web APIアプリケーションでJWTベアラートークン(JSON Webトークン)をサポートしようとしていますが、道に迷ってしまいます。
.NET CoreとOWINアプリケーションがサポートされています。
現在、IISでアプリケーションをホストしています。
アプリケーションでこの認証モジュールをどのように実現できますか?<authentication>
フォーム/ Windows認証を使用する方法と同様の構成を使用する方法はありますか?
Web APIアプリケーションでJWTベアラートークン(JSON Webトークン)をサポートしようとしていますが、道に迷ってしまいます。
.NET CoreとOWINアプリケーションがサポートされています。
現在、IISでアプリケーションをホストしています。
アプリケーションでこの認証モジュールをどのように実現できますか?<authentication>
フォーム/ Windows認証を使用する方法と同様の構成を使用する方法はありますか?
回答:
私はこの質問に答えました:HMACを使用して4年前に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.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1NzI0LCWYQYQYQMQYQYQMQYQYQMQYQYQMQYQYQMQQYQYQYQMQQYQMQQYQM
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;
}
}
これは単純なアクションです。本番環境では、POSTリクエストまたは基本認証エンドポイントを使用してJWTトークンを提供する必要があります。
に基づいてトークンを生成する方法はusername
?
System.IdentityModel.Tokens.Jwt
Microsoftから呼び出されたNuGetパッケージを使用してトークンを生成したり、必要に応じて別のパッケージを使用したりすることもできます。デモでは、私はHMACSHA256
と一緒に使用しSymmetricKey
ます:
/// <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
(認証フィルターの詳細はこちら)。
この属性を使用すると、任意のアクションを認証できます。そのアクションにこの属性を配置するだけです。
public class ValueController : ApiController
{
[JwtAuthentication]
public string Get()
{
return "value";
}
}
WebController(コントローラーまたはアクションに固有ではない)のすべての着信要求を検証する場合は、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
hmac = new HMACSHA256();var key = Convert.ToBase64String(hmac.Key);
最小限の労力で(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を有効にした後、スタートアップNSWag
ライブラリが機能しなくなりました(たとえば、Angularアプリの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;
}
}
もう一つの重要な側面-私は経由JWTトークンを送信し、承認、次のように、ヘッダ私にとってとてもtypescriptですコードのルックスを:
@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')
I replaced NSWag with Swashbuckle and didn't have any further issues.
Swashbuckleにはtypescriptファイルを生成する機能がありますか、それとも自分で追加したものですか?
これは、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");
}
次のようConfigureServices
に、startup.cs内のサービスに認証を追加して、JWT認証をデフォルトの認証サービスとして追加する必要があります。
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));
}
});
これで、次のように承認したいメソッドのいずれかにポリシーフィルターを設定できます。
[HttpPost("update")]
[Authorize(Policy = "ACC_UP")]
public async Task<IActionResult> Update([FromBody] UserRequestVM requestVm, CancellationToken ct)
{
//your logic goes here
}
お役に立てれば
JWTトークンをサポートするには、いくつかの3Dパーティーサーバーを使用する必要があると思います。WEBAPI 2では、すぐに使えるJWTサポートはありません。
ただし、署名付きトークン(JWTではない)のいくつかの形式をサポートするOWINプロジェクトがあります。縮小されたOAuthプロトコルとして機能し、Webサイトに単純な認証形式を提供します。
かなり長いですが、ほとんどの部分は、コントローラーとASP.NET Identityの詳細であり、まったく必要ない場合があります。最も重要なのは
ステップ9:OAuthベアラートークン生成のサポートを追加する
ステップ12:バックエンドAPIのテスト
そこで、フロントエンドからアクセスできるエンドポイント(「/ token」など)のセットアップ方法(およびリクエストのフォーマットの詳細)を読むことができます。
他の手順では、そのエンドポイントをデータベースなどに接続する方法の詳細が提供され、必要な部分を選択できます。
私の場合、JWTは別のAPIによって作成されるため、ASP.NETはそれをデコードして検証するだけで済みます。受け入れられた回答とは対照的に、非対称アルゴリズムであるRSAを使用しているため、SymmetricSecurityKey
上記のクラスは機能しません。
結果は次のとおりです。
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Threading;
using System.Threading.Tasks;
public static async Task<JwtSecurityToken> VerifyAndDecodeJwt(string accessToken)
{
try
{
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{securityApiOrigin}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
var openIdConfig = await configurationManager.GetConfigurationAsync(CancellationToken.None);
var validationParameters = new TokenValidationParameters()
{
ValidateLifetime = true,
ValidateAudience = false,
ValidateIssuer = false,
RequireSignedTokens = true,
IssuerSigningKeys = openIdConfig.SigningKeys,
};
new JwtSecurityTokenHandler().ValidateToken(accessToken, validationParameters, out var validToken);
// threw on invalid, so...
return validToken as JwtSecurityToken;
}
catch (Exception ex)
{
logger.Info(ex.Message);
return null;
}
}