API:接口安全(权限)验证、JWT

下午研究了一下JWT,又回顾了了下工作中碰到的一些接口安全性验证,写个小结吧:

 

一、后端对后端

1、IP过滤

因为很多都是内网项目,直接简单粗暴配置下可访问的IP地址也凑合着够用(局域网没高手潜伏着搞破坏就好。。。),具体就是拦截器里

通过Web.Config参数判断是否要验证IP或账号密码,直接写死在Web.Config里,各地上线时去配置,基本是配置了内网IP段即可

public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
    bool isVerifyIP = Utiliy.Tools.Text.ToBool(ConfigurationManager.AppSettings["IsVerifyIP"]);
    bool isVerifyUser = Utiliy.Tools.Text.ToBool(ConfigurationManager.AppSettings["IsVerifyUser"]);

    if (isVerifyIP == false && isVerifyUser == false)
    {
        return;
    }

    if (isVerifyIP == true)
    {
        string clientIP = WebApiConfig.GetClientIp(actionContext.Request);
        if (Utiliy.Tools.Text.CheckInChar(ConfigurationManager.AppSettings["AssignIP"], clientIP) == false
            && Utiliy.Tools.Text.VerifyIsLanIp(clientIP) == false)
        {
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }
    }

    if (isVerifyUser == true)
    {
        if (actionContext.Request.Headers.Authorization != null)
        {
            string userInfo = Encoding.Default.GetString(Convert.FromBase64String(actionContext.Request.Headers.Authorization.Parameter));
            if (string.Equals(userInfo, string.Format("{0}:{1}", "admin", "XXX")))
            {
                IsAuthorized(actionContext);
            }
            else
            {
                HandleUnauthorizedRequest(actionContext);
            }
        }
        else
        {
            HandleUnauthorizedRequest(actionContext);
        }
    }
}

 

2、直接在URL里获取登录账号拼个&UseName=XX,然后根据这个用户名看是否有权限

呃这个嘛,完全没安全性(传个admin咋整。。。),只是判断内部管理的权限,配合内网IP限制,又是后端对后端的,基本不会暴露出去,凑合吧。

 

3、参数拼上MD5同时传进来,比如有个public GetXX(string a, int b, string md5Key),那就是拿 a + b.ToString(),通过公司公共MD5加密(里面封装好了Salt),对方拿到后也同样把 a + b.ToString()进行同样公共方法的加密,看双方md5Key是否一致。

这个安全性倒是有一定保证,但有好几个衍生问题:

  a.参数顺序不能乱,只能按a、b这样,如果反过来,md5也不一样了

  b.如果不是这种拼简单有限的几个参数,是用实体(实体可能定义了冗余的十几个字段)接收的话,要封装公共方法进行反射循环取值,排序不能乱。按理也是可以,但没试过。

  c.md5也可以反身暴力破解,网上都有免费直接用的,但拼上Salt后应该还好。此方案里不好加时间戳。或者加个分钟的时间戳,后台还要弄个时间段循环比对?比如取前3分钟后3分钟,以分钟字符串去md5,分别比对?效率太低了,不过有些小接口也凑合用就是了。

  d.最大问题,这个封装的md5加密公共方法,是公司内部定义的,和其它合作伙伴或公司等对接,双方无法达成一致的加密,主要是Salt不能共用,自家md5的盐总不能给别人吧。

虽说有各种问题,但因这个简单有效,所以很多有一定安全要求的内部简单接口,还是会采用这个方案。

 

4、直接传个约定好的字符串,该字符串可以用GUID生成,双方都存着,固定附在参数后面进来,固定验证

和前面参数拼MD5类似,但双方代码更简单,也可以和其它公司对接,聊胜于无。实际也基本没安全保证,一旦被抓包或怎么泄露了,所有接口都能调用了。。。但因为是后端对后端,又多是内网项目,所以有时也会这样用,别人只知道接口几个业务参数,比如单号、名称什么的,这个随机串,没有专业点的抓包什么的,是不会知道。也是寄希望于内网没高手搞破坏吧(有中级IT水平就可能会泄露了。。。)。还是因其简单粗暴,项目试运行期间也偶尔会这样约定,后期再改进(实际一忙可能就一直这样用下去了。。。)

 

5、公钥私钥,比较正规,但双方开发都较麻烦,一般用于和其它公司对接(很多是其它公司约定好这种方案,我们沿用)。

 

