当前位置: 首页 > 知识库问答 >
问题:

ASP.NET Web API的JWT身份验证

百里景山
2023-03-14

我试图在我的Web API应用程序中支持JWT承载令牌(JSON Web令牌),但我迷路了。

我看到了对.NET核心和OWIN应用程序的支持。
我当前正在IIS中托管我的应用程序。

我如何在我的应用程序中实现这个身份验证模块?是否有任何方法可以使用 配置,与使用Forms/Windows身份验证的方法类似?

共有2个答案

许振海
2023-03-14

我已经设法以最小的努力实现了它(就像使用ASP.NET Core一样简单)。

为此,我使用了OWINstartup.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核心,所以正如你们所看到的,它与我们现有的没有太大区别。

它真的让我很困惑首先,我正在尝试实现自定义提供程序等。但我没想到它会这么简单。owin真是乱七八糟!

只有一件事需要提及--在我启用OWIN启动后,nswag库对我停止工作(例如,有些人可能想为Angular app自动生成typescript HTTP代理)。

解决方案也非常简单--我用swashbuckle替换了nswag并且没有任何进一步的问题。

好,现在共享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代码如下所示查找我:

(以下代码由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')
            })
        };

参见标题部分-“授权”:“barer”+localstorage.getitem('token')

蔡理
2023-03-14

我在4年前回答了这个问题:如何使用HMAC保护ASP.NET Web API。

现在,在安全性方面发生了很多变化,特别是JWT越来越流行。在这个答案中,我将尝试解释如何以最简单、最基本的方式使用JWT,这样我们就不会迷失在OWIN、Oauth2、ASP.NET标识的丛林中...:)

如果您不了解JWT令牌,需要看一看:

https://tools.ietf.org/html/rfc7519

基本上,JWT令牌如下所示:

<base64-encoded header>.<base64-encoded claims>.<base64-encoded signature>

示例:

EYJHBGCIOIJIUZI1NiisInR5CCI6IKPXVCJ9.EYJ1BMLXDWVFBMFTZSI6ImN1B25NiiWiBMJMIJOXNDC3NTY1NZI0LCJLEHAIOJE0NZC1NJY5MJQSIMLHDCI6MTQ3NZU2NTCYNH0.6MZD1VWA5ACOCAJKFYKHYYBR3H13IZJDYHM9ZYSDFQ

JWT令牌有三个部分:

  1. 头:用base64编码的JSON格式
  2. 声明:用base64编码的JSON格式。
  3. 签名:基于base64编码的头和声明创建和签名。

如果您使用带有上述令牌的网站jwt.io,您可以对令牌进行解码,并看到如下所示:

从技术上讲,JWT使用一个签名,该签名是从标头签名的,并使用标头中指定的安全算法声明(例如:HMACSHA256)。因此,如果您在JWT的声明中存储任何敏感信息,则必须通过HTTPs传输JWT。

现在,为了使用JWT身份验证,如果您有一个遗留的Web Api系统,您实际上并不需要OWIN中间件。简单的概念是如何提供JWT令牌,以及在请求来临时如何验证令牌。就是这样。

在我创建的演示(github)中,为了使JWT令牌保持轻量级,我只存储usernameexpiration time。但是这样的话,您必须重新构建新的本地标识(主体)来添加更多的信息,比如角色,如果您想要进行角色授权等等。但是,如果您想要添加更多的信息到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令牌。

您可以使用来自Microsoft的名为system.identitymodel.tokens.jwt的NuGet包来生成令牌,如果愿意,甚至可以使用另一个包。在演示中,我将HMACSHA256Symmetrickey一起使用:

/// <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令牌的端点。

在演示中,我构建了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

 类似资料:
  • 我正在开发一个具有自己的身份验证和授权机制的REST应用程序。我想使用JSON Web Tokens进行身份验证。以下是有效且安全的实现吗? < li >将开发一个REST API来接受用户名和密码并进行认证。要使用的HTTP方法是POST,因此没有缓存。此外,在传输时还会有安全SSL < li >在认证时,将创建两个JWTs访问令牌和刷新令牌。刷新令牌将具有更长的有效期。这两个令牌都将写入coo

  • 在auth-routes示例中,api和nuxt一起启动并使用一个Node.js服务器实例。但是,有时我们应该使用jsonWebToken处理外部api身份验证问题。在这个例子中,将用最简单的方式解释。 官方 auth-module 如果要实现复杂的身份验证流程,例如OAuth2,我们建议使用官方 auth-module 结构 由于Nuxt.js同时提供服务器和客户端呈现,并且浏览器的cookie

  • 我正在使用SpringBoot开发具有微服务架构的Rest Backend。为了保护endpoint,我使用了JWT令牌机制。我正在使用Zuul API网关。 如果请求需要权限(来自JWT的角色),它将被转发到正确的微服务。Zuul api网关的“WebSecurityConfigrerAdapter”如下。 这样,我必须在这个类中编写每个请求授权部分。因此,我希望使用方法级安全性,即“Enabl

  • 我必须说,我对整个模型非常困惑,我需要帮助把所有的浮动件粘在一起。 我不是在做Spring REST,只是简单的WebMVC控制器。 什么让人困惑?(错误之处请指正) 第三方身份验证 要针对第三方进行身份验证,我需要通过扩展AuthenticationProvider来拥有自定义提供程序 null 问题: 何时调用AbstractAuthenticationProcessingFilter#Suc

  • 我在做一个全堆栈的web应用程序。我的前端由angular-cli组成,后端由node+Express构建。

  • jwt不应该仅仅用于认证用户吗?我读到过可以在里面存储非敏感的东西,比如用户ID。将权限级别之类的东西存储在令牌中可以吗?这样我可以避免数据库调用。