c# - JWT 和 Web API(JwtAuthForWebAPI?)——寻找示例

标签 c# asp.net-web-api oauth-2.0 access-token jwt

我有一个以 Angular 为前端的 Web API 项目,我想使用 JWT token 来保护它。我已经进行了用户/密码验证,所以我认为我只需要实现 JWT 部分。

我相信我已经选择了 JwtAuthForWebAPI,所以使用它的示例会很棒。

我假设任何未使用 [Authorize] 修饰的方法都将像往常一样运行,并且如果客户端传递的 token 不匹配,任何使用 [Authorize] 修饰的方法都将返回 401。

我还不知道如何在初始身份验证时将 token 发送回客户端。

我只是想用一个神奇的字符串开始,所以我有这段代码:

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 方面,则加分。

最佳答案

我最终不得不从几个不同的地方获取信息来创建适合我的解决方案(实际上,这是一个可行的生产解决方案的开始 - 但它确实有效!)

我摆脱了 JwtAuthForWebAPI(尽管我确实从中借用了一部分以允许没有授权 header 的请求流向不受 [Authorize] 保护的 WebAPI Controller 方法)。

我使用的是 Microsoft 的 JWT 库(JSON Web Token Handler for the Microsoft .NET Framework - 来自 NuGet)。

在我的身份验证方法中,在进行实际身份验证之后,我创建了 token 的字符串版本并将其与经过身份验证的名称(在本例中为传递给我的相同用户名)和一个角色一起传回,实际上,可能会在身份验证期间派生。

方法如下:

[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;
}

更新

关于在后续请求中处理 token 的问题。我所做的是创建一个 DelegatingHandler 来尝试读取/解码 token ,然后创建一个 Principal 并将其设置为 Thread.CurrentPrincipal 和 HttpContext.Current.User(您需要将其设置为两者)。最后,我用适当的访问限制装饰 Controller 方法。

这是 DelegatingHandler 的主要部分:

private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
{
    token = null;
    IEnumerable<string> 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<HttpResponseMessage> 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<HttpResponseMessage>.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<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(statusCode));
}

不要忘记将它添加到 MessageHandlers 管道中:

public static void Start()
{
    GlobalConfiguration.Configuration.MessageHandlers.Add(new JwtTokenValidationHandler());
}

最后,装饰你的 Controller 方法:

[Authorize(Roles = "OneRoleHere")]
[GET("/api/admin/settings/product/allorgs")]
[HttpGet]
public List<Org> GetAllOrganizations()
{
    return QueryableDependencies.GetMergedOrganizations().ToList();
}

[Authorize(Roles = "ADifferentRoleHere")]
[GET("/api/admin/settings/product/allorgswithapproval")]
[HttpGet]
public List<ApprovableOrg> GetAllOrganizationsWithApproval()
{
    return QueryableDependencies.GetMergedOrganizationsWithApproval().ToList();
}

关于c# - JWT 和 Web API(JwtAuthForWebAPI?)——寻找示例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22587992/

相关文章:

java - 如何验证 oAuth2 access_token 是否被在 Spring security 中颁发给它的同一个客户端使用?

c# - 使用定时器更新标签

c# - 我如何测试以查看网络管道服务是否正在监听

c# - 从 Windows 服务但不是控制台应用程序获取错误 "Cannot create Active X component"

c# - 是否存在内存泄漏?

entity-framework - ASP.NET Web API 中 DbContext 的推荐生命周期?

c# - 获取属性路由模板列表 asp.net webapi 2.2

asp.net-core - 如何在asp.net core web api的 Controller 中使用模型绑定(bind)排除属性

oauth-2.0 - 引用 token 是否需要刷新 token ?

python - 通过 Flask-Dance 使用 Google API 刷新 token