NET Core中实现一个Token base的身份认证

NET Core中实现一个Token base的身份认证

注:本文提到的代码示例下载地址> How to achieve a bearer token authentication and authorization in ASP.NET Core

在ASP.NET Core中实现一个Token base的身份认证

以前在web端的身份认证都是基于Cookie | Session的身份认证, 在没有更多的终端出现之前,这样做也没有什么问题,
但在Web API时代,你所需要面对的就不止是浏览器了,还有各种客户端,这样就有了一个问题,这些客户端是不知道cookie是什么鬼的。 (cookie其实是浏览器搞出来的小猫腻,用来保持会话的,但HTTP本身是无状态的, 各种客户端能提供的无非也就是HTTP操作的API)

而基于Token的身份认证就是应对这种变化而生的,它更开放,安全性也更高。

基于Token的身份认证有很多种实现方式,但我们这里只使用微软提供的API。

接下来的例子将带领大家完成一个使用微软JwtSecurityTokenHandler完成一个基于beare token的身份认证。

注意:这种文章属于Step by step教程,跟着做才不至于看晕,下载完整代码分析代码结构才有意义。

前期准备

创建项目

在VS中新建项目,项目类型选择ASP.NET Core Web Application(.NET Core), 输入项目名称为CSTokenBaseAuth

