关于token和refresh token

0x01. refresh token 的意义

传统的认证方式一般采用cookie/session来实现,这是我们的出发点。

1.为什么选用token而不选用cookie/session?
本质上token和cookie/session都是字符串,然而token是自带加密算法和用户信息(比如用户id),;而cookie本身不包含用户信息,它指向的是服务器上用户的 session,而由session保存用户信息。这点差别,决定token可以很容易的跨服务器,只要不同服务器实现相同解密算法即可;而cookie/session是存在于某一台服务器上,

要实现跨服务器比较困难,这是任何基于cookie/session应用天生的短板。

2.token需要过期时间吗?
token即是获取受保护资源的凭证,当然必须有过期时间。否则一次登录便可永久使用,认证功能就失去了其意义。非但必须有个过期时间,而且过期时间还不能太长,

参考各个主流网站的token过期时间,一般不超过1h.

3.token过期该怎么办?
token过期,就要重新获取。那么重新获取有两种方式,一是重复第一次获取token的过程(比如登录,扫描授权等),这样做的缺点是用户体验不好,每一小时强制登录一次几乎是无法忍受的。那么还剩第二种方法,那就是主动去刷新token. 主动刷新token的凭证是refresh token,也是加密字符串,并且和token是相关联的。相比获取各种资源的token,refresh token的作用仅仅是获取新的token,因此其作用和安全性要求都大为降低,所以其过期时间也可以设置得长一些。

4.refresh token需要过期时间么?
客户端需要保存token和refresh token,以便下一次访问能继续。如果客户端是浏览器,那么两个token都需要设置过期时间;但是可以设置得长一点,可以以天为单位(例如7天、15天);如果客户端是一个服务器,那么refresh token可以永久有效,直到下一次登录,refresh token本身被更新为止。

以上几个问题是层层递进的,虽说如此,也无法从逻辑上/理论上保证认证系统的绝对安全。但是,我觉得任何一种认证方式,都不能做到逻辑上的绝对安全和毫无漏洞。但是如果给攻击者造成了足够的麻烦,使其破解成本大大提升,那么我们就认为认证系统足够安全了。认证功能最后的落地实现,总是和现实想妥协的结果。

0x02.  refresh token 刷新机制

 access token 是客户端访问资源服务器的令牌。拥有这个令牌代表着得到用户的授权。然而,这个授权应该是临时的,有一定有效期。这是因为,access token 在使用的过程中可能会泄露。给 access token 限定一个较短的有效期可以降低因 access token 泄露而带来的风险。

  然而引入了有效期之后,客户端使用起来就不那么方便了。每当 access token 过期,客户端就必须重新向用户索要授权。这样用户可能每隔几天,甚至每天都需要进行授权操作。这是一件非常影响用户体验的事情。希望有一种方法,可以避免这种情况。

  于是 Oauth2.0 引入了 refresh token 机制。refresh token 的作用是用来刷新 access token。鉴权服务器提供一个刷新接口,例如:

  http://xxx.xxx.com/refresh?refreshtoken=&client_id=

  传入 refresh token 和 client_id,鉴权服务器验证通过后,返回一个新的 access token。为了安全,Oauth2.0 引入了两个措施:

  1,Oauth2.0 要求,refresh token 一定是保存在客户端的服务器上的,而绝不能存放在狭义的客户端(例如移动 app、PC端软件) 上。调用 refresh 接口的时候,一定是从服务器到服务器的访问;

  2,Oauth2.0 引入了 client_secret 机制。即每一个 client_id 都对应一个 client_secret。这个 client_secret 会在客户端申请 client_id 时,随 client_id 一起分配给客户端。客户端必须把 client_secret 妥善保管在服务器上,决不能泄露。刷新 access token 时,需要验证这个 client_secret。

  于是,实际上的刷新接口应该是类似这样的:

  http://xxx.xxx.com/refresh?refreshtoken=&client_id=&client_secret=

  以上就是 refresh token 机制。refresh token 的有效期非常长,会在用户授权时,随 access token 一起重定向到回调 url,传递给客户端。

 

0x03. Token refresh的实现

实现原理:

在access_token里加入refresh_token标识,给access_token设置短时间的期限(例如一天),给refresh_token设置长时间的期限(例如七天)。当活动用户(拥有access_token)发起request时,在权限验证里,对于requeset的header包含的access_token、refresh_token分别进行验证:

1、access_token没过期,即通过权限验证;

2、access_token过期,refresh_token没过期,则返回权限验证失败,并在返回的response的header中加入标识状态的key,在request方法的catch中通过webException来获取标识的key,获取新的token(包含新的access_token和refresh_token),再次发起请求,并返回给客户端请求结果以及新的token,再在客户端更新公共静态token模型;

3、access_token过期,refresh_token过期即权限验证失败。

 

下面展示一下关键代码:

一、登录生成token的时候加入refresh标识

