zoukankan      html  css  js  c++  java
  • ASP.NET MVC使用JWT代替session,实现单点登陆

    ASP.NET MVC使用JWT代替session,实现单点登陆

    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,在客户端会保存一个cookie,以便在下次用户请求时进行身份核验。但是这样就暴露了两个问题。第一个问题是,session是存储到服务器的内存中,当请求的用户数量增加时,会加重服务器的压力。第二个问题是,若是有多台服务器,而session只能存储到当前的某一台服务器中,这就不适用于分布式开发。

    CSRF: Session是基于cookie来进行用户识别的,如果cookie被截获,用户就很容易受到跨站请求伪造攻击,本文暂时不考虑csrf(cross site request forgery)。

    Token的验证机制
    token的验证不需要在服务器端保留任何的用户信息,因此,当用户再客户端通过单点登陆后,可以访问多台服务器,利于分布式开发。而且token的是一串加密后的字符串,可以设置过期日期,不容易被仿造。

    使用token,客户端和服务端的交互流程大致是如下:

    1. 用户使用用户名密码来请求服务器
    2. 服务器进行验证用户的信息
    3. 服务器通过验证发送给用户一个token
    4. 客户端存储token,并在每次请求时附送上这个token值
    5. 服务端验证token值,并返回数据

    token可以存放在cookie中,也可以保存在请求头中,建议将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);
            }
        }

    在Home控制器中定义一个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);

    源码地址:

     
  • 相关阅读:
    随机数
    ASP .NET下的301重定向如何做
    网站外部链接建设方案
    解析ASP.NET WebForm和Mvc开发的区别
    委托、匿名委托和lambda表达式
    图片垂直居中
    jquery函数写法
    [转]函数方法常用的动词
    CSS Hack
    富文本编辑器
  • 原文地址:https://www.cnblogs.com/netlock/p/13191659.html
Copyright © 2011-2022 走看看