Coding

  • 创建一些辅助类

    在项目根目录下创建一个文件夹Auth,并添加RSAKeyHelper.cs以及TokenAuthOption.cs两个文件

    • 在RSAKeyHelper.cs中

      复制代码
      using System.Security.Cryptography;
      
      namespace CSTokenBaseAuth.Auth
      {
          public class RSAKeyHelper
          {
              public static RSAParameters GenerateKey()
              {
                  using (var key = new RSACryptoServiceProvider(2048))
                  {
                      return key.ExportParameters(true);
                  }
              }
          }
      }
      复制代码
    • 在TokenAuthOption.cs中

      复制代码
      using System;
      using Microsoft.IdentityModel.Tokens;
      
      namespace CSTokenBaseAuth.Auth
      {
          public class TokenAuthOption
          {
              public static string Audience { get; } = "ExampleAudience";
              public static string Issuer { get; } = "ExampleIssuer";
              public static RsaSecurityKey Key { get; } = new RsaSecurityKey(RSAKeyHelper.GenerateKey());
              public static SigningCredentials SigningCredentials { get; } = new SigningCredentials(Key, SecurityAlgorithms.RsaSha256Signature);
      
              public static TimeSpan ExpiresSpan { get; } = TimeSpan.FromMinutes(20);
          }
      }
      复制代码
  • Startup.cs

    在ConfigureServices中添加如下代码:

    复制代码
    services.AddAuthorization(auth =>
    {
        auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
            .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​)
            .RequireAuthenticatedUser().Build());
    });
    复制代码

    完整的代码应该是这样

    复制代码
    public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services.AddApplicationInsightsTelemetry(Configuration);
        // Enable the use of an [Authorize("Bearer")] attribute on methods and classes to protect.
        services.AddAuthorization(auth =>
        {
            auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
                .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​)
                .RequireAuthenticatedUser().Build());
        });
        services.AddMvc();
    }
    复制代码

    在Configure方法中添加如下代码

    复制代码
    app.UseExceptionHandler(appBuilder => {
        appBuilder.Use(async (context, next) => {
            var error = context.Features[typeof(IExceptionHandlerFeature)] as IExceptionHandlerFeature;
            //when authorization has failed, should retrun a json message to client
            if (error != null && error.Error is SecurityTokenExpiredException)
            {
                context.Response.StatusCode = 401;
                context.Response.ContentType = "application/json";
                await context.Response.WriteAsync(JsonConvert.SerializeObject(
                    new { authenticated = false, tokenExpired = true }
                ));
            }
            //when orther error, retrun a error message json to client
            else if (error != null && error.Error != null)
            {
                context.Response.StatusCode = 500;
                context.Response.ContentType = "application/json";
                await context.Response.WriteAsync(JsonConvert.SerializeObject(
                    new { success = false, error = error.Error.Message }
                ));
            }
            //when no error, do next.
            else await next();
        });
    });
    复制代码

    这段代码主要是Handle Error用的,比如当身份认证失败的时候会抛出异常,而这里就是处理这个异常的。

    接下来在相同的方法中添加如下代码,

    复制代码
    app.UseExceptionHandler(appBuilder => {
        appBuilder.Use(async (context, next) => {
            var error = context.Features[typeof(IExceptionHandlerFeature)] as IExceptionHandlerFeature;
    
            //when authorization has failed, should retrun a json message to client
            if (error != null && error.Error is SecurityTokenExpiredException)
            {
                context.Response.StatusCode = 401;
                context.Response.ContentType = "application/json";
    
                await context.Response.WriteAsync(JsonConvert.SerializeObject(
                    new { authenticated = false, tokenExpired = true }
                ));
            }
            //when orther error, retrun a error message json to client
            else if (error != null && error.Error != null)
            {
                context.Response.StatusCode = 500;
                context.Response.ContentType = "application/json";
                await context.Response.WriteAsync(JsonConvert.SerializeObject(
                    new { success = false, error = error.Error.Message }
                ));
            }
            //when no error, do next.
            else await next();
        });
    });
    复制代码

    应用JwtBearerAuthentication

    复制代码
    app.UseJwtBearerAuthentication(new JwtBearerOptions {
        TokenValidationParameters = new TokenValidationParameters {
            IssuerSigningKey = TokenAuthOption.Key,
            ValidAudience = TokenAuthOption.Audience,
            ValidIssuer = TokenAuthOption.Issuer,
            ValidateIssuerSigningKey = true,
            ValidateLifetime = true,
            ClockSkew = TimeSpan.FromMinutes(0)
        }
    });
    复制代码

    完整的代码应该是这样

    复制代码
    using System;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Logging;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using CSTokenBaseAuth.Auth;
    using Microsoft.AspNetCore.Diagnostics;
    using Microsoft.IdentityModel.Tokens;
    using Microsoft.AspNetCore.Http;
    using Newtonsoft.Json;
    
    namespace CSTokenBaseAuth
    {
        public class Startup
        {
            public Startup(IHostingEnvironment env)
            {
                var builder = new ConfigurationBuilder()
                    .SetBasePath(env.ContentRootPath)
                    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                    .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
    
                if (env.IsEnvironment("Development"))
                {
                    // This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
                    builder.AddApplicationInsightsSettings(developerMode: true);
                }
    
                builder.AddEnvironmentVariables();
                Configuration = builder.Build();
            }
    
            public IConfigurationRoot Configuration { get; }
    
            // This method gets called by the runtime. Use this method to add services to the container
            public void ConfigureServices(IServiceCollection services)
            {
                // Add framework services.
                services.AddApplicationInsightsTelemetry(Configuration);
    
                // Enable the use of an [Authorize("Bearer")] attribute on methods and classes to protect.
                services.AddAuthorization(auth =>
                {
                    auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
                        .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​)
                        .RequireAuthenticatedUser().Build());
                });
    
                services.AddMvc();
            }
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline
            public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
            {
                loggerFactory.AddConsole(Configuration.GetSection("Logging"));
                loggerFactory.AddDebug();
    
                app.UseApplicationInsightsRequestTelemetry();
    
                app.UseApplicationInsightsExceptionTelemetry();
    
                #region Handle Exception
                app.UseExceptionHandler(appBuilder => {
                    appBuilder.Use(async (context, next) => {
                        var error = context.Features[typeof(IExceptionHandlerFeature)] as IExceptionHandlerFeature;
    
                        //when authorization has failed, should retrun a json message to client
                        if (error != null && error.Error is SecurityTokenExpiredException)
                        {
                            context.Response.StatusCode = 401;
                            context.Response.ContentType = "application/json";
    
                            await context.Response.WriteAsync(JsonConvert.SerializeObject(
                                new { authenticated = false, tokenExpired = true }
                            ));
                        }
                        //when orther error, retrun a error message json to client
                        else if (error != null && error.Error != null)
                        {
                            context.Response.StatusCode = 500;
                            context.Response.ContentType = "application/json";
                            await context.Response.WriteAsync(JsonConvert.SerializeObject(
                                new { success = false, error = error.Error.Message }
                            ));
                        }
                        //when no error, do next.
                        else await next();
                    });
                });
                #endregion
    
                #region UseJwtBearerAuthentication
                app.UseJwtBearerAuthentication(new JwtBearerOptions {
                    TokenValidationParameters = new TokenValidationParameters {
                        IssuerSigningKey = TokenAuthOption.Key,
                        ValidAudience = TokenAuthOption.Audience,
                        ValidIssuer = TokenAuthOption.Issuer,
                        ValidateIssuerSigningKey = true,
                        ValidateLifetime = true,
                        ClockSkew = TimeSpan.FromMinutes(0)
                    }
                });
                #endregion
    
                app.UseMvc(routes =>
                {
                    routes.MapRoute(
                        name: "default",
                        template: "{controller=Login}/{action=Index}");
                });
            }
        }
    }
    复制代码
  • 在Controllers中新建一个Web API Controller Class,命名为TokenAuthController.cs。我们将在这里完成登录授权

    在同文件下添加两个类,分别用来模拟用户模型,以及用户存储,代码应该是这样

    复制代码
    public class User
    {
        public Guid ID { get; set; }
        public string Username { get; set; }
        public string Password { get; set; }
    }
    
    public static class UserStorage
    {
        public static List<User> Users { get; set; } = new List<User> {
            new User {ID=Guid.NewGuid(),Username="user1",Password = "user1psd" },
            new User {ID=Guid.NewGuid(),Username="user2",Password = "user2psd" },
            new User {ID=Guid.NewGuid(),Username="user3",Password = "user3psd" }
        };
    }
    复制代码

    接下来在TokenAuthController.cs中添加如下方法

    复制代码
    private string GenerateToken(User user, DateTime expires)
    {
        var handler = new JwtSecurityTokenHandler();
        
        ClaimsIdentity identity = new ClaimsIdentity(
            new GenericIdentity(user.Username, "TokenAuth"),
            new[] {
                new Claim("ID", user.ID.ToString())
            }
        );
    
        var securityToken = handler.CreateToken(new SecurityTokenDescriptor
        {
            Issuer = TokenAuthOption.Issuer,
            Audience = TokenAuthOption.Audience,
            SigningCredentials = TokenAuthOption.SigningCredentials,
            Subject = identity,
            Expires = expires
        });
        return handler.WriteToken(securityToken);
    }
    复制代码

    该方法仅仅只是生成一个Auth Token,接下来我们来添加另外一个方法来调用它

    在相同文件中添加如下代码

    复制代码
    [HttpPost]
    public string GetAuthToken(User user)
    {
        var existUser = UserStorage.Users.FirstOrDefault(u => u.Username == user.Username && u.Password == user.Password);
    
        if (existUser != null)
        {
            var requestAt = DateTime.Now;
            var expiresIn = requestAt + TokenAuthOption.ExpiresSpan;
            var token = GenerateToken(existUser, expiresIn);
    
            return JsonConvert.SerializeObject(new {
                stateCode = 1,
                requertAt = requestAt,
                expiresIn = TokenAuthOption.ExpiresSpan.TotalSeconds,
                accessToken = token
            });
        }
        else
        {
            return JsonConvert.SerializeObject(new { stateCode = -1, errors = "Username or password is invalid" });
        }
    }
    复制代码

    该文件完整的代码应该是这样

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;
    using Newtonsoft.Json;
    using System.IdentityModel.Tokens.Jwt;
    using System.Security.Claims;
    using System.Security.Principal;
    using Microsoft.IdentityModel.Tokens;
    using CSTokenBaseAuth.Auth;
    
    namespace CSTokenBaseAuth.Controllers
    {
        [Route("api/[controller]")]
        public class TokenAuthController : Controller
        {
            [HttpPost]
            public string GetAuthToken(User user)
            {
                var existUser = UserStorage.Users.FirstOrDefault(u => u.Username == user.Username && u.Password == user.Password);
    
                if (existUser != null)
                {
                    var requestAt = DateTime.Now;
                    var expiresIn = requestAt + TokenAuthOption.ExpiresSpan;
                    var token = GenerateToken(existUser, expiresIn);
    
                    return JsonConvert.SerializeObject(new {
                        stateCode = 1,
                        requertAt = requestAt,
                        expiresIn = TokenAuthOption.ExpiresSpan.TotalSeconds,
                        accessToken = token
                    });
                }
                else
                {
                    return JsonConvert.SerializeObject(new { stateCode = -1, errors = "Username or password is invalid" });
                }
            }
    
            private string GenerateToken(User user, DateTime expires)
            {
                var handler = new JwtSecurityTokenHandler();
                
                ClaimsIdentity identity = new ClaimsIdentity(
                    new GenericIdentity(user.Username, "TokenAuth"),
                    new[] {
                        new Claim("ID", user.ID.ToString())
                    }
                );
    
                var securityToken = handler.CreateToken(new SecurityTokenDescriptor
                {
                    Issuer = TokenAuthOption.Issuer,
                    Audience = TokenAuthOption.Audience,
                    SigningCredentials = TokenAuthOption.SigningCredentials,
                    Subject = identity,
                    Expires = expires
                });
                return handler.WriteToken(securityToken);
            }
        }
    
        public class User
        {
            public Guid ID { get; set; }
    
            public string Username { get; set; }
    
            public string Password { get; set; }
        }
    
        public static class UserStorage
        {
            public static List<User> Users { get; set; } = new List<User> {
                new User {ID=Guid.NewGuid(),Username="user1",Password = "user1psd" },
                new User {ID=Guid.NewGuid(),Username="user2",Password = "user2psd" },
                new User {ID=Guid.NewGuid(),Username="user3",Password = "user3psd" }
            };
        }
    }
    复制代码
  • 接下来我们来完成授权验证部分

    在Controllers中新建一个Web API Controller Class,命名为ValuesController.cs

    在其中添加如下代码

    复制代码
    public string Get()
    {
        var claimsIdentity = User.Identity as ClaimsIdentity;
    
        var id = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "ID").Value;
    
        return $"Hello! {HttpContext.User.Identity.Name}, your ID is:{id}";
    }
    复制代码

    为方法添加装饰属性

    [HttpGet]
    [Authorize("Bearer")]

    完整的文件代码应该是这样

    复制代码
    using System.Linq;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Authorization;
    using System.Security.Claims;
    
    namespace CSTokenBaseAuth.Controllers
    {
        [Route("api/[controller]")]
        public class ValuesController : Controller
        {
            [HttpGet]
            [Authorize("Bearer")]
            public string Get()
            {
                var claimsIdentity = User.Identity as ClaimsIdentity;
    
                var id = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "ID").Value;
    
                return $"Hello! {HttpContext.User.Identity.Name}, your ID is:{id}";
            }
        }
    }
    复制代码
  • 最后让我们来添加视图

    在Controllers中新建一个Web Controller Class,命名为LoginController.cs

    其中的代码应该是这样

    复制代码
    using Microsoft.AspNetCore.Mvc;
    
    namespace CSTokenBaseAuth.Controllers
    {
        [Route("[controller]/[action]")]
        public class LoginController : Controller
        {
            public IActionResult Index()
            {
                return View();
            }
        }
    }
    复制代码

    在项目Views目录下新建一个名为Login的目录,并在其中新建一个Index.cshtml文件。

    代码应该是这个样子

    复制代码
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title></title>
    </head>
    <body>
        <button id="getToken">getToken</button>
        <button id="requestAPI">requestAPI</button>
    
        <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
        <script>
            $(function () {
                var accessToken = undefined;
    
                $("#getToken").click(function () {
                    $.post(
                        "/api/TokenAuth",
                        { Username: "user1", Password: "user1psd" },
                        function (data) {
                            console.log(data);
                            if (data.stateCode == 1)
                            {
                                accessToken = data.accessToken;
    
                                $.ajaxSetup({
                                    headers: { "Authorization": "Bearer " + accessToken }
                                });
                            }
                        },
                        "json"
                    );
                })
    
                $("#requestAPI").click(function () {
                    $.get("/api/Values", {}, function (data) {
                        alert(data);
                    }, "text");
                })
            })
        </script>
    </body>
    </html>
    复制代码

最后:完整的代码Sample以及运行手册,请访问:How to achieve a bearer token authentication and authorization in ASP.NET Core

更多脚本样例, 访问微软One Code样例库:http://aka.ms/onescriptsamples 更多代码样例, 访问微软One Script样例库:http://aka.ms/onecodesamples
原文地址:https://www.cnblogs.com/Leo_wl/p/6077203.html