基于JWT的身份认证(05)
目录:
- 过滤器
- JWT概念
一. Web Api中的过滤器
(1)所处命名空间不同
WebApi过滤器的命名空间是”System.web.Http”,而MVC过滤器的命名空间是”System.Web.Mvc”
(2)WebApi没有结果过滤器
WP过滤器的执行顺序为:OnAuthorization–OnActionExecuting–Action方法执行–OnActionExecuted
WebApi过滤器的基本用法:
// 授权过滤器
public class MyAuthorize : AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
//1.如果保留如下代码,则会运行.net framework定义好的身份验证,如果希望自定义身份验证,则删除如下代码
// base.OnAuthorization(actionContext);
//2.获取控制器作用的Controller和action的名字
string controllerName = actionContext.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower();
string actionName = actionContext.ActionDescriptor.ActionName.ToLower();
HttpContext.Current.Response.Write("身份验证过滤器作用于" + controllerName + "控制器下的" + actionName + "方法");
}
}
//行为过滤器
public class MyAction: ActionFilterAttribute
{
/// <summary>
/// 在Action方法运行之前调用
/// </summary>
public override void OnActionExecuting(HttpActionContext actionContext)
{
//2.获取控制器作用的Controller和action的名字
string controllerName = actionContext.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower();
string actionName = actionContext.ActionDescriptor.ActionName.ToLower();
HttpContext.Current.Response.Write("行为过滤器OnActionExecuting作用于" + controllerName + "控制器下的" + actionName + "方法运行之前");
}
/// <summary>
/// 在Action方法运行之后调用
/// </summary>
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
//2.获取控制器作用的Controller和action的名字
string controllerName = actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerName.ToLower();
string actionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName.ToLower();
HttpContext.Current.Response.Write("行为过滤器OnActionExecuted作用于" + controllerName + "控制器下的" + actionName + "方法运行之后");
}
}
// 异常过滤器
public class MyException : FilterAttribute,IExceptionFilter
{
public async Task ExecuteExceptionFilterAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
//1.获取异常信息
string errorMsg = actionExecutedContext.Exception.ToString();
//2.对获取的异常信息进行处理
using (StreamWriter writer = File.AppendText("d:/err.txt"))
{
await writer.WriteLineAsync(errorMsg);
}
}
}
二.JWT
- Json web token 简称:JWT,一种基于JSON的开放标准,便于从资源服务器获取资源,也可被加密
- 优点:
-JWT是无状态的,不需要客户端保存客户会话信息,减轻服务器读取压力,同时易于扩展、易于分布式部署
-跨语言
-便于传输,构成简单,字节占用空间少
-自身构成有payload部分,可以存储一下业务逻辑相关的非敏感信息
- 三段信息组成,”.”连接:
(1)eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
(2)eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.
(3)TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
- 三部分组成
(1)Header 头部
-两部分:类型(如 “typ”:”JWT”)和加密算法(如”alg”:”HS256″)也可以添加其它自定义的一些参数,然后对这个对象机型base64编码,生成一段字符串。
{
\’typ\’: \’JWT\’,
\’alg\’: \’HS256\’
} //Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。
(2)Pyload 负载
用来存放一些业务但不敏感的信息
默认声明:iss: jwt签发者、sub: jwt所面向的用户、aud: 接收jwt的一方、exp: jwt的过期时间,这个过期时间必须要大于签发时间、nbf: 定义在什么时间之前,该jwt都是不可用的.、 iat: jwt的签发时间、 jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
(3)Singnature 签名
保存在服务器端三部分组成:header (base64后的)、payload (base64后的)、 secret
在Asp.net WebAPI 中使用JWT
(1)安装JWT包、创建JWT帮助类
public class JwtToken
{
//HMACSHA256加密
static IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
//序列化和反序列
static IJsonSerializer serializer = new JsonNetSerializer();
//Base64编解码
static IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
//UTC时间获取
static IDateTimeProvider provider = new UtcDateTimeProvider(); const string secret = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQK";//服务端
[HttpGet]
public string JiaM()
{
//设置过期时间(可以不设置,下面表示签名后 20分钟过期)
double exp = (DateTime.UtcNow.AddMinutes(20) - new DateTime(1970, 1, 1)).TotalSeconds;
var payload = new Dictionary<string, object>
{
{ "UserId", 123 },
{ "UserName", "admin" },
{"exp",exp } //该参数也可以不写
};
//注意这个是额外的参数,默认参数是 typ 和alg
var headers = new Dictionary<string, object>
{
{ "typ1", "1234" },
{ "alg2", "admin" }
};
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder,algorithm);
var token = encoder.Encode(headers, payload, secret);
return token;
}
[HttpGet]
public string JieM(string token)
{
try
{
//用于验证JWT的类
IJwtValidator validator = new JwtValidator(serializer, provider);
//用于解析JWT的类
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder,);
var json = decoder.Decode(token, secret, true);
return json;
}
catch (TokenExpiredException e)
{
//过期了自动进入这里
return "Token has expired";
}
catch (SignatureVerificationException e)
{
//校验未通过自动进入这里
return "Token has invalid signature";
}
catch (Exception e)
{
//其它错误,自动进入到这里
return "other error";
}
(2)模拟登陆接口
/// <summary>
/// 模拟登陆
/// </summary>
[HttpGet]
public string Login1(string userAccount, string pwd)
{
try
{
//这里模拟数据操作,只要是admin和123456就验证通过
if (userAccount == "admin" && pwd == "123456")
{
//1. 进行业务处理(这里模拟获取userId)
string userId = "0806";
//过期时间(可以不设置,下面表示签名后 20分钟过期)
double exp = (DateTime.UtcNow.AddMinutes(20) - new DateTime(1970, 1, 1)).TotalSeconds;
//进行组装
var payload = new Dictionary<string, object>
{
{"userId", userId },
{"userAccount", userAccount },
{"exp",exp }
};
//2. 进行JWT签名
var token = JWTHelp.JWTJiaM(payload);
var result = new { result = "ok", token = token };
return JsonConvert.SerializeObject(result);
}
else
{
var result = new { result = "error", token = "" };
return JsonConvert.SerializeObject(result);
}
}
catch (Exception)
{
var result = new { result = "error", token = "" };
return JsonConvert.SerializeObject(result);
}
}
(3)客户端调用登陆接口
//1.登录
$(\'#j_jwtLogin\').on(\'click\', function () {
$.Ajax({
url: "/api/Seventh/Login1",
type: “get”,
data: { userAccount: "admin", pwd: "123456" },
success: function (data) {
var jsonData = JSON.parse(data);
if (jsonData.result == "ok") {
console.log(jsonData.token);
//存放到本地缓存中
window.localStorage.setItem("token", jsonData.token);
alert("登录成功,ticket=" + jsonData.token);
} else {
alert("登录失败");
}
});
});
(4)服务器端过滤器
两种获取header中信息的方式:校验通过的话,则使用 actionContext.RequestContext.RouteData.Values.Add(“auth”, result); 进行解密值的存储,方便后续action的直接获取。
/// <summary>
/// 验证JWT算法的过滤器
/// </summary>
public class JWTCheck : AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
//获取表头Header中值的几种方式
//方式一:
//{
// var authHeader2 = from t in actionContext.Request.Headers
// where t.Key == "auth"
// select t.Value.FirstOrDefault();
// var token2 = authHeader2.FirstOrDefault();
//}
//方式二:
IEnumerable<string> auths;
if (!actionContext.Request.Headers.TryGetValues("auth", out auths))
{
//HttpContext.Current.Response.Write("报文头中的auth为空");
//返回状态码验证未通过,并返回原因(前端进行401状态码的捕获),注意:这句话并不能阶段该过滤器,还会继续往下走,要借助if-else
actionContext.Response = actionContext.Request.
CreateErrorResponse(HttpStatusCode.Unauthorized,
new HttpError("报文头中的auth为空"));
}
else
{
var token = auths.FirstOrDefault();
if (token != null)
{
if (!string.IsNullOrEmpty(token))
{
var result = JWTHelp.JWTJieM(token);
if (result == "expired")
{
//返回状态码验证未通过,并返回原因(前端进行401状态码的捕获)
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("expired"));
}
else if (result == "invalid")
{
//返回状态码验证未通过,并返回原因(前端进行401状态码的捕获)
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("invalid"));
}
else if (result == "error")
{
//返回状态码验证未通过,并返回原因(前端进行401状态码的捕获)
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("error"));
}
else
{
//表示校验通过,用于向控制器中传值
actionContext.RequestContext.RouteData.Values.Add("auth", result);
}
}
}
else
{
//返回状态码验证未通过,并返回原因(前端进行401状态码的捕获)
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("token 空"));
}
}
}
}
(5)服务器端获取信息的方法
/// <summary>
/// 加密后的获取信息
/// </summary>
[JWTCheck]
[HttpGet]
public string GetInfor()
{
var userData = JsonConvert.DeserializeObject<userData>(RequestContext.RouteData.Values["auth"].ToString()); ;
if (userData == null)
{
var result = new { Message = "error", data = "" };
return JsonConvert.SerializeObject(result);
}
else
{
var data = new { userId = userData.userId, userAccount = userData.userAccount };
var result = new { Message = "ok", data =data };
return JsonConvert.SerializeObject(result);
}
}
(6)客户端调用获取信息方法
//2.获取信息
$(\'#jwtGetInfor\').on(\'click\', function () {
//从本地缓存中读取token值
var token = window.localStorage.getItem("token");
$.ajax({
url: "/api/Seventh/GetInfor",
type: "Get",
data: {},
datatype: "json",
//设置header的方式1
headers: { "auth": token},
//设置header的方式2
//beforeSend: function (xhr) {
// xhr.setRequestHeader("auth", token)
//},
success: function (data) {
console.log(data);
var jsonData = JSON.parse(data);
if (jsonData.Message == "ok") {
var myData = jsonData.data;
console.log("获取成功");
console.log(myData.userId);
console.log(myData.userAccount);
} else {
console.log("获取失败");
}
},
//当安全校验未通过的时候进入这里
error: function (xhr) {
if (xhr.status == 401) {
console.log(xhr.responseText);
var jsonData = JSON.parse(xhr.responseText); //通过xhr.responseText获取详细的值进行判断。
console.log("授权失败,原因为:" + jsonData.Message);
}
}
});
});