《權(quán)限設(shè)計(jì)》關(guān)于權(quán)限設(shè)計(jì)的一些方案,這里是使用.net core來(lái)實(shí)現(xiàn)jwt的授權(quán)驗(yàn)證,為了方便平時(shí)快速接入,開(kāi)箱即用。jwt有token發(fā)行端(JwtSecurityTokenExtension)和接收端驗(yàn)證(PermissionsControl)兩塊內(nèi)容,Github源碼。
Jwt Token發(fā)行端
有時(shí)候只是簡(jiǎn)單用JWT沒(méi)必要上Identity4,殺雞焉用宅牛刀,JwtSecurityTokenExtension類(lèi)庫(kù)生成token使用了微軟的System.IdentityModel.Tokens.Jwt,加密算法是對(duì)稱加密(HS256),里面有創(chuàng)建token以及刷新token的方法,講下關(guān)鍵步驟和代碼思路,具體代碼可以自行去github翻看JwtSecurityTokenExtension類(lèi)庫(kù)
token密文包含的內(nèi)容
token生成會(huì)默認(rèn)帶幾個(gè)參數(shù),上面的是頭部信息,加密方式和類(lèi)型,還有body這是必須有的兩個(gè)時(shí)間,用于校驗(yàn)端檢查token是否過(guò)期的。
生成的token可以通過(guò) http://jwt.calebb.net/ 這個(gè)網(wǎng)站進(jìn)行解析
{
alg: "HS256",
typ: "JWT"
}.
{
exp:"jwt過(guò)期時(shí)間",
nbf:"jwt生效時(shí)間"
}
你還可以你填寫(xiě)其他內(nèi)容進(jìn)去,CreateToken()方法的參數(shù)就是字典,把你需要傳遞的參數(shù)都轉(zhuǎn)化在token里一起加密。
public interface IJwtSecurityTokenExtension
{
TokenOut CreateToken(Dictionary<string, string> claimsContent = null);
TokenOut RefreshToken(string refreshToken);
}
返回值TokenOut里有token值和刷新token的值,以及給前端驗(yàn)證token是否過(guò)期的過(guò)期時(shí)間,過(guò)期時(shí)間前端可以先校驗(yàn)一遍是否過(guò)期。
public class TokenOut
{
/// <summary>
/// token
/// </summary>
public string Token { get; set; }
/// <summary>
/// token過(guò)期時(shí)間
/// </summary>
public DateTime TokenExpires { get; set; }
/// <summary>
/// 刷新token
/// </summary>
public string RefreshToken { get; set; }
/// <summary>
/// refresh token過(guò)期時(shí)間
/// </summary>
public DateTime RefreshTokenExpires { get; set; }
}
token 和 refreshToken 的用途
客戶端請(qǐng)求授權(quán)服務(wù)器會(huì)一起頒發(fā)兩個(gè)token的過(guò)期時(shí)間比較短,refreshToken的過(guò)期時(shí)間比較長(zhǎng),如果token過(guò)期了,客戶端會(huì)通過(guò)refreshToken去跟授權(quán)服務(wù)器換一個(gè)新的token。這樣就不需要頻繁登錄,因?yàn)槊看蔚卿浂夹枰脩羧ヌ顚?xiě)登錄密碼或者第三方授權(quán)。
針對(duì)這個(gè)用途,我剛開(kāi)始搞這塊的時(shí)候其實(shí)是有個(gè)疑問(wèn)的,既然用refreshToken可以置換新token,那為何不直接把token的時(shí)間設(shè)置長(zhǎng)些呢?簡(jiǎn)單粗暴何嘗不好