复制代码
 public TOKEN GetToken(string username, string password)
        {
            TOKEN token = new TOKEN();
            U_USER model = new BLLU_USER().Token(username, password);
            if (model != null)
            {
                string pwd = new SDDMD().MD5_jie(model.USERPWD);
                string md5 = new SDDMD().MD5_jia(model.USERID +"&"+ DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") +"&"+ pwd+"&refresh"+ DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                token.access_token = md5;
                token.token_type = "Authorization";
                token.expires_in = DateTime.Now.ToString("yyyyMMddHHmmss");
                //token.refresh_token = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "&refresh";
            }
            return token;
        }
复制代码
二、在权限验证环节,对于access_token、refresh_token设置不同时间的期限。再根据判断结果返回状态。

复制代码
        /// <summary>
        /// 校验token是否有效
        /// </summary>
        /// <param name="encryptTicket">用户提交的Token值</param>
        /// <returns></returns>
        private int ValidateTicket(string encryptTicket)
        {
            try
            {
                var strTicket = new DBUtility.SDDMD().MD5_jie(encryptTicket);
                string[] TicketMsg = strTicket.Split('&');
                if (TicketMsg.Length == 4)
                {
                    string username = TicketMsg[0];//用户名
                    string passwrod = TicketMsg[2];//密码
                    U_USER model = new BLL.BLLU_USER().Token(username, passwrod);//登陆验证
                    if (model != null)
                    {
                        _userModel = model;
                        //定义Access_Token有效期为1天,精确到秒
                        DateTime dt = Convert.ToDateTime(TicketMsg[1]).AddDays(1);
                        //定义Refresh_Token有效期为1天,精确到秒
                        DateTime dtNew= Convert.ToDateTime(TicketMsg[3].Substring(7)).AddDays(7);//
                        if (dt > DateTime.Now)
                            return 1; //1:access_token没过期
                        else
                        {
                            if (dtNew > DateTime.Now)
                                return 2; //2:access_token过期,refresh_token没过期
                            else
                                return 3; //3:access_token过期,refresh_token过期
                        } 
                    }
                    else
                        return 3;
                }
                else
                    return 3;
            }
            catch { return 3; }
        }
复制代码
三、根据反馈的状态执行不同的方法,“2”(access_token过期,refresh_token没过期)状态下,给返回失败的response的header中加入识别的key值。

复制代码
        /// <summary>
        /// 定义Access_token验证过期但Reflesh_token有效返回请求刷新token的信息
        /// </summary>
        /// <param name="actionContext"></param>
        protected void RefreshToken(HttpActionContext actionContext)
        {
            var content = "refreshtoken";
            base.HandleUnauthorizedRequest(actionContext);
            var response = actionContext.Response;
            response.StatusCode = HttpStatusCode.Forbidden;
            response.Content = new StringContent(content, Encoding.UTF8, "application/json");
            response.Headers.Add("token", "refresh");//加入识别的key值
        }
复制代码
四、request方法中通过Catch捕获webException对象获取Key值,并获取新的token(包含新的access_token和refresh_token),再次发起请求,并返回给客户端请求结果以及新的token。

复制代码
        /// <summary>
        /// Get head中携带token发送请求
        /// </summary>
        /// <param name="url">WebAPI访问路径</param>
        /// <param name="tokentype">token验证方式 《Authorization》</param>
        /// <param name="tokenvalue">token 《"Bearer " + Token》</param>
        /// <returns></returns>
        public string GetRequest(string url, string tokentype, string tokenvalue)
        {
            try
            {
                string responseStr = string.Empty;
                WebRequest request = WebRequest.Create(http + url);
                request.Timeout = 60000;
                request.Method = "Get";
                request.Headers.Add(tokentype, tokenvalue);
                var response = request.GetResponse();
                Stream ReceiveStream = response.GetResponseStream();
                using (StreamReader stream = new StreamReader(ReceiveStream, Encoding.UTF8))
                {
                    responseStr = stream.ReadToEnd();
                }
                return responseStr;
            }
            catch (WebException e)
            {
                using (WebResponse response = e.Response)
                {
                    string result=response.Headers.Get("token");//获取识别的KEY
                    if (result == "refresh")//验证是否为状态“2”方法所加的key值
                    {
                        var strTicket = new SDDMD().MD5_jie(tokenvalue.Substring(6));
                        string[] TicketMsg = strTicket.Split('&');
                        string username = TicketMsg[0];//用户名
                        string passwrod = TicketMsg[2];//密码
                        string responseStr = GetRequest("CYUMS/Token/" + username + "/" + passwrod);//获取新的token
                        TOKEN tokenmodel = JsonConvert.DeserializeObject<TOKEN>(responseStr);
                        string secondResponseStr= GetRequest(url, tokenmodel.token_type, "Haval " + tokenmodel.access_token);//再次发起请求
                        return secondResponseStr + "|" + responseStr;//返回请求结果以及新的token
                    }
                    else
                        throw e;
                }
            }
        }
复制代码
五、客户端识别token是否更新,如果更新,就更新公共静态token模型中的access_token的值;

复制代码
            string user = new HttpHelper().GetRequest(getUserModel, tokenmodel.token_type, "Haval " + tokenmodel.access_token);
            string[] str = user.Split('|');
            if (str.Length == 2)//判断是否有token更新
            {
                TOKEN tokens = JsonConvert.DeserializeObject<TOKEN>(str[1]);
                tokenmodel.access_token = tokens.access_token;
            }
            U_USER users = JsonConvert.DeserializeObject<U_USER>(str[0]);
            //business code...

 

参考文献:

https://www.cnblogs.com/eedc/p/9088421.html

https://www.cnblogs.com/blowing00/p/4524132.html

http://www.cnblogs.com/minirice/p/9232355.html

 

 

  • 7
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值