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);
}
}
基本上就是這樣了,有問題或者是有建議的歡迎留言評論或者私信我。