JWT和Web API(JwtAuthForWebAPI?)- 寻找一个例子

18 浏览
0 Comments

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端的交互,那就更好了。

0
0 Comments

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;
    IEnumerable authzHeaders;
    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 List GetAllOrganizations()
{
    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一样。

0