二、前端对后端

因前后端分离等,有很多专为前端服务的接口(如手机移动端),回忆下日常工作中都是怎么验证的

因手机端基本都是互联网了,基本不存在内网IP判断了,所以安全性要比后端对后端考虑多些

1、微信入口(企业号、公众号、订阅号、小程序等)

先有一层后端,先验证好微信的授权,那一层再调API相当于后端对后端了,具体就用上面几种后端对后端方案了

 

2、普通移动端H5页面,手机号注册/验证后,调API取数据

也有用双方md5后传加密串验证的

前端加密有个缺点,就是不管用什么加密方式,因为JS最终都是暴露出去的,虽然经过混淆后会较难破译些,也不保险。

项目有采用这种把数据用JS的md5插件加密,又用了一些数组什么的组成盐,各种混淆,看着基本也没问题,数据又不是什么特别敏感或涉及金融的,所以这种方案也不时有用。

后来同事还改进过,可传时间戳,后端循环验证,3分钟还是5分钟内有效

 

3、先登录,不管是账号密码,还是扫码,还是短信验证码,总之登录完会返回一个token,请求时把这个token附上就可以。每次后台会判断,如果快过期了,会生成个新的token,返回给前端,下次前端就要用新的token访问了。只要若干天内有登录过就会重新刷新,不影响正常操作的流畅性。

其实oAuth2、JWT这些基于Token(AccessToken、RefreshToken)的,基本也是这套路了,只不过封装得更好。

 

4、JWT

本想着重写下JWT的心得的,但其实是差不多的:

JWT 标准的 Token 有三个部分:
   header(头部)
   payload(数据)
   signature(签名)
   中间用点分隔开,并且都会使用 Base64 编码,所以真正的 Token 看起来像这样:
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

就是把单纯的验证,能多传一些其它参数,而且更正规

header只是声明是什么样的加密方式

payload是客户端自已想传的一些额外参数,通常是账号相关信息

signature就是把上面两个的base64进行加密(用的算法就是header中声明的那个)

最后生成的token就是把这三个依次拼起来,分隔符是 . (英文的点)

客户端使用用户名密码进行认证
服务端生成有效时间较短的 Access Token(例如 10 分钟),和有效时间较长的 Refresh Token(例如 7 天)
客户端访问需要认证的接口时,携带 Access Token
如果 Access Token 没有过期,服务端鉴权后返回给客户端需要的数据
如果携带 Access Token 访问需要认证的接口时鉴权失败(例如返回 401 错误),则客户端使用 Refresh Token 向刷新接口申请新的 Access Token
如果 Refresh Token 没有过期,服务端向客户端下发新的 Access Token
客户端使用新的 Access Token 访问需要认证的接口

作者:simpleapples
链接:https://www.jianshu.com/p/25ab2f456904
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

前后端要注意封装和判断下

后端:1、验证AccessToken,如过期,则用RefreshToken去获取新的AccessToken

  2、每次都判断下RefreshToken,如果快过期了,则刷新下RefreshToken,返回新的RefreshToken、AccessToken。每次去数据库取有效期不值得,可用Redis缓存着  

  3、过期不过期干脆由前端判断。一种是前端自已判断快过期了,先调下刷新token的接口。另种是前端把过期时间通过payload传进来,因为每次返回实体数据、token时,也可以顺便返回token过期时间。

  不用担心伪造过期时间,毕竟前端提醒过期只是为了方便,即使伪造未过期,不刷新token,到时也是用不了的,自已骗自已

 

前端:1、调接口的公共方法里封装好,如果401授权失败等,要重新去调token接口获取新的token,并重新请求数据,这个要做到用户无感知(最多因为多调了两次接口显得慢了一些,但整体流程未中断)

2、返回的Token,要缓存在cookie或localstorage等,每个请求都要用的,如果要在payload里多传参数,也可放缓存里一并打包

 

JWT有个小问题,就是后端要禁用客户时,会删除RefreshToken,以后就无法更新AccessToken了,但在过期间AccessToken仍然可用。可以搭配着其它验证(比如Redis缓存个黑名单,禁用时把账号加进去,每次都验证下黑名单,因为黑名单不多,所以效率损失可以忽略不计)

 

posted @ 2019-05-26 21:02  文刂亻右景彡  阅读(4688)  评论(1编辑  收藏  举报