asp.net mvc使用JWT代替session,实现单点登陆

https://blog.csdn.net/weixin_44308006/article/details/90459801?utm_medium=distribute.pc_relevant.none-task-blog-searchFromBaidu-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-searchFromBaidu-1.control

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);

 

源码地址:

posted @ 2020-12-22 23:08  竹林听雨行  阅读(738)  评论(0编辑  收藏  举报