吃亏绝不亏,惜福才有福
jwt优势:
服务器不用存储,非常方便于分布式开发,给APP提供数据,前后台分离的项目等。登录产生jwt token的项目完全可以独立与其他项目,所谓的单点登录,jwt可以非常方便的实现,可以和项目和语言无关,用统一的解析方式就可以了,更标准化。
jwt是怎么验证用户信息的
当用户访问登录接口的时候会返回一个token,然后访问其他需要登录的接口都会带上这个token,后台进行验证如果token是有效的我们就认为用户是正常登录的,然后我们可以从token中取出来一些携带的信息进行操作。
当然这些携带的信息都可以通过其他额外的字段进行传递,但是用token传递的话,不用其他额外加其他字段了,这样数据传递也要小一点
jwt是怎么防止被伪造的:
jwt服务器不存储,用户不登陆直接随便找一个jwt发送到服务器不就可以用了?
服务器有个验证签名的过程,签名是headers , payloads用一个密钥加密而成的
服务器每次收到后在通过header和body加密后和用户传递过程的token匹配一下就知道jwt是否真实有效了
也就是说只有知道密钥才能构建一个被服务器认可的token。
但是前台已经知道了token每部分的组成,也就是说前台可以得到一个明文和一个加密和的字符串,为了防止被反推,
所以我们应该使用不可逆的摘要算法。
当然也不是说jwt就绝对的安全,只要客户端能够拿到加密的秘钥或者其他手段构建一个可以被正确验证的token,就能被绕过,
所以我们不要在jwt中去存储太敏感的信息。还有就是比较重要的信息,我们处理验证jwt正确性以为还要把用户的身份进去验证
访问重要数据被非法方法
Net Core登录生成JWT Token
net core中自带的库是:System.IdentityModel.Tokens.Jwt,基本上现在的版本都自带了,如果没有,用nuget下载即可,或者你想要用单独一个库去封装jwt的帮助类,也需要下载一下。
我们把jwt帮助类相关的可以弄到一个文件夹下面
下面分别贴一下这四个类的代码:
JWTConfig:用来保存读取jwt相关配置
public class JWTConfig { public string Issuer { get; set; } public string Audience { get; set; } public string IssuerSigningKey { get; set; } public int AccessTokenExpiresMinutes { get; set; } }
TnToken :存放需要返回的字段,很简单就一个token和一个过期时间
public class TnToken { public string TokenStr{ get; set; } public DateTime Expires { get; set; } }
ITokenHelper:token工具类的接口,方便使用依赖注入,很简单提供两个常用的方法
public interface ITokenHelper { /// <summary> /// 根据一个对象通过反射提供负载生成token /// </summary> /// <typeparam name="T"></typeparam> /// <param name="user"></param> /// <returns></returns> TnToken CreateToken<T>(T user) where T : class; /// <summary> /// 根据键值对提供负载生成token /// </summary> /// <param name="keyValuePairs"></param> /// <returns></returns> TnToken CreateToken(Dictionary<string, string> keyValuePairs); }
TokenHelper:实现类
public class TokenHelper : ITokenHelper { private IOptions<JWTConfig> _options; public TokenHelper(IOptions<JWTConfig> options) { _options = options; } /// <summary> /// 根据一个对象通过反射提供负载生成token /// </summary> /// <typeparam name="T"></typeparam> /// <param name="user"></param> /// <returns></returns> public TnToken CreateToken<T>(T user) where T:class { //携带的负载部分,类似一个键值对 List<Claim> claims = new List<Claim>(); //这里我们用反射把model数据提供给它 foreach (var item in user.GetType().GetProperties()) { object obj = item.GetValue(user); string value = ""; if (obj != null) value = obj.ToString(); claims.Add(new Claim(item.Name, value)); } //创建token return CreateToken(claims); } /// <summary> /// 根据键值对提供负载生成token /// </summary> /// <param name="keyValuePairs"></param> /// <returns></returns> public TnToken CreateToken(Dictionary<string,string> keyValuePairs) { //携带的负载部分,类似一个键值对 List<Claim> claims = new List<Claim>(); //这里我们通过键值对把数据提供给它 foreach (var item in keyValuePairs) { claims.Add(new Claim(item.Key,item.Value)); } //创建token return CreateToken(claims); } private TnToken CreateToken(List<Claim> claims) { var now = DateTime.Now; var expires = now.Add(TimeSpan.FromMinutes(_options.Value.AccessTokenExpiresMinutes)); var token = new JwtSecurityToken( issuer: _options.Value.Issuer,//Token发布者 audience: _options.Value.Audience,//Token接受者 claims: claims,//携带的负载 notBefore: now,//当前时间token生成时间 expires: expires,//过期时间 signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.Value.IssuerSigningKey)), SecurityAlgorithms.HmacSha256)); return new TnToken { TokenStr = new JwtSecurityTokenHandler().WriteToken(token), Expires = expires }; } }
这里封装了两个提供携带负载数据的方法,反射对象和键值对,当然你也可以封装一些常用的方法进去,看项目和需求了。
在Startup中去配置jwt相关:
ConfigureServices中:
#region jwt相关 services.AddTransient<ITokenHelper, TokenHelper>(); //读取配置文件配置的jwt相关配置 services.Configure<JWTConfig>(Configuration.GetSection("JWT")); //启用JWT services.AddAuthentication(Options => { Options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; Options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }). AddJwtBearer(); #endregion
如果找不到JwtBearerDefaults.AuthenticationScheme与AddJwtBearer();下载两个依赖即可,具体版本根据项目使用的版本
install-package Microsoft.Extensions.DependencyInjection install-package Microsoft.AspNetCore.Authentication.JwtBearer -version 3.0
Configure中去启用验证中间件:
//启用认证中间件 app.UseAuthentication();
appsettings中简单配置一下jwt相关的信息:
"JWT": { "Issuer": "TNBLOG", //Token发布者 "Audience": "AXJ", //Token接受者 "IssuerSigningKey": "签名秘钥",//(很重要,拿到秘钥就可以构建服务器认可的token),注意签名秘钥长度不能太短了否则报错 "AccessTokenExpiresMinutes": "30" //过期时间 }
然后在登录api的地方就可以使用了:
[ApiVersion("1.0")] [Route("api/v1/[controller]")] [ApiController] public class LoginController : ControllerBase { private ITokenHelper tokenHelper = null; private IUserDAL userDAL; public LoginController(ITokenHelper _tokenHelper, IUserDAL _userDAL) { tokenHelper = _tokenHelper; userDAL = _userDAL; } [HttpGet("{username}/{pwd}")] public ReturnModel<TnToken> Get(string username, string pwd) { ReturnModel<TnToken> returnModel = new ReturnModel<TnToken>(); if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(pwd)) { returnModel.Code = 201; returnModel.Msg = "用户名密码不能为空"; return returnModel; } UserInfo userInfo = new UserInfo() { UserName = username, UserPwd = pwd }; //实现登录 UserInfo user = userDAL.Login(userInfo); if (user != null) { //给jwt提供携带的数据 Dictionary<string, string> keyValuePairs = new Dictionary<string, string>(); keyValuePairs.Add("userName", user.UserName); TnToken tnToken = tokenHelper.CreateToken(keyValuePairs); returnModel.Code = 200; returnModel.Msg = "登录成功!"; returnModel.Value = tnToken; return returnModel; } else { returnModel.Code = 300; returnModel.Msg = "登录失败!"; return returnModel; } } }
tip:ReturnModel只是我自己封装的一个统一的接口返回格式标准而已不必在意
访问下登录接口试试
可以看到我们成功生成了token,我们可以把生成的token拿去jwt官网解密试试:
tip:一般情况下jwt只做身份证验证,jwt里边携带的数据也是为了辅助身份验证,不会直接取出来在客户端用,不需要加其他业务信息在里面,前台需要用户展示的数据比如登录成功后需要拿到用户名和设置的头像等,我们单独给字段提供就好,前台也方便直接解析。
比如像这样多提供几个字段,和token一起返回即可,当然也可以另外提供一个单独的接口在调用一次,看自己项目的业务逻辑了
注意报错:
[PII is hidden]'. Exceptions caught: '[PII is hidden]'. token: '[PII is hidden]
是因为签名秘钥长度不能太短了否则报错
net core使用jwt2:验证前台传递的token
http://www.tnblog.net/aojiancc2/article/details/2845
欢迎加群讨论技术,群:677373950(满了,可以加,但通过不了),2群:656732739