JWT和Web API(JwtAuthForWebAPI?)- 寻找一个例子
JWT和Web API(JwtAuthForWebAPI?)- 寻找一个例子
我有一个由Angular前端支持的Web API项目,我想使用JWT令牌对其进行安全保护。我已经实现了用户/密码验证,所以我认为我只需要实施JWT部分即可。
我相信我已经选择了JwtAuthForWebAPI,所以使用该选项的示例将非常好。
我假设未使用[Authorize]装饰的任何方法都将像以前一样运行,而使用[Authorize]装饰的任何方法都将在客户端传递的令牌不匹配时返回401错误。
我目前还无法弄清楚如何在初始身份验证时将令牌发送回客户端。
我想先使用一个魔法字符串,所以我有以下代码:
RegisterRoutes(GlobalConfiguration.Configuration.Routes); var builder = new SecurityTokenBuilder(); var jwtHandler = new JwtAuthenticationMessageHandler { AllowedAudience = "http://xxxx.com", Issuer = "corp", SigningToken = builder.CreateFromKey(Convert.ToBase64String(new byte[]{4,2,2,6})) }; GlobalConfiguration.Configuration.MessageHandlers.Add(jwtHandler);
但我不确定它如何最初返回给客户端。我认为我知道如何在客户端处理此问题,但如果您还能展示Angular端的交互,那就更好了。
JWT和Web API (JwtAuthForWebAPI?) - 寻找一个例子
问题的出现原因:
我不得不从几个不同的地方获取信息,以创建一个适合我的解决方案(实际上是一个可以投入生产的解决方案的开端 - 但它有效!)
我摒弃了JwtAuthForWebAPI(尽管我从中借用了一部分,以允许没有Authorization头的请求流经未由[Authorize]保护的WebAPI控制器方法)。
相反,我正在使用微软的JWT库(JSON Web Token Handler for the Microsoft .NET Framework - 来自NuGet)。
在我的身份验证方法中,在进行实际身份验证后,我创建了令牌的字符串版本,并将其与经过验证的名称(在本例中传递给我的相同用户名)和角色一起返回,实际上,这个角色可能在身份验证期间派生。
这是这个方法:
[HttpPost] public LoginResult PostSignIn([FromBody] Credentials credentials) { var auth = new LoginResult() { Authenticated = false }; if (TryLogon(credentials.UserName, credentials.Password)) { var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, credentials.UserName), new Claim(ClaimTypes.Role, "Admin") }), AppliesToAddress = ConfigurationManager.AppSettings["JwtAllowedAudience"], TokenIssuerName = ConfigurationManager.AppSettings["JwtValidIssuer"], SigningCredentials = new SigningCredentials(new InMemorySymmetricSecurityKey(JwtTokenValidationHandler.SymmetricKey), "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256", "http://www.w3.org/2001/04/xmlenc#sha256") }; var tokenHandler = new JwtSecurityTokenHandler(); var token = tokenHandler.CreateToken(tokenDescriptor); var tokenString = tokenHandler.WriteToken(token); auth.Token = tokenString; auth.Authenticated = true; } return auth; }
解决方法:
关于处理后续请求的令牌的问题。我创建了一个DelegatingHandler来尝试读取/解码令牌,然后创建一个Principal并将其设置为Thread.CurrentPrincipal和HttpContext.Current.User(您需要将其设置为两者)。最后,我使用适当的访问限制修饰控制器方法。
这是DelegatingHandler的主要部分:
private static bool TryRetrieveToken(HttpRequestMessage request, out string token) { token = null; IEnumerableauthzHeaders; if (!request.Headers.TryGetValues("Authorization", out authzHeaders) || authzHeaders.Count() > 1) { return false; } var bearerToken = authzHeaders.ElementAt(0); token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken; return true; } protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { HttpStatusCode statusCode; string token; var authHeader = request.Headers.Authorization; if (authHeader == null) { // missing authorization header return base.SendAsync(request, cancellationToken); } if (!TryRetrieveToken(request, out token)) { statusCode = HttpStatusCode.Unauthorized; return Task .Factory.StartNew(() => new HttpResponseMessage(statusCode)); } try { JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler(); TokenValidationParameters validationParameters = new TokenValidationParameters() { AllowedAudience = ConfigurationManager.AppSettings["JwtAllowedAudience"], ValidIssuer = ConfigurationManager.AppSettings["JwtValidIssuer"], SigningToken = new BinarySecretSecurityToken(SymmetricKey) }; IPrincipal principal = tokenHandler.ValidateToken(token, validationParameters); Thread.CurrentPrincipal = principal; HttpContext.Current.User = principal; return base.SendAsync(request, cancellationToken); } catch (SecurityTokenValidationException e) { statusCode = HttpStatusCode.Unauthorized; } catch (Exception) { statusCode = HttpStatusCode.InternalServerError; } return Task .Factory.StartNew(() => new HttpResponseMessage(statusCode)); }
不要忘记将其添加到MessageHandlers管道中:
public static void Start() { GlobalConfiguration.Configuration.MessageHandlers.Add(new JwtTokenValidationHandler()); }
最后,修饰您的控制器方法:
[Authorize(Roles = "OneRoleHere")] [GET("/api/admin/settings/product/allorgs")] [HttpGet] public ListGetAllOrganizations() { return QueryableDependencies.GetMergedOrganizations().ToList(); } [Authorize(Roles = "ADifferentRoleHere")] [GET("/api/admin/settings/product/allorgswithapproval")] [HttpGet] public List GetAllOrganizationsWithApproval() { return QueryableDependencies.GetMergedOrganizationsWithApproval().ToList(); }
如何处理传入令牌的解码?
谢谢,非常好的回答。我不知道为什么MS没有关于这个的官方文档...
我缺少什么参考?LoginResult和Credentials未定义。
这些是我自己的POCO。LoginResult用于通信尝试登录的结果(处理或未处理凭据,用户是否已验证)。Credentials是用户凭据 - 用户名/密码等。使用适合您环境的适当内容。
您可以使用Headers.Authorization.Scheme和Headers.Authorization.Parameter来简化TryRetrieveToken方法。
SymmetricKey是什么?它是另一个POCO吗?如果是这样,请展示一下它。
您需要对令牌进行签名,以便在提交时可以验证它。这可以通过多种方式完成,但对称密钥是最简单的。它只是一个在两个不同位置保存的密钥 - 用于签署令牌和验证令牌的位置。至于它是什么,它只是一个随机的Base64编码的byte[]。这是我刚生成的一个:LYdTiahQpxMl4IdFpn5WKKjah0qavBgk
很棒!只有一个问题 - 这个令牌是否支持滑动过期?
我对滑动过期不太熟悉,但我不认为这个令牌本身支持它。
谢谢!我通过调用刷新终结点来实现它,就像OAuth一样。