在ASP.Net Core中实现一个Token Base身份认证,使用场景主要就是Web API下,可以调用Web API的不止是浏览器,还有各种各样的客户端,有些客户端没有Cookies,也无法使用Session。这时候就需要Token来救场了,相比Cookies,Token更开放,而安全性也要比Cookies高很多。

下面使用微软JwtSecurityTokenHandler来实现一个基于beare token的身份认证。

1.创建辅助类:

在项目中,新建一个Auth文件夹,在Auth文件夹中添加一个RSAKeyHelper类:

using System.Security.Cryptography;
namespace Biz126.WebAPI.Auth
{
    public class RSAKeyHelper
    {
        public static RSAParameters GenerateKey()
        {
            using (var key = new RSACryptoServiceProvider(2048))
            {
                return key.ExportParameters(true);
            }
        }
    }
}

和TokenAuthOption类:

using Microsoft.IdentityModel.Tokens;
namespace Biz126.WebAPI.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);
    }
}

2.修改Startup.cs文件:

打开Startup.cs文件,在ConfigureServices中添加:

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

完整的ConfigureServices方法:

        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddApplicationInsightsTelemetry(Configuration);
            services.Configure<Biz126.Models.ConnectionStrings>(Configuration.GetSection("ConnectionStrings"));    //这个是取数据库配置,与本例无关
            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 }
                            new { Status = false, Message = "Token已失效", Code = -401, Result = new { 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 }
                            new { Status = false, Code = -500, Message = error.Error.Message }
                        ));
                    }
                    //when no error, do next.
                    else await next();
                });
            });
            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 { Status = false, Message = "Token已失效", Code = -401, Result = new { 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 { Status = false, Code = -500, Message = 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)
                }
            });

完整的Configure方法:

        // 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();
            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 }
                            new { Status = false, Message = "Token已失效", Code = -401, Result = new { 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 }
                            new { Status = false, Code = -500, Message = error.Error.Message }
                        ));
                    }
                    //when no error, do next.
                    else await next();
                });
            });
            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 { Status = false, Message = "Token已失效", Code = -401, Result = new { 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 { Status = false, Code = -500, Message = error.Error.Message }
                        ));
                    }
                    //when no error, do next.
                    else await next();
                });
            });
            app.UseJwtBearerAuthentication(new JwtBearerOptions
            {
                TokenValidationParameters = new TokenValidationParameters
                {
                    IssuerSigningKey = TokenAuthOption.Key,
                    ValidAudience = TokenAuthOption.Audience,
                    ValidIssuer = TokenAuthOption.Issuer,
                    ValidateIssuerSigningKey = true,
                    ValidateLifetime = true,
                    ClockSkew = TimeSpan.FromMinutes(0)
                }
            });
            app.UseMvc();
        }

3.登录授权接口

在Controllers中,增加TokenAuthController的Web API控制器,在该控制器下添加如下方法:

        /// <summary>
        /// 生成Token
        /// </summary>
        /// <param name="user"></param>
        /// <param name="serviceid"></param>
        /// <param name="device"></param>
        /// <param name="expires"></param>
        /// <returns></returns>
        private string GenerateToken(Biz126.Customer.Models.Detail user,string serviceid,string device, DateTime expires)
        {
            var handler = new JwtSecurityTokenHandler();
            ClaimsIdentity identity = new ClaimsIdentity(
                new GenericIdentity(user.UserName, "TokenAuth"),
                new[] {
                    new Claim("ID", user._Id.ToString()),
                    new Claim("TrueName",user.TrueName),
                    new Claim("ServiceId",serviceid),
                    new Claim("Device",device)  //绑定硬件号
                }
            );
            var securityToken = handler.CreateToken(new SecurityTokenDescriptor
            {
                Issuer = TokenAuthOption.Issuer,
                Audience = TokenAuthOption.Audience,
                SigningCredentials = TokenAuthOption.SigningCredentials,
                Subject = identity,
                Expires = expires
            });
            return handler.WriteToken(securityToken);
        }

正如注释中所说的,该方法用于生成Token。

还是在该控制器下,继续添加GetAuthorize方法,用于取得授权,代码如下:

        [HttpPost]
        public Biz126.Models.ResultModel<object> GetAuthorize(Biz126.Authorization.Models.UserAuth user)
        {
            var result = new Biz126.Models.ResultModel<object>();
            var log = new Biz126.Customer.Models.Login() { UserName = user.UserName, Password = user.Password };
            var login = _users.Login(log);    //登录

            if (login.Status)
            {
                //用户账号状态正常
                //1.查看该用户有几个有效的该授权
                //2.已经开通了几个
                //3.在剩余范围内
                //4.查看该机器是否已经有过授权
                //5.如果已经有过授权,更新Token,如果没有过授权,增加新授权
                var requestAt = DateTime.Now;
                var expiresIn = requestAt + TokenAuthOption.ExpiresSpan;    //过期时间(正常为授权结束日期)
                var token = GenerateToken(login.Result,user.ServiceId,user.Device, expiresIn);
                result.Result = new { requestAt = requestAt, expiresIn = TokenAuthOption.ExpiresSpan.TotalSeconds, accessToken = token };
                result.Status = true;
                result.Message = "success";
                result.Code = 0;
            }
            else
            {
                result.Status = false;
                result.Message = "用户名或密码错误";
                result.Code = -403;
            }
            return result;
        }

4.验证授权:

在Controllers中新建一个CheckAuthController的Web API控制器,添加下面的方法:

        [Authorize("Bearer")]
        public string Post()
        {
            var claimsIdentity = User.Identity as ClaimsIdentity;
            var id = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "ID").Value;
            var device= claimsIdentity.Claims.FirstOrDefault(c => c.Type == "Device").Value;
            return $"Hello! {HttpContext.User.Identity.Name}, your Device is:{device}";
        }

5.测试:

打开Postman,先访问/api/TokenAuth/GetAuthorize,使用用户名和密码进行登录,接口会返回生成的Token,记下返回的Token。继续使用Postman工具,访问接口/api/CheckAuth,在Headers中,添加:Authorization:Bearer 上一步生成的Token,如下图所示

要注意“Bearer”与后面的Token之间有一个空格,之后提交请求,可以看到验证通过并给返回相应的信息。

上面代码只是一个例子,具体可以灵活的运用到自己的项目中。

以上。