在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>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。