我试图在我的Web API应用程序中支持JWT承载令牌(JSON Web令牌),但我迷路了。
我看到了对.NET Core和OWIN应用程序的支持。
我目前在IIS中托管我的应用程序。
如何在我的应用程序中实现此身份验证模块?有什么方法可以使用<authentication>
与使用表单/ Windows身份验证类似的配置?
我试图在我的Web API应用程序中支持JWT承载令牌(JSON Web令牌),但我迷路了。
我看到了对.NET Core和OWIN应用程序的支持。
我目前在IIS中托管我的应用程序。
如何在我的应用程序中实现此身份验证模块?有什么方法可以使用<authentication>
与使用表单/ Windows身份验证类似的配置?
Answers:
我回答了这个问题: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.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1NzI0LCJleHAiOjEnNy1YzMyQ1JnHyZnZyZNyCyHJZhZNZyZYZYZMJQ1YJZJ1Z
JWT令牌包含三个部分:
如果您将网站jwt.io与上面的令牌一起使用,则可以对令牌进行解码,如下所示:
从技术上讲,JWT使用从标头签名的声明和具有标头中指定的安全算法的声明(例如:HMACSHA256)。因此,如果您在声明中存储任何敏感信息,则需要通过HTTP传输JWT。
现在,要使用JWT身份验证,如果您有旧版Web Api系统,则实际上不需要OWIN中间件。简单的概念是如何提供JWT令牌以及如何在请求到来时验证令牌。而已。
回到演示,让JWT令牌轻质,我只储存username
和expiration time
中智威汤逊。但是这样,您就必须重新构建新的本地身份(主要身份)以添加更多信息,例如:role ..如果要进行角色授权。但是,如果您想向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";
}
}
如果要验证WebAPI的所有传入请求(不特定于Controller或action),也可以使用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令牌已验证并且主体返回,则应该构建一个新的本地身份,并在其中添加更多信息以检查角色授权。
请记住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
只是石头!
只需提及一件事-在启用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;
}
}
另一个重要方面-我通过Authorization标头发送了JWT令牌,因此打字稿代码如下所示:
(以下代码由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')
I replaced NSWag with Swashbuckle and didn't have any further issues.
Swashbuckle是否具有生成打字稿文件的功能,或者是您自己添加的东西?
这是在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");
}
现在,您需要在startup.csConfigureServices
内部的服务中添加身份验证,以将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
}
希望这可以帮助
我认为您应该使用一些3d派对服务器来支持JWT令牌,并且WEB API 2中没有开箱即用的JWT支持。
但是,有一个OWIN项目用于支持某种格式的签名令牌(不是JWT)。它用作简化的OAuth协议,仅提供一种简单的网站身份验证形式。
它相当长,但是大多数部分都是您可能根本不需要的控制器和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;
}
}