其實(shí)這么搞也可以,但有時(shí)候處于安全考慮,雙token可以提高那么一點(diǎn)點(diǎn)安全性(減少被截獲token時(shí)候出現(xiàn)的攻擊可能性),因?yàn)榭蛻舳撕蛻?yīng)用服務(wù)交互的頻率是遠(yuǎn)遠(yuǎn)大于客戶端和授權(quán)服務(wù)的交互,黑客劫持大概率能劫持到為客戶端和應(yīng)用服務(wù)交互的token,但因?yàn)閠oken的過(guò)期時(shí)間短,相對(duì)安全。
更換 refreshToken 的存儲(chǔ)方式
refreshToken的創(chuàng)建繼承于IRefreshTokenStore,以TryAddSingleton方式注入。你可以新寫(xiě)你自己的refreshToken存儲(chǔ)方式替換實(shí)現(xiàn),Startup重新注入便會(huì)替換(TryAddSingleton特性)
public interface IRefreshTokenStore
{
RefreshTokenModel Get(string refreshToken);
void Set(string refreshToken, RefreshTokenModel model, TimeSpan expirationTime);
}
//注入
services.TryAddSingleton<IRefreshTokenStore, RefreshTokenStore>();
Jwt Token校驗(yàn)端
一般是引用服務(wù)權(quán)限校驗(yàn)使用,《關(guān)于權(quán)限的設(shè)》計(jì) 這里有篇文章講述幾種方案(基于資源/基于角色/基于權(quán)限)
Github源碼的PermissionsControl模塊分別實(shí)現(xiàn)了這幾種方式的案例和組件。能應(yīng)對(duì)比較多種情況不用動(dòng)手?jǐn)]輪子。

使用方式
只需要ConfigureServices注入便可,但只實(shí)現(xiàn)和基于權(quán)限和基于角色。
基于資源的統(tǒng)一通用性非常差,需要自己編寫(xiě)(在WebApp項(xiàng)目下的DocumentAuthorization有demo)。

[Authorize]只能驗(yàn)證token時(shí)間有沒(méi)有過(guò)期,[UserPermissionCheck()] 和[RoleCheck("admin")] 分別驗(yàn)證權(quán)限和角色。
services.AddExtensionAuthorization(Configuration);
基于角色
如果你需要這個(gè)能識(shí)別到角色,需要在創(chuàng)建token時(shí)候加入,rol是約定
var dic = new Dictionary<string, string>
{
{"rol", role},//角色
};
return _tokenHelper.CreateToken(dic);
[RoleCheck("admin")]就可以直接使用了
這種是針對(duì)一些角色跟權(quán)限掛鉤的,并且權(quán)限變化少的項(xiàng)目。在開(kāi)發(fā)時(shí)候定好角色就行。
基于權(quán)限
如果你需要這個(gè)能識(shí)別到權(quán)限,需要在創(chuàng)建token時(shí)候加入,per是約定
var dic = new Dictionary<string, string>
{
{"per", role},//權(quán)限
};
return _tokenHelper.CreateToken(dic);
[PermissionCheck("update")]
和基于角色同理,可以傳入權(quán)限,但如果不傳,會(huì)根據(jù)當(dāng)前控制器名字來(lái)校驗(yàn)權(quán)限,這種方式是針對(duì)應(yīng)用于權(quán)限比較少的項(xiàng)目,因?yàn)闄?quán)限名字全部存儲(chǔ)在token里,token長(zhǎng)度會(huì)增加,導(dǎo)致每次請(qǐng)求浪費(fèi)大量網(wǎng)絡(luò)資源。
所以延伸出
[UserPermissionCheck()]
使用這個(gè)標(biāo)簽需要自行實(shí)現(xiàn)IUserPermissionStore獲取用戶權(quán)限接口,這種是把權(quán)限持久化到存儲(chǔ)媒介中,然后通過(guò)GetUserPermission()查詢出來(lái),那么token就無(wú)需帶上用戶所有權(quán)限了。其實(shí)如果做到這種程度,是否使用JWT也是個(gè)問(wèn)題,session或者每次都查詢r(jià)edis校驗(yàn)更好,見(jiàn)仁見(jiàn)智把。
public interface IUserPermissionStore
{
string [] GetUserPermission(object key);
}
這種方式更適合于微服務(wù)體系中多系統(tǒng)(子系統(tǒng))但用戶體系相同的。用戶以及其權(quán)限可以統(tǒng)一管理,但每個(gè)子系統(tǒng)的權(quán)限設(shè)計(jì)都千差萬(wàn)別,每個(gè)服務(wù)啟動(dòng)的時(shí)候都會(huì)把服務(wù)的所有權(quán)限同步到總的管理系統(tǒng),便于統(tǒng)一管理。