在ASP.NET Core中实现一个Token base的身份认证实例
以前在web端的身份认证都是基于Cookie|Session的身份认证,在没有更多的终端出现之前,这样做也没有什么问题,但在WebAPI时代,你所需要面对的就不止是浏览器了,还有各种客户端,这样就有了一个问题,这些客户端是不知道cookie是什么鬼的。(cookie其实是浏览器搞出来的小猫腻,用来保持会话的,但HTTP本身是无状态的,各种客户端能提供的无非也就是HTTP操作的API)
而基于Token的身份认证就是应对这种变化而生的,它更开放,安全性也更高。
基于Token的身份认证有很多种实现方式,但我们这里只使用微软提供的API。
接下来的例子将带领大家完成一个使用微软JwtSecurityTokenHandler完成一个基于bearetoken的身份认证。
注意:这种文章属于Stepbystep教程,跟着做才不至于看晕,下载完整代码分析代码结构才有意义。
前期准备
推荐使用VS2015Update3作为你的IDE,下载地址:https://www.nhooo.com/softjc/446184.html
你需要安装.NETCore的运行环境以及开发工具,这里提供VS版:https://www.nhooo.com/softs/472362.html
创建项目
在VS中新建项目,项目类型选择ASP.NETCoreWebApplication(.NETCore),输入项目名称为CSTokenBaseAuth
Coding
创建一些辅助类
在项目根目录下创建一个文件夹Auth,并添加RSAKeyHelper.cs以及TokenAuthOption.cs两个文件
在RSAKeyHelper.cs中
usingSystem.Security.Cryptography; namespaceCSTokenBaseAuth.Auth { publicclassRSAKeyHelper { publicstaticRSAParametersGenerateKey() { using(varkey=newRSACryptoServiceProvider(2048)) { returnkey.ExportParameters(true); } } } }
在TokenAuthOption.cs中
usingSystem; usingMicrosoft.IdentityModel.Tokens; namespaceCSTokenBaseAuth.Auth { publicclassTokenAuthOption { publicstaticstringAudience{get;}="ExampleAudience"; publicstaticstringIssuer{get;}="ExampleIssuer"; publicstaticRsaSecurityKeyKey{get;}=newRsaSecurityKey(RSAKeyHelper.GenerateKey()); publicstaticSigningCredentialsSigningCredentials{get;}=newSigningCredentials(Key,SecurityAlgorithms.RsaSha256Signature); publicstaticTimeSpanExpiresSpan{get;}=TimeSpan.FromMinutes(20); } }
Startup.cs
在ConfigureServices中添加如下代码:
services.AddAuthorization(auth=> { auth.AddPolicy("Bearer",newAuthorizationPolicyBuilder() .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser().Build()); });
完整的代码应该是这样
publicvoidConfigureServices(IServiceCollectionservices) { //Addframeworkservices. services.AddApplicationInsightsTelemetry(Configuration); //Enabletheuseofan[Authorize("Bearer")]attributeonmethodsandclassestoprotect. services.AddAuthorization(auth=> { auth.AddPolicy("Bearer",newAuthorizationPolicyBuilder() .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser().Build()); }); services.AddMvc(); }
在Configure方法中添加如下代码
app.UseExceptionHandler(appBuilder=>{ appBuilder.Use(async(context,next)=>{ varerror=context.Features[typeof(IExceptionHandlerFeature)]asIExceptionHandlerFeature; //whenauthorizationhasfailed,shouldretrunajsonmessagetoclient if(error!=null&&error.ErrorisSecurityTokenExpiredException) { context.Response.StatusCode=401; context.Response.ContentType="application/json"; awaitcontext.Response.WriteAsync(JsonConvert.SerializeObject( new{authenticated=false,tokenExpired=true} )); } //whenorthererror,retrunaerrormessagejsontoclient elseif(error!=null&&error.Error!=null) { context.Response.StatusCode=500; context.Response.ContentType="application/json"; awaitcontext.Response.WriteAsync(JsonConvert.SerializeObject( new{success=false,error=error.Error.Message} )); } //whennoerror,donext. elseawaitnext(); }); });
这段代码主要是HandleError用的,比如当身份认证失败的时候会抛出异常,而这里就是处理这个异常的。
接下来在相同的方法中添加如下代码,
app.UseExceptionHandler(appBuilder=>{ appBuilder.Use(async(context,next)=>{ varerror=context.Features[typeof(IExceptionHandlerFeature)]asIExceptionHandlerFeature; //whenauthorizationhasfailed,shouldretrunajsonmessagetoclient if(error!=null&&error.ErrorisSecurityTokenExpiredException) { context.Response.StatusCode=401; context.Response.ContentType="application/json"; awaitcontext.Response.WriteAsync(JsonConvert.SerializeObject( new{authenticated=false,tokenExpired=true} )); } //whenorthererror,retrunaerrormessagejsontoclient elseif(error!=null&&error.Error!=null) { context.Response.StatusCode=500; context.Response.ContentType="application/json"; awaitcontext.Response.WriteAsync(JsonConvert.SerializeObject( new{success=false,error=error.Error.Message} )); } //whennoerror,donext. elseawaitnext(); }); });
应用JwtBearerAuthentication
app.UseJwtBearerAuthentication(newJwtBearerOptions{ TokenValidationParameters=newTokenValidationParameters{ IssuerSigningKey=TokenAuthOption.Key, ValidAudience=TokenAuthOption.Audience, ValidIssuer=TokenAuthOption.Issuer, ValidateIssuerSigningKey=true, ValidateLifetime=true, ClockSkew=TimeSpan.FromMinutes(0) } });
完整的代码应该是这样
usingSystem; usingMicrosoft.AspNetCore.Builder; usingMicrosoft.AspNetCore.Hosting; usingMicrosoft.Extensions.Configuration; usingMicrosoft.Extensions.DependencyInjection; usingMicrosoft.Extensions.Logging; usingMicrosoft.AspNetCore.Authorization; usingMicrosoft.AspNetCore.Authentication.JwtBearer; usingCSTokenBaseAuth.Auth; usingMicrosoft.AspNetCore.Diagnostics; usingMicrosoft.IdentityModel.Tokens; usingMicrosoft.AspNetCore.Http; usingNewtonsoft.Json; namespaceCSTokenBaseAuth { publicclassStartup { publicStartup(IHostingEnvironmentenv) { varbuilder=newConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json",optional:true,reloadOnChange:true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json",optional:true); if(env.IsEnvironment("Development")) { //ThiswillpushtelemetrydatathroughApplicationInsightspipelinefaster,allowingyoutoviewresultsimmediately. builder.AddApplicationInsightsSettings(developerMode:true); } builder.AddEnvironmentVariables(); Configuration=builder.Build(); } publicIConfigurationRootConfiguration{get;} //Thismethodgetscalledbytheruntime.Usethismethodtoaddservicestothecontainer publicvoidConfigureServices(IServiceCollectionservices) { //Addframeworkservices. services.AddApplicationInsightsTelemetry(Configuration); //Enabletheuseofan[Authorize("Bearer")]attributeonmethodsandclassestoprotect. services.AddAuthorization(auth=> { auth.AddPolicy("Bearer",newAuthorizationPolicyBuilder() .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser().Build()); }); services.AddMvc(); } //Thismethodgetscalledbytheruntime.UsethismethodtoconfiguretheHTTPrequestpipeline publicvoidConfigure(IApplicationBuilderapp,IHostingEnvironmentenv,ILoggerFactoryloggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); app.UseApplicationInsightsRequestTelemetry(); app.UseApplicationInsightsExceptionTelemetry(); #regionHandleException app.UseExceptionHandler(appBuilder=>{ appBuilder.Use(async(context,next)=>{ varerror=context.Features[typeof(IExceptionHandlerFeature)]asIExceptionHandlerFeature; //whenauthorizationhasfailed,shouldretrunajsonmessagetoclient if(error!=null&&error.ErrorisSecurityTokenExpiredException) { context.Response.StatusCode=401; context.Response.ContentType="application/json"; awaitcontext.Response.WriteAsync(JsonConvert.SerializeObject( new{authenticated=false,tokenExpired=true} )); } //whenorthererror,retrunaerrormessagejsontoclient elseif(error!=null&&error.Error!=null) { context.Response.StatusCode=500; context.Response.ContentType="application/json"; awaitcontext.Response.WriteAsync(JsonConvert.SerializeObject( new{success=false,error=error.Error.Message} )); } //whennoerror,donext. elseawaitnext(); }); }); #endregion #regionUseJwtBearerAuthentication app.UseJwtBearerAuthentication(newJwtBearerOptions{ TokenValidationParameters=newTokenValidationParameters{ 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中新建一个WebAPIControllerClass,命名为TokenAuthController.cs。我们将在这里完成登录授权
在同文件下添加两个类,分别用来模拟用户模型,以及用户存储,代码应该是这样
publicclassUser { publicGuidID{get;set;} publicstringUsername{get;set;} publicstringPassword{get;set;} } publicstaticclassUserStorage { publicstaticList<User>Users{get;set;}=newList<User>{ newUser{ID=Guid.NewGuid(),Username="user1",Password="user1psd"}, newUser{ID=Guid.NewGuid(),Username="user2",Password="user2psd"}, newUser{ID=Guid.NewGuid(),Username="user3",Password="user3psd"} }; }
接下来在TokenAuthController.cs中添加如下方法
privatestringGenerateToken(Useruser,DateTimeexpires) { varhandler=newJwtSecurityTokenHandler(); ClaimsIdentityidentity=newClaimsIdentity( newGenericIdentity(user.Username,"TokenAuth"), new[]{ newClaim("ID",user.ID.ToString()) } ); varsecurityToken=handler.CreateToken(newSecurityTokenDescriptor { Issuer=TokenAuthOption.Issuer, Audience=TokenAuthOption.Audience, SigningCredentials=TokenAuthOption.SigningCredentials, Subject=identity, Expires=expires }); returnhandler.WriteToken(securityToken); }
该方法仅仅只是生成一个AuthToken,接下来我们来添加另外一个方法来调用它
在相同文件中添加如下代码
[HttpPost] publicstringGetAuthToken(Useruser) { varexistUser=UserStorage.Users.FirstOrDefault(u=>u.Username==user.Username&&u.Password==user.Password); if(existUser!=null) { varrequestAt=DateTime.Now; varexpiresIn=requestAt+TokenAuthOption.ExpiresSpan; vartoken=GenerateToken(existUser,expiresIn); returnJsonConvert.SerializeObject(new{ stateCode=1, requertAt=requestAt, expiresIn=TokenAuthOption.ExpiresSpan.TotalSeconds, accessToken=token }); } else { returnJsonConvert.SerializeObject(new{stateCode=-1,errors="Usernameorpasswordisinvalid"}); } }
该文件完整的代码应该是这样
usingSystem; usingSystem.Collections.Generic; usingSystem.Linq; usingSystem.Threading.Tasks; usingMicrosoft.AspNetCore.Mvc; usingNewtonsoft.Json; usingSystem.IdentityModel.Tokens.Jwt; usingSystem.Security.Claims; usingSystem.Security.Principal; usingMicrosoft.IdentityModel.Tokens; usingCSTokenBaseAuth.Auth; namespaceCSTokenBaseAuth.Controllers { [Route("api/[controller]")] publicclassTokenAuthController:Controller { [HttpPost] publicstringGetAuthToken(Useruser) { varexistUser=UserStorage.Users.FirstOrDefault(u=>u.Username==user.Username&&u.Password==user.Password); if(existUser!=null) { varrequestAt=DateTime.Now; varexpiresIn=requestAt+TokenAuthOption.ExpiresSpan; vartoken=GenerateToken(existUser,expiresIn); returnJsonConvert.SerializeObject(new{ stateCode=1, requertAt=requestAt, expiresIn=TokenAuthOption.ExpiresSpan.TotalSeconds, accessToken=token }); } else { returnJsonConvert.SerializeObject(new{stateCode=-1,errors="Usernameorpasswordisinvalid"}); } } privatestringGenerateToken(Useruser,DateTimeexpires) { varhandler=newJwtSecurityTokenHandler(); ClaimsIdentityidentity=newClaimsIdentity( newGenericIdentity(user.Username,"TokenAuth"), new[]{ newClaim("ID",user.ID.ToString()) } ); varsecurityToken=handler.CreateToken(newSecurityTokenDescriptor { Issuer=TokenAuthOption.Issuer, Audience=TokenAuthOption.Audience, SigningCredentials=TokenAuthOption.SigningCredentials, Subject=identity, Expires=expires }); returnhandler.WriteToken(securityToken); } } publicclassUser { publicGuidID{get;set;} publicstringUsername{get;set;} publicstringPassword{get;set;} } publicstaticclassUserStorage { publicstaticList<User>Users{get;set;}=newList<User>{ newUser{ID=Guid.NewGuid(),Username="user1",Password="user1psd"}, newUser{ID=Guid.NewGuid(),Username="user2",Password="user2psd"}, newUser{ID=Guid.NewGuid(),Username="user3",Password="user3psd"} }; } }
接下来我们来完成授权验证部分
在Controllers中新建一个WebAPIControllerClass,命名为ValuesController.cs
在其中添加如下代码
publicstringGet() { varclaimsIdentity=User.IdentityasClaimsIdentity; varid=claimsIdentity.Claims.FirstOrDefault(c=>c.Type=="ID").Value; return$"Hello!{HttpContext.User.Identity.Name},yourIDis:{id}"; }
为方法添加装饰属性
[HttpGet] [Authorize("Bearer")] 完整的文件代码应该是这样 usingSystem.Linq; usingMicrosoft.AspNetCore.Mvc; usingMicrosoft.AspNetCore.Authorization; usingSystem.Security.Claims; namespaceCSTokenBaseAuth.Controllers { [Route("api/[controller]")] publicclassValuesController:Controller { [HttpGet] [Authorize("Bearer")] publicstringGet() { varclaimsIdentity=User.IdentityasClaimsIdentity; varid=claimsIdentity.Claims.FirstOrDefault(c=>c.Type=="ID").Value; return$"Hello!{HttpContext.User.Identity.Name},yourIDis:{id}"; } } }
最后让我们来添加视图
在Controllers中新建一个WebControllerClass,命名为LoginController.cs
其中的代码应该是这样
usingMicrosoft.AspNetCore.Mvc; namespaceCSTokenBaseAuth.Controllers { [Route("[controller]/[action]")] publicclassLoginController:Controller { publicIActionResultIndex() { returnView(); } } }
在项目Views目录下新建一个名为Login的目录,并在其中新建一个Index.cshtml文件。
代码应该是这个样子
<htmlxmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> </head> <body> <buttonid="getToken">getToken</button> <buttonid="requestAPI">requestAPI</button> <scriptsrc="https://code.jquery.com/jquery-3.1.1.min.js"></script> <script> $(function(){ varaccessToken=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>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。