JWT是什么东西我就不说太多了,我就简单介绍,要看官方一点的解释可以百度,反正我是受不了看那个。
JWT现在比较流行的跨域认证解决方案,其最大的特点我认为是:无需在服务端保存客户端会话信息(session)
这个好理解,以往用户login时,向服务端发起申请时都会收到来自服务端随机生成的一个token信息,这个token信息会在服务端以及客户端都保存下来,客户端发起请求时带上这个token,服务端验证有效即可认为是有效用户操作行为。这里面的问题就是服务端的验证,首先服务端必须在生成token时把它存储到数据库中,一般用redis(快)。服务端收到token,去数据库取出key为token值所对应的value信息,比如包含token的有效期啊,所对应的用户ID啊,用户名啊等等等等,这个时候服务端多了一个redis(数据库)的需求,对不?
JWT很简单,他也会生成一个token发送给客户端,在生成token时你可以输入有效期,用户ID信息等(当然,你啥都不放也可以),但是是加密的,你可以理解为他把你输入的信息加密后生成了一个加密后的“字符串”,他把这个“字符串”发送给了客户端使用。当服务端收到客户端发来的“字符串”只需要对其解密,并解析出你输入的信息,如果你生成token时输入了有效期,那么这个时候你解析出“有效期”跟当前时间做个比对不就知道有没有过期了吗?如果过期了,要求客户端重新登录,返回新的token即可。这个过程中服务端是完全不用去存储token的。
有一个应用场景特别适合它,分布式服务集群,因为无需存储token到服务端,服务端不需要同步用户登录的token信息。并且服务器集群在接收到同一用户的token用相同的密钥就能解析出一个有效且相同的用户信息。
现在说一下C# (.Net) 的实现方式吧
我引用的库
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
Startup.cs的配置,这里我展示的只展示了需要添加的代码
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
//JWT Authentication Configuration
services.AddAuthentication(option =>
{
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidAudience = Configuration["JsonWebToken:Audience"],//这里读取的是配置文件
ValidIssuer = Configuration["JsonWebToken:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JsonWebToken:SigningKey"]))//这是配置文件里的密钥
};
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthentication();
app.UseAuthorization();
}
生成token
private async Task<LoginOM> IssueAccessToken(AccountInfo account)
{
try
{
var jsonWebToken = ConfigurationManager.GetJsonWebToken();
var signinKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jsonWebToken.SigningKey));//密钥
int expiryInMinutes = Convert.ToInt32(jsonWebToken.ExpiryInMinutes);//有效期
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(JwtRegisteredClaimNames.Aud, jsonWebToken.Audience),
new Claim(JwtRegisteredClaimNames.Iss, jsonWebToken.Issuer),
new Claim(JwtRegisteredClaimNames.NameId, account.ID.ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, account.Guid),
new Claim(JwtRegisteredClaimNames.Exp, DateTime.UtcNow.AddMinutes(expiryInMinutes).ToString()),
new Claim(JwtRegisteredClaimNames.Email, account.Email)
}),
Expires = DateTime.UtcNow.AddMinutes(expiryInMinutes),
SigningCredentials = new SigningCredentials(signinKey, SecurityAlgorithms.HmacSha256Signature)//用密钥签名(加密)
};
var tokenHandler = new JwtSecurityTokenHandler();
var createToken = tokenHandler.CreateToken(tokenDescriptor);
var accessToken = tokenHandler.WriteToken(createToken);//生成的主角
//Update Account
account.LastLoginDate = DateTime.UtcNow;
await UpdateAccount(account);//更新数据库用户登录信息,比如最后一次登陆时间
return new LoginOM//这个是我自己定义的输出结构
{
AccessToken = accessToken,//这是主角
ExpiresTime = tokenDescriptor.Expires.ToString()//返回给前端有效期
};
}
catch (Exception ex)
{
Log.Error(ex, "AccountsComponent/IssueAccessToken Error");
return null;
}
}
解析
public static async Task<AccountInfo> GetUser(this ControllerBase controller)
{
var tokenString = GetTokenString(controller.Request.Headers["Authorization"].ToString());//从区块头获取token信息
JwtSecurityToken jst = new JwtSecurityTokenHandler().ReadJwtToken(tokenString);//解析
var accountId = Convert.ToInt32(jst.Claims.Where(c => c.Type == JwtRegisteredClaimNames.NameId).FirstOrDefault().Value);//取用户ID
var expDate = TimeHelper.GetDateTime(jst.Claims.Where(c => c.Type == JwtRegisteredClaimNames.Exp).FirstOrDefault().Value);//取有效期
if (expDate.CompareTo(DateTime.UtcNow) < 0)//有效期判断
{
throw new BaseException(ErrorCode.ACCOUNT_LOGIN_TIMEOUT, "the login timeout");
}
var user = await new AccountsComponent().GetAccountById(accountId);//使用用户ID查询用户信息
if (!string.IsNullOrWhiteSpace(user.Guid) && Guid.Parse(user.Guid) != Guid.Empty)
{
return user;//没有问题就返回
}
else
{
Log.Error(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name.ToString() + "(): " + accountId + ErrorMessage.Account_Not_Found.Message);
throw new BaseException(ErrorMessage.Invalid_Credentials.Code, GeneralResources.InvalidCredentials);
}
}
基本上就是这样了,有问题或者是有建议的欢迎留言评论或者私信我。