asp.Netmvcはセッションの代わりにJWTを使用し,単一のログインを実現する
55590 ワード
ASP.NET MVCはセッションの代わりにJWTを使用してワンポイントログインを実現 1. Tokenとは? 2. JWTとは? 3. TokenとSessionの比較 4. ASP.NET MVCはどのようにjwtを使って単点登録 を実現します
1.Tokenとは?
tokenって何?tokenは、コンピュータ認証でよく用いられるトークンと理解できる.サーバとデータ転送を行う前に、認証が行われます.
2.JWTとは?
JWTとは?JWTはJson Web Tokenの略称であり、Tokenの仕様である.暗号化された文字列で、構成部分はA.B.Cです.この文字列は、tokenを記録する暗号化方式、文字列長(A部分)、基本的なユーザ情報、荷重、発行者、期限切れ時間など(B部分)、およびAとBの共通の暗号化部分(C部分)から構成される.
3.TokenとSessionの比較
従来のSessionで明らかになった問題Session:ユーザーは、コンピュータのアイデンティティ認証のたびに、サーバメモリにsessionを格納し、クライアントにクッキーを保存して、次回のユーザー要求時に認証を行う.しかし、これで2つの問題が明らかになった.1つ目の問題は、sessionがサーバに格納されたメモリであり、要求されたユーザ数が増加すると、サーバの圧力が増大することである.2つ目の問題は、複数のサーバが存在し、sessionが現在の1つのサーバにしか格納できない場合、分散開発には適用されません.
CSRF:Sessionはcookieに基づいてユーザ識別を行い,cookieがキャプチャされるとユーザはクロスステーション要求偽造攻撃を受けやすくなり,本稿ではcsrf(cross site request forgery)をしばらく考慮しない.
Tokenの検証メカニズムtokenの検証は、サーバ側にユーザ情報を保持する必要がないため、ユーザ再クライアントが単一のログインを通過した後、複数のサーバにアクセスでき、分散開発に有利である.またtokenは暗号化された文字列で、期限切れを設定でき、模倣されにくい.
tokenを使用すると、クライアントとサービス側のインタラクティブなプロセスは大体次のようになります.ユーザは、ユーザ名パスワードを使用してサーバ に要求する.サーバは、ユーザの情報 を検証する.サーバは、認証によってユーザにtoken を送信する.クライアントはtokenを格納し、要求のたびにこのtoken値 を添付する.サービス側はtoken値を検証し、データ を返す.
tokenはクッキーに保存してもよいし、リクエストヘッダに保存してもよいし、tokenをリクエストヘッダに配置し、macアドレスとマシン名を携帯することを推奨する.
4. ASP.NET MVCはどのようにjwtを使って単点上陸を実現します
UserStateクラスの定義
AppManagerクラスとTokenInfoクラスの定義
JsonHelperの定義
ホームコントローラでLoginを定義する方法
生成tokenはNuGetを使用してJWTをダウンロードする.dll
カスタムアイデンティティ認証ここでは、AuthorizeAttributeをカスタマイズするカスタムアイデンティティ認証モードを採用します.
HTMLページ
Common.js
ソースアドレス: Github:https://github.com/Lyq1454759684/Token-Demo コードクラウド:https://gitee.com/Tim_Lee_ZhongShan/Token-Demo
1.Tokenとは?
tokenって何?tokenは、コンピュータ認証でよく用いられるトークンと理解できる.サーバとデータ転送を行う前に、認証が行われます.
2.JWTとは?
JWTとは?JWTはJson Web Tokenの略称であり、Tokenの仕様である.暗号化された文字列で、構成部分はA.B.Cです.この文字列は、tokenを記録する暗号化方式、文字列長(A部分)、基本的なユーザ情報、荷重、発行者、期限切れ時間など(B部分)、およびAとBの共通の暗号化部分(C部分)から構成される.
3.TokenとSessionの比較
従来のSessionで明らかになった問題Session:ユーザーは、コンピュータのアイデンティティ認証のたびに、サーバメモリにsessionを格納し、クライアントにクッキーを保存して、次回のユーザー要求時に認証を行う.しかし、これで2つの問題が明らかになった.1つ目の問題は、sessionがサーバに格納されたメモリであり、要求されたユーザ数が増加すると、サーバの圧力が増大することである.2つ目の問題は、複数のサーバが存在し、sessionが現在の1つのサーバにしか格納できない場合、分散開発には適用されません.
CSRF:Sessionはcookieに基づいてユーザ識別を行い,cookieがキャプチャされるとユーザはクロスステーション要求偽造攻撃を受けやすくなり,本稿ではcsrf(cross site request forgery)をしばらく考慮しない.
Tokenの検証メカニズムtokenの検証は、サーバ側にユーザ情報を保持する必要がないため、ユーザ再クライアントが単一のログインを通過した後、複数のサーバにアクセスでき、分散開発に有利である.またtokenは暗号化された文字列で、期限切れを設定でき、模倣されにくい.
tokenを使用すると、クライアントとサービス側のインタラクティブなプロセスは大体次のようになります.
tokenはクッキーに保存してもよいし、リクエストヘッダに保存してもよいし、tokenをリクエストヘッダに配置し、macアドレスとマシン名を携帯することを推奨する.
4. ASP.NET MVCはどのようにjwtを使って単点上陸を実現します
UserStateクラスの定義
namespace LYQ.TokenDemo.Models.Infrastructure
{
public class UserState
{
public string UserName { get; set; }
public string UserID { get; set; }
public int Level { get; set; }
}
}
AppManagerクラスとTokenInfoクラスの定義
public static UserState UserState
{
get
{
HttpContext httpContext = HttpContext.Current;
var cookie = httpContext.Request.Cookies[Key.AuthorizeCookieKey];
var tokenInfo = cookie?.Value ?? "";
//token
var encodeTokenInfo = TokenHelper.GetDecodingToken(tokenInfo);
UserState userState = JsonHelper<UserState>.JsonDeserializeObject(encodeTokenInfo);
return userState;
}
}
public class TokenInfo
{
public TokenInfo()
{
iss = "LYQ";
iat = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
exp = iat + 300;
aud = "";
sub = "LYQ.VIP";
jti = "LYQ." + DateTime.Now.ToString("yyyyMMddhhmmss");
}
public string iss { get; set; }
public double iat { get; set; }
public double exp { get; set; }
public string aud { get; set; }
public double nbf { get; set; }
public string sub { get; set; }
public string jti { get; set; }
}
JsonHelperの定義
public class JsonHelper<T> where T : class
{
public static T JsonDeserializeObject(string json)
{
return JsonConvert.DeserializeObject<T>(json);
}
public static string JsonSerializeObject(object obj)
{
return JsonConvert.SerializeObject(obj);
}
}
ホームコントローラでLoginを定義する方法
[HttpGet]
[LYQ.TokenDemo.Models.CustomAttribute.Authorize(false)]
public ActionResult Login()
{
return View();
}
[HttpPost]
[LYQ.TokenDemo.Models.CustomAttribute.Authorize(false)]
public ActionResult Login(string account, string password)
{
if (account == "Tim" && password == "abc123")
{
var cookie = new HttpCookie(Key.AuthorizeCookieKey, TokenHelper.GenerateToken());
HttpContext.Response.Cookies.Add(cookie);
return Json("y");
}
else
{
var cookie = new HttpCookie(Key.AuthorizeCookieKey, "");
HttpContext.Response.Cookies.Add(cookie);
return Json("n");
}
}
生成tokenはNuGetを使用してJWTをダウンロードする.dll
namespace LYQ.TokenDemo.Models
{
public class TokenHelper
{
//jwt ,
private const string SecretKey = "LYQ.abcqwe123";
public static string GenerateToken()
{
var tokenInfo = new TokenInfo();
var payload = new Dictionary<string, object>
{
{"iss", tokenInfo.iss},
{"iat", tokenInfo.iat},
{"exp", tokenInfo.exp},
{"aud", tokenInfo.aud},
{"sub", tokenInfo.sub},
{"jti", tokenInfo.jti},
{ "userName", "Tim" },
{ "userID", "001" },
{ "level",18}
};
IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
var token = encoder.Encode(payload, SecretKey);
return token;
}
public static string GetDecodingToken(string strToken)
{
try
{
IJsonSerializer serializer = new JsonNetSerializer();
IDateTimeProvider provider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, provider);
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder);
var json = decoder.Decode(strToken, SecretKey, verify: true);
return json;
}
catch (Exception)
{
return "";
}
}
}
}
カスタムアイデンティティ認証ここでは、AuthorizeAttributeをカスタマイズするカスタムアイデンティティ認証モードを採用します.
namespace LYQ.TokenDemo.Models.CustomAttribute
{
public class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
public AuthorizeAttribute(bool _isCheck = true)
{
this.isCheck = _isCheck;
}
private bool isCheck { get; }
public void OnAuthorization(AuthorizationContext filterContext)
{
var httpContext = filterContext.HttpContext;
var actionDescription = filterContext.ActionDescriptor;
if (actionDescription.IsDefined(typeof(AllowAnonymousAttribute), false) ||
actionDescription.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), false)) { return; }
if (!isCheck) return;
if (AppManager.UserState == null)
{
if (httpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult()
{
Data = new { Status = "Fail", Message = "403 Forbin", StatusCode = "403" },
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
else
{
filterContext.Result = new RedirectResult(("/Home/Login"));
}
}
else
{
// , token
var cookie = new HttpCookie(Key.AuthorizeCookieKey, TokenHelper.GenerateToken());
filterContext.HttpContext.Response.Cookies.Add(cookie);
}
}
}
}
HTMLページ
@{
ViewBag.Title = "Login";
}
<link href="~/Content/bootstrap.min.css" rel="stylesheet" />
<h2>This is login page.</h2>
<div class="container">
<form class="box-body" action="/Home/Login" method="post">
<div class="form-group row">
<label class="col-sm-1 col-md-1">Account:</label>
<div class="col-sm-5 col-md-5">
<input type="text" class="form-control" id="account" name="account" />
</div>
</div>
<div class="form-group row">
<label class="col-sm-1 col-md-1">Password:</label>
<div class="col-sm-5 col-md-5">
<input type="password" class="form-control" id="password" name="password" />
</div>
</div>
<div class="form-group row">
<div class="col-sm-1 col-md-1"></div>
<div class="col-sm-5 col-md-5">
<button type="button" class="btn btn-info" onclick="Login();">Login</button>
<button type="reset" class="btn btn-info">Reset</button>
</div>
</div>
<div class="form-group row">
<div class="col-sm-1 col-md-1"></div>
<div class="col-sm-5 col-md-5">
<span>account:Tim; password:abc123</span>
</div>
</div>
</form>
</div>
<script src="~/Scripts/jquery-3.3.1.min.js"></script>
<script src="~/StaticFiles/Frontend/Scripts/Common.js"></script>
<script>
function Login() {
var paras =
{
account: $("#account").val(),
password: $("#password").val()
};
LYQ.sendAjaxRequest({
type: "post",
url: "/Home/Login",
param: paras,
dataType: "json",
callBack: function (result) {
if (result == "y") {
console.log("Login success");
alert("Login success");
window.location = "/";
} else {
console.log("Login fail");
alert("Login fail");
}
}
});
}
</script>
Common.js
!(function (window) {
var functions = {
sendAjaxRequest: function (opts) {
var self = this;
$.ajax({
type: opts.type || "post",
url: opts.url,
data: opts.param || {},
contentType: opts.contentType === null ? true : opts.contentType,
cache: opts.cache === null ? true : opts.cache,
processData: opts.processData === null ? true : opts.processData,
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader(LYQ.getAuthorizationKey(), "");
},
dataType: opts.dataType || "json",
success: function (result) {
if (Object.prototype.toString.call(opts.callBack) === "[object Function]") { // callback function
opts.callBack(result);
} else {
console.log("CallBack is not a function");
}
}
});
},
getRequestHeaderAuthorizationToken: function () {
var document_cookie = document.cookie;
//var reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
//if (document_cookie = document.cookie.match(reg))
// return unescape(arr[2]);
//else
// return null;
console.log(document_cookie);
return document_cookie;
},
getAuthorizationKey: function () {
return 'Authorization';
}
};
window.LYQ = functions;
})(this);
ソースアドレス: