首页 文章

Asp.Net Web Api的JWT身份验证

提问于
浏览
162

我正在尝试在我的web api应用程序中支持JWT bearer token(Json Web Token),我迷路了 .

我看到对 .net coreOWIN 应用程序的支持 .
我目前正在 IIS 上托管我的申请 .

如何在我的应用程序中实现此身份验证模块?有没有什么方法可以使用 <authentication> 配置类似于我使用窗体的\ windows身份验证的方式?

4 回答

  • 1

    我回答了这个问题:How to secure an ASP.NET Web API 4年前使用HMAC .

    现在,许多事情在安全方面发生了变化,尤其是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.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1NzI0LCJleHAiOjE0Nzc1NjY5MjQsImlhdCI6MTQ3NzU2NTcyNH0.6MzD1VwA5AcOcajkFyKhLYybr3h13iZjDyHm9zysDFQ

    JWT令牌有三个部分:

    • 标头:JSON格式,编码为base64

    • Claims:JSON格式,编码为base64 .

    • 签名:基于 Headers 和声明创建和签名,编码为base64 .

    如果您使用上面带有令牌的网站jwt.io,您可以解码并查看令牌,如下所示:

    enter image description here

    从技术上讲,JWT使用签名,签名是从头文件和声明中签名的,并使用标头中指定的安全算法(例如:HMACSHA256) . 因此,如果您在声明中存储任何敏感信息,则需要通过HTTP传输JWT .

    现在,为了使用JWT身份验证,如果您拥有传统的Web Api系统,则实际上并不需要OWIN中间件 . 简单的概念是如何提供JWT令牌以及如何在请求到来时验证令牌 . 而已 .

    回到演示,为了保持JWT标记轻量级,我只在JWT中存储 usernameexpiration time . 但是这样,您必须重新构建新的本地标识(主体)以添加更多信息,例如:roles ..如果您想要进行角色授权 . 但是,如果你想在JWT中添加更多信息,这取决于你,非常灵活 .

    您可以通过使用来自控制器的操作简单地提供JWT令牌 endpoints ,而不是使用OWIN中间件:

    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请求或基本身份验证 endpoints 来提供JWT令牌 .

    如何基于 username 生成令牌?

    您可以使用MS中名为 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令牌 endpoints 做,现在,如何验证JWT当请求到来时,其中来自 IAuthenticationFilter 继承,关于here认证过滤器更详细我已经 Build 了演示 JwtAuthenticationAttribute .

    使用此属性,您可以对任何操作进行身份验证,只需将此属性放在该操作上即可 .

    public class ValueController : ApiController
    {
        [JwtAuthentication]
        public string Get()
        {
            return "value";
        }
    }
    

    如果要验证WebApi的所有传入请求(不是特定于Controller或操作),您还可以使用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令牌并且返回了principal,那么您应该构建新的本地身份并将更多信息放入其中以检查角色授权 .

    请记住在全局范围内添加 config.Filters.Add(new AuthorizeAttribute()); (默认授权),以防止对您的资源发出任何匿名请求 .

    您可以使用Postman来测试演示:

    请求令牌(我上面提到的天真,仅用于演示):

    GET http://localhost:{port}/api/token?username=cuong&password=1
    

    将JWT令牌放在 Headers 中以获取授权请求,例如:

    GET http://localhost:{port}/api/value
    
    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1MjU4LCJleHAiOjE0Nzc1NjY0NTgsImlhdCI6MTQ3NzU2NTI1OH0.dSwwufd4-gztkLpttZsZ1255oEzpWCJkayR_4yvNL1s
    

    这个演示放在这里:https://github.com/cuongle/WebApi.Jwt

  • 1

    我还在我的项目中实现了Jason Web Token API,你可以从这个链接下载JWT API Token . 您可以使用 [authorize] 来检查用户是否已通过身份验证?

  • 419

    我认为您应该使用一些3d派对服务器来支持JWT令牌,并且WEB API 2中没有开箱即用的JWT支持 .

    但是,有一个OWIN项目用于支持某种格式的签名令牌(而不是JWT) . 它可以作为减少的OAuth协议,只为网站提供一种简单的身份验证形式 .

    您可以阅读更多相关信息,例如here .

    它相当长,但大多数部分都是控制器和ASP.NET身份的细节,您根本不需要它们 . 最重要的是

    步骤9:添加对OAuth承载令牌生成的支持步骤12:测试后端API

    在那里,您可以阅读如何设置可以从前端访问的 endpoints (例如“/ token”)(以及请求格式的详细信息) .

    其他步骤提供了有关如何将该 endpoints 连接到数据库等的详细信息,您可以选择所需的部件 .

  • 4

    我设法用最少的努力实现它(就像使用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代理) .

    解决方案也非常简单 - 我用 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;
        }
    }
    

    另一个重要方面 - 我通过 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')
                })
            };
    

    见 Headers 部分 - "Authorization": "Bearer " + localStorage.getItem('token')

相关问题