V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ericgui
V2EX  ›  程序员

前后端是怎么验证身份的?

  •  
  •   ericgui · 2018-01-25 07:56:58 +08:00 · 11188 次点击
    这是一个创建于 2498 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我前几天看了一个写 RESTful API 的教程

    前端用 React 后端用 Express JS 和 MongoDB

    后端写了几个 API,然后前端是一个单页应用( SPA ),用 fetch 来请求 api,获取数据。

    看着相当好。

    后来我就觉得不对:这 api 连个验证都没有,岂不是谁都可以来请求这个 api。

    这数据也不是机密数据吧,但如果任何人都可以请求 api,拿岂不是 api 暴露给任何人了,用个爬虫,轻松就把数据下载光了。流量和带宽,该多费钱。。。。。

    所以能否请高人介绍一下,这前后端分离,一般是怎样验证这个“前端”是自己的、官方的,而不是爬虫或者什么其他人写的非官方前端。尤其是写 app 的时候,app 显然算是“前端”或者“客户端”,那么服务端怎么验证这个请求是从自己官方的客户端发出的呢?

    JSON Web Tokens ( jwt )?

    谢谢

    56 条回复    2018-07-31 16:07:25 +08:00
    MrVito
        1
    MrVito  
       2018-01-25 07:59:44 +08:00 via Android
    签名?
    toku
        2
    toku  
       2018-01-25 08:00:06 +08:00 via iPhone
    token ?
    conn4575
        3
    conn4575  
       2018-01-25 08:05:31 +08:00 via Android
    先登录获取合法的 token,然后后面的交互都先验证这个 token,token 每隔一小时左右更新一次,防止失效,参考 jwt 实现
    CEBBCAT
        4
    CEBBCAT  
       2018-01-25 08:06:00 +08:00 via Android
    这,我记得谷歌翻译 Web 版调用的 API 有一个参数是在页面内用 JS 生成的,除此之外好像也没啥验证了
    Mutoo
        5
    Mutoo  
       2018-01-25 08:11:14 +08:00
    对网页前端来说,并没有‘自己官方的客户端’这个说法,你的代码是运行在 UserAgent 上的,通常是浏览器。而爬虫也是一种 UserAgent,那么用户就可以授权 UserAgent 去获取数据。你能做的是对用户鉴权,而不是对 UserAgent。
    ericgui
        6
    ericgui  
    OP
       2018-01-25 08:11:28 +08:00
    @conn4575 谢谢
    ericgui
        7
    ericgui  
    OP
       2018-01-25 08:16:39 +08:00
    @Mutoo 这教程里说的前后端分离,大意是后端就是服务器,expressjs 做的,前端是广义的,可以是一个单页应用 SPA,也可以是一个 app,或者一个什么其他的东西。总之,就是一个广义的前端对后端的 api 发起请求。

    请问“对用户鉴权”是这个意思么:用户登陆,后端 api 接收用户名和密码,如果匹配,就返回一个 token ?在 token 的有效期内,前端可以请求后端的 api,并且每次请求的时候,一起把 token 也发送回服务器,服务器要验证这个 token,如果验证通过,就返回相应的数据,如果验证不通过,就给一个 403 ( No Permission )?

    请问是这个意思么?谢谢
    zhlssg
        8
    zhlssg  
       2018-01-25 08:30:02 +08:00 via iPhone
    Session 也是一样的,不过是教验 cookie 而已,所以接口做不到绝对安全
    RqPS6rhmP3Nyn3Tm
        9
    RqPS6rhmP3Nyn3Tm  
       2018-01-25 08:30:47 +08:00 via iPhone
    我用的是 signature+token
    ericgui
        10
    ericgui  
    OP
       2018-01-25 08:35:36 +08:00
    @BXIA 请教,用的啥 node module ?什么库?
    fujie
        11
    fujie  
       2018-01-25 08:42:02 +08:00
    1. 前端发起 http 请求时有携带 cookie,
    2. 后端拿到此 cookie 对比服务器 session,有登陆则放过此请求,无登录,redirect 到登录页面。
    3. 登录页面,前端登录,后端比对用户名密码,成功则生成唯一标识符,放在 session,并且种入浏览器 cookie。
    另外,合法用户可以拿到自己合法的 cookie,用任何程序构造一个合法的客户端 http 请求。其他用户拿不到这个 cookie,
    因个人过失暴露 cookie 给其他人,属于用户个人的行为,比如你在网吧里登录 QQ,服务端没有办法不允许这样操作。而客户端的人应有安全意识,在公共场所及时清空 cookie,或者停止使用一切 [不随 session 关闭而 cookie 失效] 的应用。
    Mutoo
        12
    Mutoo  
       2018-01-25 09:04:08 +08:00
    @ericgui #7 是的。
    v2chou
        13
    v2chou  
       2018-01-25 09:14:54 +08:00
    我也有这种疑惑 比如在小程序中 接口都是请求的 没有登录的操作 怎么防止别人调用我的接口呢
    LeeSeoung
        14
    LeeSeoung  
       2018-01-25 09:18:07 +08:00
    防不了前端是不是自己的,但是可以针对一些特征返回脏数据,而不是直接拒绝返回,这样可以玩弄下初级爬虫的。。后台 API 加权限验证。
    swordne
        15
    swordne  
       2018-01-25 09:18:57 +08:00   ❤️ 3
    其实在 web 环境下,对用户的鉴权只能是不断提高伪造的成本,不能从根本上解决。
    前端环境太复杂,所有的东西都是暴漏的,所有的鉴权参数获取都是成本问题而非技术问题。
    oott123
        16
    oott123  
       2018-01-25 09:23:09 +08:00 via Android
    不关心前端是不是自己的,甚至鼓励别人来写前端~
    MyDaLin
        17
    MyDaLin  
       2018-01-25 09:28:08 +08:00
    都是高手,我路过的
    geelaw
        18
    geelaw  
       2018-01-25 09:28:31 +08:00
    在客户端由不受信第三方(比如用户)控制的情况下,不存在能够验证请求来自“自己的”前端的方法。

    如果客户端不受不受信第三方的控制——比如你可以确保你的(二进制)代码只被分发商看到,而最终用户无法看到代码——则可以在请求中使用密码学手段保证发送请求的程序“知道”某个秘密——比如一个密钥。

    #7 的回复让我搞不明白楼主想要解决的问题,到底是验证发送请求的程序是自己的程序,还是使用程序的用户是在访问自己可以访问的数据。这是不同的问题。
    RqPS6rhmP3Nyn3Tm
        19
    RqPS6rhmP3Nyn3Tm  
       2018-01-25 09:28:52 +08:00 via iPhone
    @ericgui tornado ……
    miketeam
        20
    miketeam  
       2018-01-25 09:54:51 +08:00 via iPhone   ❤️ 1
    我目前是采用自己建 oauth2.0,登录一次,获取主 token,有效期一年。然后用这个 token 给每个 API 刷临时 token。这是我自己项目尝试。公司的项目用 cookie …
    miketeam
        21
    miketeam  
       2018-01-25 10:05:12 +08:00 via iPhone
    还有每个 API 有个时间请求的限制,1 秒
    houn1995
        22
    houn1995  
       2018-01-25 10:12:47 +08:00
    接口根据需求加认证权限,比如登陆之后才能用的,可以在登陆之后,返回前端一个 token,每次前端在请求的时候吧 token 放到 header 的验证中,这样后台判断这个 token 是否正确从而确定是否返回接口数据,通常 token 也会有个有效时间。
    qiutc
        23
    qiutc  
       2018-01-25 10:15:56 +08:00
    服务端 session 生成 sid 放在 cookie 中,登录时在返回的 Header Set-Cookie,之后的请求都会带上这个 cookie,服务端拿 cookie 对应 session 来验证。
    edisonchen
        24
    edisonchen  
       2018-01-25 10:41:06 +08:00
    对于这个问题,假设服务端用 JWT 等方式,我用账号密码登陆后可以获取到这个返回 token,然后我自己用我自己的这个 token 拿爬虫来调接口,这种方式算不算上文提到的:(---这个请求是从自己官方的客户端发出的呢?--)
    yifeng1212
        25
    yifeng1212  
       2018-01-25 10:43:32 +08:00
    总结楼上几位的评论:
    大体分为两种方法,

    1.API 鉴权,可以使用 cookie,或者登录 token。如果是开发 API,可以参考微信或微博的开发者平台 API,他们所有的接口都要通过 token 来鉴权

    2.API 访问限制,可以限制同一个 IP 请求次数,或者限制请求频率(每天限额,或者单位时间请求数有上限)

    不能做到绝对的安全,但能提高被不怀好意利用的成本。
    xkeyideal
        26
    xkeyideal  
       2018-01-25 11:03:33 +08:00
    说一下,我的做法:
    1、前端只做 UI 的展示层,登录,权限校验,均交给后端完成
    2、登录接入公司统一的登录,类似 OAuth 这种,贵司不可能没有吧?
    3、每个 api 都有相应的路由权限控制,权限控制系统由后端完成,类似做个 middleware 或拦截器
    4、访问 api 必须带着当前登录用户的 token,用于校验用户信息,并且查询该用户能够操作的数据,token 是一天变化一次,并没有使用 JWT 这类的开源实现,原因就是太重
    5、每行数据都只能有该数据权限的人才能访问和操作

    总结:前端是无设防的,不可以信任; 全部的校验都由后端完成
    NEETLEE
        27
    NEETLEE  
       2018-01-25 11:08:47 +08:00
    我司目前的项目就是用 jwt 做的验证,jwt 也有一定的局限,不过仅仅是避免被爬应该可以胜任的
    AlisaDestiny
        28
    AlisaDestiny  
       2018-01-25 11:10:40 +08:00
    添加拦截器。为每个请求都做验证。通过验证的请求才交路由处理器。
    wujunchuan2008
        29
    wujunchuan2008  
       2018-01-25 11:29:49 +08:00
    前端做验证只是为了用户体验,比如控制按钮的显示隐藏,单页应用的路由跳转等等。
    后端才是最终的保障。
    timwei
        30
    timwei  
       2018-01-25 11:43:02 +08:00
    @ericgui

    先不讨论爬虫,浏览器 XHR 有同源策略,除非你有配置 CORS 相关的 Header,其他人在浏览器中无法直接调用你的 API

    https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

    爬虫的话,即使你做了相关的验证还是很好破

    以前用 captcha 挺有用的,不过自从 Google Vision 之类的 OCR 服务方便使用后也不靠谱了

    辨识率挺高的
    hlwjia
        31
    hlwjia  
       2018-01-25 11:48:03 +08:00 via iPhone
    首先,要了解传统方式(你一直在用的)里,后端是前端发来的请求的怎么验证身份的?

    如果这个都不清楚,就还是先补补基础
    StevenTong
        32
    StevenTong  
       2018-01-25 11:49:31 +08:00
    jwt token
    hlwjia
        33
    hlwjia  
       2018-01-25 11:49:37 +08:00 via iPhone
    在身份验证上其实没有本质区别
    ericgui
        34
    ericgui  
    OP
       2018-01-25 13:45:49 +08:00 via Android
    @xkeyideal 目前是 password 来做校验。所以我也有点晕。有了 password,还要用 jwt 么?
    xkeyideal
        35
    xkeyideal  
       2018-01-25 14:11:32 +08:00
    @ericgui 我们的做法是使用 token,password 那是登录做的事情,如果接入了统一登录,系统是拿不到用户密码的,至于 jwt 我没用过,太重
    alinwu05
        36
    alinwu05  
       2018-01-25 14:28:51 +08:00
    API 没有权限控制,如果跳一跳被随便 Post 数据一样,必须有验证啊!只是好多教程是前端写的,不太熟悉后端,所以都忽略了这一块。之前我写过一篇科普文章,看完你就明白了。https://my.oschina.net/u/248080/blog/875243
    wizardoz
        37
    wizardoz  
       2018-01-25 14:47:04 +08:00
    楼主可以区别一下 Authentication 和 Authorization,一个是 password 干的事情一个是 token 干的事情,对于一个完整的业务来说,两者都是必须的,只是具体如何实现就很灵活了。比如 OAuth2 的话 Authentication 和 Authorization 就可能在不同的地方进行。
    pandaaa
        38
    pandaaa  
       2018-01-25 14:52:33 +08:00
    楼主可以 google 一下 Anti CSRF Token
    MaxTan
        39
    MaxTan  
       2018-01-25 15:11:43 +08:00
    v2 月经贴
    CoderGeek
        40
    CoderGeek  
       2018-01-25 15:17:25 +08:00 via iPhone
    jwt 签名+自定义 token 先过了签名验证再验证 token
    Enochyun
        41
    Enochyun  
       2018-01-25 15:20:21 +08:00
    oauth2 是干嘛滴?
    POPOEVER
        42
    POPOEVER  
       2018-01-25 15:21:59 +08:00
    @v2chou wx.login 的时候把用户 OpenID 解出来自己存库啊,小程序里校验 api 只是对微信侧的验证支持,你自己 request api 那边的安全只能自己写 user 表后用 OpenID 来比对校验然后放行
    ivydom
        43
    ivydom  
       2018-01-25 21:09:01 +08:00
    最好正好在做通用认证系统,简单说一下:

    具体可以看这里: https://github.com/Authing/authing

    ### 认证流程

    ![auth_uml]( http://usercontents.authing.cn/white_paper/authing_auth_uml.png)

    认证通过后,后端会生成基于 JWT 规范的 Token。客户端将 Token 放到 HTTP 协议中的```Authorzation```头中并加上标注```Bearer```即可进行登录验证。

    流程如下:

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

    #### JWT

    JWT 是由三段信息构成的,将这三段信息文本用.链接一起就构成了 JWT 字符串。就像这样:

    ```
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    ```

    ##### JWT 的构成
    第一部分我们称它为头部( header),第二部分我们称其为载荷( payload, 类似于飞机上承载的物品),第三部分是签证( signature).

    ###### header
    jwt 的头部承载两部分信息:

    - 声明类型,这里是 jwt
    - 声明加密的算法 通常直接使用 HMAC SHA256

    完整的头部就像下面这样的 JSON:

    ``` javascript
    {
    'typ': 'JWT',
    'alg': 'HS256'
    }
    ```

    然后将头部进行 base64 加密(该加密是可以对称解密的),构成了第一部分.
    ```
    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
    playload
    ```

    载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

    - 标准中注册的声明
    - 公共的声明
    - 私有的声明

    标准中注册的声明 (建议但不强制使用) :

    - ```iss```: jwt 签发者
    - ```sub```: jwt 所面向的用户
    - ```aud```: 接收 jwt 的一方
    - ```exp```: jwt 的过期时间,这个过期时间必须要大于签发时间
    - ```nbf```: 定义在什么时间之前,该 jwt 都是不可用的.
    - ```iat```: jwt 的签发时间
    - ```jti```: jwt 的唯一身份标识,主要用来作为一次性 token,从而回避重放攻击。


    **公共的声明 :**

    公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

    ** 私有的声明 :**

    私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为 base64 是对称解密的,意味着该部分信息可以归类为明文信息。

    定义一个 payload:

    ``` javascript
    {
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true
    }
    ```

    然后将其进行 base64 加密,得到 Jwt 的第二部分。

    ```
    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
    ```

    **signature**

    jwt 的第三部分是一个签证信息,这个签证信息由三部分组成:

    - header (base64 后的)
    - payload (base64 后的)
    - secret

    这个部分需要 base64 加密后的 header 和 base64 加密后的 payload 使用.连接组成的字符串,然后通过 header 中声明的加密方式进行加盐 secret 组合加密,然后就构成了 jwt 的第三部分。

    ``` javascript
    var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

    var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    ```
    将这三部分用.连接成一个完整的字符串,构成了最终的 jwt:

    ```
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    ```

    注意:secret 是保存在服务器端的,jwt 的签发生成也是在服务器端的,secret 就是用来进行 jwt 的签发和 jwt 的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个 secret,那就意味着客户端是可以自我签发 jwt 了。
    Zzde
        44
    Zzde  
       2018-01-25 21:13:44 +08:00
    我的做法(小程序没有登陆)

    和前端协商一个固定的 Token
    每次请求以
    * sign(md5(token+time)
    * time
    为参数验证身份

    time 有效时间为 10s (其实可以更短)
    ai277014717
        45
    ai277014717  
       2018-01-26 10:55:42 +08:00
    看过一个源码,每次调用接口会发送两个请求,第一鉴定 session,第二个头里面包含有效的 session。session 过期逻辑可以自己设置,访问量,ip,时间,推出登陆之类的。
    RorschachZZZ
        46
    RorschachZZZ  
       2018-01-26 12:09:06 +08:00
    1 登录发 token。2 前端每次请求带着 token。3 后端在逻辑前面验证 token。
    ericgui
        47
    ericgui  
    OP
       2018-01-27 00:26:36 +08:00
    @ai277014717 能分享一下源码么? github url ?
    Lullaby
        48
    Lullaby  
       2018-01-27 13:15:16 +08:00
    令牌桶 & IP 频次记录
    ai277014717
        49
    ai277014717  
       2018-01-27 15:43:23 +08:00
    @ericgui 我看的是 parse-server-ios-sdk https://github.com/parse-community/Parse-SDK-iOS-OSX
    还有 js 和安卓 php 等等的版本。
    ericgui
        50
    ericgui  
    OP
       2018-01-28 11:25:00 +08:00
    @ai277014717 感谢分享,这个 Parse 是干什么的?看了半天没看明白啥意思
    sothx
        51
    sothx  
       2018-01-29 05:52:48 +08:00 via Android
    目前很多开放平台的的权限验证基本都是 token,也可以用 session ( cookie )
    注意下安全方面的设置就差不多了。
    前端也可以做权限鉴权,根据用户的权限,加载对应权限的路由,虽然是不可信的,但是也在一定程度上提高了安全。
    ai277014717
        52
    ai277014717  
       2018-01-29 09:39:31 +08:00
    @ericgui leancloud 就是模仿 parse 的是 Facebook 开源的一套产品,主要是把 HTTP 部分封装,客户端再也不用调用接口了,还有集成推送,云编码等功能。从 server 到客户端 sdk,很有学习的价值。
    YMB
        53
    YMB  
       2018-02-03 16:05:11 +08:00   ❤️ 1
    我们公司的这部分服务端就是我编码的,采用的模式是较为普遍的 Access Token,参考了微信的部分机制,过段时间有时间准备调研更优的实现,准备做一个更加灵活的架构。有一些文章楼主可以参考下: https://mengkang.net/625.html
    ericgui
        54
    ericgui  
    OP
       2018-02-04 02:03:22 +08:00
    @YMB 感谢!
    mingyun
        55
    mingyun  
       2018-06-09 19:58:31 +08:00
    @ivydom 很详细,star 了
    mingszu
        56
    mingszu  
       2018-07-31 16:07:25 +08:00
    说说我的看法:

    首先 restful 也是基于 HTTP 协议,而 HTTP 事务本来就是无状态的,也就是每一次请求与响应都是相互独立的,而身份认证那些都是慢慢扩展出来的

    认证方法
    (一) HTTP 请求头例如 Authorization,用于存储用户名与密码来实现用户验证,就算是 restful 也是 http,你也可以每次都在请求头上带上你的用户名密码来验证,但是每次都传输用户名密码太不安全了
    (二) session-cookie 模式:用户第一次登陆验证通过服务器将用户信息存储于 session 中,然后将 session 存储在本地服务器中,将 sessionid 发给客户端,客户端将 sessionid 存于 cookie,以后每次请求都带上 cookie,服务器根据 cookie 中的 sessionid 去查询 session,从而获取用户信息,就这样实现了身份认证
    (三)基于 token 认证方式:用户第一次登陆验证通过时服务器将用户信息等生成 token,然后返回给客户端,客户端下次访问时带上 token,服务器解析 token 就可以获取用户信息;
    token 与 session 相比:token 存储在客户端,session 存储在服务器,因此 token 适用手机 app 与服务器通信而 session 不适合
    (四) oauth2 第三方授权
    (五)签名:防篡改防重放,客户端将每一个请求参数与值即 key-value 对排序(末尾加上 appscret )排序然后生成签名然后发给给服务端,服务端验证自己生成的签名与客户端的是否相同(根据客户端发来的 appkey 查找对应的),相同即请求未被篡改,而根据时间戳就可以限制该客户端访问次数,从而避免流量丢失
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5643 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 46ms · UTC 01:30 · PVG 09:30 · LAX 17:30 · JFK 20:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.