.Net 使用JWT
JWT
- 什么是JSON Web Token?
SON Web Token(以下简称 JWT)是一套开放的标准(RFC 7519),它定义了一套简洁(compact)且 URL 安全(URL-safe)的方案,以安全地在客户端和服务器之间传输 JSON 格式的信息。
- 它有什么优点?
- 体积小(一串字符串)。因而传输速度快
- 传输方式多样。可以通过 HTTP 头部(推荐)/URL/POST 参数等方式传输
- 严谨的结构化。它自身(在 payload 中)就包含了所有与用户相关的验证消息,如用户可访问路由、访问有效期等信息,服务器无需再去连接数据库验证信息的有效性,并且 payload 支持应用定制
- 支持跨域验证,多应用于单点登录
- 为什么使用JWT?
充分依赖无状态 API ,契合 RESTful 设计原则(无状态的 HTTP)
JWT的Token组成部分
- header:对token的类型以及加密方法进行base64加密得到;
- payload:对有效信息进行base64加密得到;
- signature:对base64加密后的header和base64加密后的payload使用'.'组合为字符串,再通过header中指定的加密方式加secret组合加密;
他是怎么鉴别客户端传来的tojen是否被篡改的?
在我们收到客户端的token后,将token的第一和第二部分,再次进行组合加密第三部分的过程,得到一个signature。如果这个singature和token里的singnature对比不同,则token被篡改。 因此我们服务器端必须保留secret且不能泄露,否则客户端可以自行签发。
如何使用JWT
- 加密
- 引入JWT程序包
- 生成token
private static string Secret = "serect"; static void Main(string[] args) { //如果设定过期时间,一定的是秒数 var payload = new Dictionary<string, object>() { {"name","huangwei" }, }; IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); IJsonSerializer serializer = new JsonNetSerializer(); IBase64UrlEncoder urlencoder = new JwtBase64UrlEncoder(); IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlencoder); var token = encoder.Encode(payload, Secret); Console.WriteLine(token); Console.ReadKey(); }
private static string Secret = "serect"; static void Main(string[] args) { IDateTimeProvider provider = new UtcDateTimeProvider(); var timeSpan=provider.GetNow().AddHours(1).ToUnixTimeSeconds(); var payload = new Dictionary<string, object>() { {"name","huangwei" }, {"exp",timeSpan}, }; IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); IJsonSerializer serializer = new JsonNetSerializer(); IBase64UrlEncoder urlencoder = new JwtBase64UrlEncoder(); IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlencoder); var token = encoder.Encode(payload, Secret); Console.WriteLine(token); Console.ReadKey(); } }
WebApi中使用JWT进行身份验证
- 发起登录请求,并传递参数
- 接收参数,验证登录逻辑,登陆成功则返回token
- 客户端接受,保存token,请求权限api,并将token附加在header头
- 服务器验证身份,如果token不存在或token被篡改,验证失败,没有权限获得数据
- 未使用过滤器的身份验证,略显代码冗余
public class JwtTools
{
//密钥,
public static string key = "love this world and love you too";
public static string Encode(Dictionary<string,object> payload,long expTime,string key)
{
key = key == null ? key : JwtTools.key;
payload.Add("exp", expTime);
IJsonSerializer serializer = new JsonNetSerializer();
IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
IBase64UrlEncoder base64UrlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, base64UrlEncoder);
//通过header.payload 用 key加密 生成第三段 signature 并返回三段完整token
var token = encoder.Encode(payload, key);
return token;
}
private static string DeEncode(string token,string key)
{
try
{
key = key == null ? key : JwtTools.key;
IJsonSerializer jsonSerializer = new JsonNetSerializer();
var provider = new UtcDateTimeProvider();
IJwtValidator jwtValidator = new JwtValidator(jsonSerializer, provider);
IBase64UrlEncoder base64UrlEncoder = new JwtBase64UrlEncoder();
IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
IJwtDecoder decoder = new JwtDecoder(jsonSerializer,jwtValidator,base64UrlEncoder,algorithm);
var json = decoder.Decode(token, key,verify:true);
return json;
}
catch (TokenExpiredException)
{
throw;
}
catch (SignatureVerificationException)
{
throw;
}
}
public static string ValidateLogin(HttpRequestHeaders headers, string key)
{
if (headers.GetValues("token") == null || !headers.GetValues("token").Any())
{
throw new Exception("请登录");
}
return DeEncode(headers.GetValues("token").First(), key);
}
}
[HttpPost]
[Route("Login")]
public string Login(dynamic model)
{
if(model.userName.ToString().Length>2 && model.userPwd.ToString() == "123456")
{
return JwtTools.Encode(new Dictionary<string, object>(){
{"name",model.userName.ToString() }
},DateTimeOffset.UtcNow.AddMinutes(30).ToUnixTimeSeconds(),JwtTools.key);
}
throw new Exception("登录失败");
}
[HttpGet]
[Route("getMs")]
public string GetAccoutMS()
{
var json = JwtTools.ValidateLogin(Request.Headers, JwtTools.key);
//首先我们要知道webApi中 http无状态
return json;
}
}
使用这种方法可以实现身份验证,但是如果每个Action都需要通过身份验证,我们岂不是需要在每一个Action中都写一次调用。显然很麻烦。
因此我们有第二种方法,
过滤器
,当每一次请求到达时,都将先执行过滤器的方法,如果通过则将执行Action。符合asp.net管道事件。
- 过滤器
- 1.使用过滤器对JWT进行验证
- 2.将验证过后得到的结果赋给User.Identity......
- 3.因为User和Identity是接口类型,因此我们可以实现这些接口,并在过滤器中赋值给User和Identity
过滤器
public class MyAuth : Attribute, IAuthorizationFilter { public bool AllowMultiple { get; } public async Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation) { IEnumerable<string> headers; if(actionContext.Request.Headers.GetValues("token")==null|| actionContext.Request.Headers.TryGetValues("token",out headers) == false) { return new HttpResponseMessage(HttpStatusCode.Unauthorized); } var logName = JwtTools.DeEncode<Dictionary<string,object>>(headers.First())["user"]; var userID = JwtTools.DeEncode<Dictionary<string,object>>(headers.First())["userID"]; //过期时间 var expTime = (long)JwtTools.DeEncode<Dictionary<string, object>>(headers.First())["exp"]; if(DateTimeOffset.UtcNow.ToUnixTimeSeconds() > expTime) { return new HttpResponseMessage(HttpStatusCode.Unauthorized); } (actionContext.ControllerContext.Controller as ApiController).User = new AppUser(logName.ToString(), int.Parse(userID.ToString())); return await continuation(); } }
实现User和Identity
public class AppUser : IPrincipal { public AppUser(string name,int id) { Identity = new AppIdentity(name, id); } public IIdentity Identity { get; } public bool IsInRole(string role) { throw new NotImplementedException(); } } public class AppIdentity : IIdentity { public AppIdentity(string name,int id) { Name= name; Id = id; } public string Name { get; } public int Id { get;} public string AuthenticationType { get; } public bool IsAuthenticated { get; } }
JWTTools
public class JwtTools { public static string key = "huangwei@"; public static string Encode(Dictionary<string,object> payload,string key=null) { key = string.IsNullOrEmpty(key) ? JwtTools.key : key; IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); IJsonSerializer serializer = new JsonNetSerializer(); IBase64UrlEncoder base64UrlEncoder = new JwtBase64UrlEncoder(); IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, base64UrlEncoder); payload.Add("exp",DateTimeOffset.UtcNow.AddHours(2).ToUnixTimeSeconds()); var json=encoder.Encode(payload, key); return json; } public static T DeEncode<T>(string token, string key = null) { key = string.IsNullOrEmpty(key) ? JwtTools.key : key; try { var provider = new UtcDateTimeProvider(); IJsonSerializer jsonSerializer = new JsonNetSerializer(); IBase64UrlEncoder base64UrlEncoder = new JwtBase64UrlEncoder(); IJwtValidator validator = new JwtValidator(jsonSerializer, provider); IJwtAlgorithm jwtAlgorithm = new HMACSHA256Algorithm(); IJwtDecoder decoder = new JwtDecoder(jsonSerializer, validator, base64UrlEncoder, jwtAlgorithm); var json = decoder.Decode(token, key, verify: true); return JsonConvert.DeserializeObject<T>(json); } catch (TokenExpiredException) { throw; } catch (SignatureVerificationException) { throw; } } }
- WebApi支持数据注解