基于JWT(Json Web Token)的授權方式
JWT 是JSON風格輕量級的授權和身份認證規范,可實現無狀態、分布式的Web應用授權;
從客戶端請求服務器獲取token, 用該token 去訪問實現了jwt認證的web服務器。 token 可保存自定義信息,如用戶基本信息, web服務器用key去解析token,就獲取到請求用戶的信息了;
很方便解決跨域授權的問題,因為跨域無法共享cookie,.net平臺集成的 FormAuthentication 認證系統是基于Session保存授權信息,拿不到cookie就無法認證,用jwt完美解決了。
很多時候,web服務器和授權服務器是同一個項目,所以也可以用以下架構:
實現JWT授權
1.vs2015 新建一個WebApi,安裝下面的庫,可用nuget 或 命令安裝:
install-package Thinktecture.IdentityModel.Core
install-package Microsoft.Owin.Security.Jwt
2.把Startup.Auth.cs 下的 ConfigureAuth 方法清空掉,改為:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
var issuer = ConfigurationManager.AppSettings["issuer"];
var secret = TextEncodings.Base64Url.Decode(Convert.ToBase64String(System.Text.Encoding.Default.GetBytes(ConfigurationManager.AppSettings["secret"])));
//用jwt進行身份認證
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { "Any" },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
}
});
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
//生產環境設為false
AllowInsecureHttp = true,
//請求token的路徑
TokenEndpointPath = new PathString("/oauth2/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(30),
//請求獲取token時,驗證username, password
Provider = new CustomOAuthProvider(),
//定義token信息格式
AccessTokenFormat = new CustomJwtFormat(issuer, secret),
});
}
}
3.ConfigureAuth中的 AccessTokenFormat = new CustomJwtFormat(issuer, secret)是自定義token 保存的信息格式, CustomJwtFormat.cs 類代碼
/// <summary>
/// 自定義 jwt token 的格式
/// </summary>
public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly byte[] _secret;
private readonly string _issuer;
public CustomJwtFormat(string issuer, byte[] secret)
{
_issuer = issuer;
_secret = secret;
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException(nameof(data));
}
var signingKey = new HmacSigningCredentials(_secret);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
return new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken(_issuer, "Any", data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey));
}
public AuthenticationTicket Unprotect(string protectedText)
{
throw new NotImplementedException();
}
}
4.ConfigureAuth中的 Provider = new CustomOAuthProvider() 是自定義驗證username, password 的,可以用它來實現訪問數據庫的驗證業務邏輯,CustomOAuthProvider.cs類代碼
/// <summary>
/// 自定義 jwt oauth 的授權驗證
/// </summary>
public class CustomOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var username = context.UserName;
var password = context.Password;
string userid;
if (!CheckCredential(username, password, out userid))
{
context.SetError("invalid_grant", "The user name or password is incorrect");
context.Rejected();
return Task.FromResult<object>(null);
}
var ticket = new AuthenticationTicket(SetClaimsIdentity(context, userid, username), new AuthenticationProperties());
context.Validated(ticket);
return Task.FromResult<object>(null);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
private static ClaimsIdentity SetClaimsIdentity(OAuthGrantResourceOwnerCredentialsContext context, string userid, string usercode)
{
var identity = new ClaimsIdentity("JWT");
identity.AddClaim(new Claim("userid", userid));
identity.AddClaim(new Claim("username", usercode));
return identity;
}
private static bool CheckCredential(string usernme, string password, out string userid)
{
var success = false;
// 用戶名和密碼驗證
if (usernme == "admin" && password == "admin")
{
userid = "1";
success = true;
}
else
{
userid = "";
}
return success;
}
}
5.Web.config 添加 issue 和 secret
<appSettings>
<add key="issuer" value="test"/>
<!--32個字符的secret-->
<add key="secret" value="12345678123456781234567812345678"/>
</appSettings>
使用
強烈建議用 chrome 的 postman 插件來調試
獲取token
用token請求數據
header 要添加 Authorization , 值為: Bearer [token], 獲取到的 token 替換 [token], 如
Authorization Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyaWQiOiIxIiwidXNlcmNvZGUiOiIxIiwiaXNzIjoidGVzdCIsImF1ZCI6IkFueSIsImV4cCI6MTQ4NzI0MTQ5MCwibmJmIjoxNDg0NjQ5NDkwfQ.RaWlJC3OF0RNz4mLtuW4uQtRKDHF8RXwZwzIcbZoNOo
JWT缺點
- 一旦拿到token, 可用它訪問服務器,直到過期,中間服務器無法控制它,如是它失效(有解決方案: 在 token 中保存信息,可添加額外的驗證,如加一個 flag, 把數據庫對應的flag失效,來控制token有效性)。
- token 的過期時間設置很關鍵,一般把它設到凌晨少人訪問時失效,以免用戶使用過程中失效而丟失數據。
- token保存的信息有限,且都是字符串。
Demo源碼
文章列表