MVC 使用 JWT
JWT是Json Web Token的縮寫,詳細規範在RFC7519中,目的是用來傳遞JSON物件並且透過雜湊簽章來確保資料沒有被變更過。因此我們可以把它當作驗證的token來用,也可以用來在兩個client之間傳遞資料。
JWT包含了三個部分,header、payload和signature,並使用”點”( . )將三個部分連結起來成為一個字串進行傳遞,因此一個完整的JWT字串看起來會像這樣
xxasdasdasdas.asdasdasfdjaskhdkjash.asdasdasd
Header Header的json通常包含兩部分,token類型typ及加密類型alg,例如以下JSON物件代表了使用HS256演算法來產生JWT,token
{
"alg": "HS256",
"typ": "JWT"
}
然後以Base64Url編碼(例如 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
),此為JWT的header部分。
Payload
Payload則用來存放用來要求資料的資訊(claims),例如使用者名稱帳號等。
claims有三種reserved claims
,public claims
及private claims
。
在這裡可以存放一些基本的驗證資訊,在RFC7519中也包含了基本內建的幾個資訊如iss(issuer)
、sub(subject)
等等(非必要),當然我們也可以加入自己要的資訊,一個payload看起來會像這樣:
{
"sub": "testGreg",
"age": 25,
"roles": "Admin"
}
與header一樣,payload的json也會以Base64Url編碼。
Signature
Signature的是以編碼過的header,payload及密碼來產生,例如使用HMAC SHA256編碼,則簽名會以下面方式來建立:
第一部分為header.payload的Base64Url編碼。
第二部分為密碼(這邊的密碼為secret)。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
則產生簽名,簽名用來驗證JWT的發送者的身分,並確保資訊沒有被修改。 將header,payload及signature組合起來就是JWT的token。 要特別注意的是JWT只有經過編碼(encoded)但並沒有加密(encrypted),所以JWT的內容只要解碼都看得到,因此JWT中不要存放敏感的資訊例如密碼或信用卡號等等。
Install-Package Microsoft.Owin.Host.SystemWeb
提供OWIN應用運行的Host 整合ASP.NET Pipeline
安裝Nuget套件OWIN Security Jwt
Install-Package Microsoft.Owin.Security.Jwt
提供JWT驗證授權的OWIN Middleware
加入Startup啟用類別(OWIN運行的進入點)
啟用JWT Bearer Authentication
加入JWT token初始化
在這注意SymmetricKeyIssuerSecurityTokenProvider裡面請使用Microsoft.Owin.Security.DataHandler.Encoder.TextEncodings.Base64Url.Decode
在Startup.cs裡
public void Configuration(IAppBuilder app)
{
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issueMicrosoft.Owin.Security.DataHandler.Encoder.TextEncodings.Base64Url.Decode(signkey))
},
TokenValidationParameters = new TokenValidationParameters()
{
ValidAudience = audience,
ValidIssuer = issuer,
//認證SigningKey
IssuerSigningKey = new InMemorySymmetricSecurity (Microsoft.Owin.Security.DataHandler.Encoder.TextEncodings.Base64Url.Decode(signkey)),
IssuerSigningTokens = new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret).SecurityTokens,
}
});
}
下面這三行請到web.config做設定
ConfigurationManager.AppSettings["Audience"];
ConfigurationManager.AppSettings["Issuer"];
ConfigurationManager.AppSettings["SignKey"];
在這三行中設定是看個人如何設定都可 只有SignKey要注意 如果SignKey字元設定太少 會發生以下的 Exception IDX10603: The ‘System.IdentityModel.Tokens.InMemorySymmetricSecurityKey’ cannot have less than: ‘128’ bits. Parameternavn: key.KeySize The actual size was 48. 你可以將SignKey設定成這樣越長越好就不會再發生這樣問題了
<add key="SignKey" value="dsadasdasdsadasddasdJpc3MiOiJodHRwczovL3NlY3VyZS5leGFtcGxlLmNvbS8iLCJleHAiOjE0MTA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9vcmdudW0iOiI5ODc5ODc5ODciLCJodHRwOi8vZXhhbXBsZS5jb20vdXNlciI6Im1lQGV4YW1wbGUuY29tIiwiaWF0IjoxNDA4NDE5NTQwfQ" />
開始實作取得token
public static string setToken()
{
string result = string.Empty;
try
{
string audience = ConfigurationManager.AppSettings["Audience"];
string issuer = ConfigurationManager.AppSettings["Issuer"];
string signKey = ConfigurationManager.AppSettings["SignKey"];
System.Security.Claims.Claim[] claims = new[]
{
//自訂Payload附帶其他Identity其他屬性的type & value map宣告在這可以使用不敏感資料進行
new System.Security.Claims.Claim(System.IdentityModel.Claims.ClaimTypes.Name, _user.UserID),
new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Role, _user.UserRoles.ToString())
};
InMemorySymmetricSecurityKey key = new InMemorySymmetricSecurityK (Microsoft.Owin.Security.DataHandler.Encoder.TextEncodings.Base64Url.Decode(signKey));
SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature SecurityAlgorithms.Sha256Digest);
//開始塞資料給token
JwtSecurityToken token = new JwtSecurityToken(
issuer: issuer,
audience: audience,
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
string cookieToken, formToken;
//寫入token
result = new JwtSecurityTokenHandler().WriteToken(token);
}
catch (Exception ex)
{
//do somethings...
}
return result;
}
驗證Token資訊
private bool VerifyToken(string token)
{
bool result = true;
var validationParameters = new TokenValidationParameters()
{
IssuerSigningToken = new System.ServiceModel.Security.Tokens.BinarySecretSecurityTok (Microsoft.Owin.Security.DataHandler.Encoder.TextEncodings.Base64Url.Decode(signkey)),
ValidAudience = audience,
ValidIssuer = issuer,
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true
};
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SecurityToken validatedToken = null;
try
{
tokenHandler.ValidateToken(token, validationParameters, out validatedToken);
}
catch (SecurityTokenException ex)
{
//do somethings...
return false;
}
catch (Exception e)
{
//do somethings...
return false;
}
return result;
}
使用方式先在專案下建立一個資料夾叫做 App_Code 在 App_Code 底下建立頁面 CommonRazorFunctions.cshtml 寫下下面functions
@functions{
public static object GetToken()
{
object result = null;
if (User.Identity.IsAuthenticated)
{
result = FundaySales.Controllers.SettingsController.setToken();
}
return result;
}
}
使用ajax方式Set Header Token
jqxhr.setRequestHeader('RequestVerificationToken', '@CommonRazorFunctions.GetToken()')
之後再共用 Controller去做驗證動作
在這是使用OnActionExecuting
裡去做驗證 如果驗證失敗會回到登入畫面
if (!validateAJAXTokenAttribute(filterContext))
{
filterContext.Result = RedirectToAction("Logout", "Account");
return;
}
取得ajaxheader token
private bool validateAJAXTokenAttribute(ActionExecutingContext filterContext)
{
bool result = true;
//是否ajax 方式呼叫controller 與 POST 方式呼叫,才進入驗證
if (!filterContext.HttpContext.Request.IsAjaxRequest())
{
return result;
}
System.Collections.Specialized.NameValueCollection headers = filterContext.HttpContext.Request.Headers;
try
{
if (headers == null)
{
return false;
}
IEnumerable<string> xRequestedWithHeaders = headers.GetValues("X-Requested-With").AsEnumerable();
if (filterContext.HttpContext.Request.HttpMethod.ToUpper() == "POST")
{
string headerValue = xRequestedWithHeaders.FirstOrDefault();
if (!string.IsNullOrEmpty(headerValue))
{
string[] tokenHeaders = headers.GetValues("RequestVerificationToken");
result = VerifyToken(tokenHeaders[0]);
}
}
}
catch (Exception ex)
{
return false;
}
return result;
}
若有發生以下問題:
IDX10603: The 'System.IdentityModel.Tokens.InMemorySymmetricSecurityKey' cannot have less than: '128' bits. Parameternavn: key.KeySize The actual size was 48錯誤