REST风格详解(很多其他人经验总结加上我自己的归纳到一起了,从推出到使用)

一.REST演化过程小故事

故事来源于《大雄和静香的故事》,https://zhuanlan.zhihu.com/p/30396391?group_id=937244108725641216

老婆经常喜欢翻看我订阅的技术杂志,她总能从她的视角提出很多有趣的问题。

一个悠闲的周日下午,她午觉醒来,又习惯性的抓起这个月的杂志,饶有兴趣地看了起来。

果不其然,看着看着,她又对我发难了,“Restful是什么呀,老公?是restaurant的形容词吗,突然就觉得好饿了啊…”

作为一个合格的程序员,我一直把能够将一项技术讲给老婆听,并且能给她讲懂,作为我已经掌握了这项技术的标准。

如果我直接回答说,“REST就是Representational State Transfer的缩写呀,翻译为中文就是‘表述性状态转移’”,那她今晚肯定得罚我跪键盘。我必须找个合适的机会,把Restful的来龙去脉给她形象的描述一遍。

“走,咱们去楼下咖啡厅吃个下午茶吧”,我对老婆说。

“一个芝士蛋糕,一杯拿铁,两条吸管,谢谢”,我对前台的服务员说,然后我们找了个角落坐了下来。

Level 0 - 面向前台

“刚才我们向前台点了一杯拿铁,这个过程可以用这段文字来描述”,说着,我在纸上写下了这段JSON,虽然她不知道什么叫JSON,但理解这段文字对于英语专业8级的她,实在再简单不过。


    {
     "addOrder": {
        "orderName": "latte"
      }
    }

“我们通过这段文字,告诉前台,新增一笔订单,订单是一杯拿铁咖啡”,接着,前台给我们返回这么一串回复:


    {
    "orderId": "123456"
    }

“订单ID?还是订单编号?”

“恩恩,就是订单编号”

“那我们就等着前台喊‘订单123456的客户可以取餐了’,然后就可以开吃了!”

“哈哈,你真聪明,不过,在这之前,假设我们有一张会员卡,我们想查询一下这张会员卡的余额,这时候,要向前台发起另一个询问”,我继续在纸上写着:


	{
	    "queryBalance": {
	        "cardId": "886333"
	    }
	}

“查询卡号为886333的卡的余额?”

“真棒!接着,查询的结果返回来了”

	{
	    "balance": "0"
	}

“切,没钱…”

“哈哈,没钱,现在我们要跟前台说,这杯咖啡不要了”,我在纸上写到:


	{
	    "deleteOrder": {
	        "orderId": "123456"
	    }
	}

“哼,这就把订单取消啦?”

Level 1 - 面向资源

“现在这家咖啡店越做越大,来喝咖啡的人越来越多,单靠前台显然是不行的,店主决定进行分工,每个资源都有专人负责,我们可以直接面向资源操作。”

"面向资源?”

“是的,比如还是下单,请求的内容不变,但是我们多了一条消息”,我在纸上画出这次的模型:


	/orders
	{
	    "addOrder": {
	        "orderName": "latte"
	    }
	}

“多了一个斜杠和orders?这是什么意思?”

“这个表示我们这个请求是发给哪个资源的,订单是一种资源,我们可以理解为是咖啡厅专门管理订单的人,他可以帮我们处理所有有关订单的操作,包括新增订单、修改订单、取消订单等操作”

“Soga…”

“接着还是会返回订单的编号给我们”


	{
	    "orderId": "123456"
	}

“下面,我们还是要查询会员卡余额,这次请求的资源变成了cards”


	/cards
	
	{
	    "queryBalance": {
	        "cardId": "886333"
	    }
	}

“接下来是取消订单”

“这个我会”,说着,她抢走我手上的笔,在纸上写了起来:


	/orders
	
	{
	    "deleteOrder": {
	        "orderId": "123456"
	    }
	}

Level 2 - 打上标签

“接下来,店主还想继续优化他的咖啡厅的服务流程,他发现负责处理订单的员工,每次都要去订单内容里面看是新增订单还是删除订单,还是其他的什么操作,十分不方便,于是规定,所有新增资源的请求,都在请求上面写上大大的‘POST’,表示这是一笔新增资源的请求”

“其他种类的请求,比如查询类的,用‘GET’表示,删除类的,用‘DELETE’表示”

“还有修改类的,修改分为两种,第一种,如果这个修改,无论发送多少次,最后一次修改后的资源,总是和第一次修改后的一样,比如将拿铁改为猫屎,那么用‘PUT’表示;第二种,如果这个修改,每次修改都会让这个资源和前一次的不一样,比如是加一杯咖啡,那么这种请求用‘PATCH’或者‘POST’表示”,一口气讲了这么多,发现她有点似懂非懂。

“来,我们再来重复上面那个过程,来一杯拿铁”,我边说边画着:


	POST /orders
	
	{
	    "orderName": "latte"
	}

“请求的内容简洁多啦,不用告诉店员是addOrder,看到POST就知道是新增”,她听的很认真,理解的也很透彻。

“恩恩,返回的内容还是一样”


	{
	    "orderId": "123456"
	}

“接着是查询会员卡余额,这次也简化了很多”


	GET /cards
	
	{
	    "cardId": "886333"
	}

“这个请求我们还可以进一步优化为这样”

GET /cards/886333

“Soga,直接把要查询的卡号写在后面了”

“没错,接着,取消订单”

DELETE /orders/123456

Level 3 - 完美服务

“忽然有一天,有个顾客抱怨说,他买了咖啡后,不知道要怎么取消订单,咖啡厅一个店员回了一句,你不会看我们的宣传单吗,上面不写着:

DELETE /orders/{orderId}

顾客反问道,谁会去看那个啊,店员不服,又说到,你瞎了啊你…据说后面两人吵着吵着还打了起来…”

“噗,真是悲剧…”

“有了这次教训,店长决定,顾客下了单之后,不仅给他们返回订单的编号,还给顾客返回所有可以对这个订单做的操作,比如告诉用户如何删除订单。现在,我们还是发出请求,请求内容和上一次一样”


	POST /orders
	
	{
	    "orderName": "latte"
	}

“但是这次返回时多了些内容”


	{
	    "orderId": "123456",
	    "link": {
	        "rel": "cancel",
	        "url": "/order/123456"
	    }
	}

“这次返回时多了一项link信息,里面包含了一个rel属性和url属性,rel是relationship的意思,这里的关系是cancel,url则告诉你如何执行这个cancel操作,接着你就可以这样子来取消订单啦”

DELETE /orders/123456

“哈哈,这服务真是贴心,以后再也不用担心店员和顾客打起来了”

“订单123456的客户可以取餐了”,伴随着咖啡厅的广播,我们吃起了下午茶,一杯拿铁,两支吸管…

二.REST详解

来自于 九师兄-梁川川 原文:https://blog.csdn.net/qq_21383435/article/details/80032375

传统API接口

传统API接口在你对一个对象操作的时候可能会有多个url。比如你要操作一个customer,你要发送一个查找请求,就会有如下url:
http://test.com/customer/selectById?id=1
然后你又要执行一个删除操作,只时候对同样的对象url就又变成这样:
http://test.com/customer/deleteById?id=1
如果你还要执行创建和修改功能,url又会不一样,后面参数携带的不同也会变的不一样,这样就显得非常的混乱。

REST风格

首先来讲一个例子:
假设你需要请求你朋友帮你一件事,你给他寄信。在写地址的时候,你的地址要按照邮局的要求来写吧,不能自己想写什么就写什么,这样邮局就有可能看不懂。同时邮局并不关心你信里面写了什么内容,需要收件人帮你完成什么事,这都不管邮局的事情,他只负责帮你找到那个人的位置帮你把信送到。当收件人收到信了,打开信来收阅读你需要他完成的事情。
这里,邮局就像是我们的url,你的信就是请求,你只需要在url写资源地址,你不需要把你的请求写url里,url只帮你找到你需要的资源,资源收到信里面的内容来获取你的请求。

对比


	https://localhost:8080/myweb/getDogs --> GET /rest/api/dogs 获取所有小狗狗 
	https://localhost:8080/myweb/addDogs --> POST /rest/api/dogs 添加一个小狗狗 
	https://localhost:8080/myweb/updateDogs/:dog_id --> PUT /rest/api/dogs/:dog_id 修改一个小狗狗 
	https://localhost:8080/myweb/deleteDogs/:dog_id --> DELETE /rest/api/dogs/:dog_id 删除一个小狗狗

REST规则

GET 用来获取资源,
POST 用来新建资源(也可以用于更新资源),
PUT 用来更新资源,
DELETE 用来删除资源


	DELETE http://api.qc.com/v1/friends: 删除某人的好友 (在http parameter指定好友id)
	POST http://api.qc.com/v1/friends: 添加好友UPDATE 
	http://api.qc.com/v1/profile: 更新个人资料

概念

REST 是面向资源的,这个概念非常重要,而资源是通过 URI 进行暴露。
URI 的设计只要负责把资源通过合理方式暴露出来就可以了。
对资源的操作与它无关,操作是通过 HTTP动词来体现,所以REST 通过 URI 暴露资源时,会强调不要在 URI 中出现动词。
比如:左边是错误的设计,而右边是正确的


	GET /rest/api/getDogs --> GET /rest/api/dogs 获取所有小狗狗 
	GET /rest/api/addDogs --> POST /rest/api/dogs 添加一个小狗狗 
	GET /rest/api/editDogs/:dog_id --> PUT /rest/api/dogs/:dog_id 修改一个小狗狗 
	GET /rest/api/deleteDogs/:dog_id --> DELETE /rest/api/dogs/:dog_id 删除一个小狗狗

REST很好地利用了HTTP本身就有的一些特征,如HTTP动词、HTTP状态码、HTTP报头等等。
REST API 是基于 HTTP的,所以你的API应该去使用 HTTP的一些标准。
这样所有的HTTP客户端(如浏览器)才能够直接理解你的API(当然还有其他好处,如利于缓存等等)。
REST 实际上也非常强调应该利用好 HTTP本来就有的特征,而不是只把 HTTP当成一个传输层这么简单了。


	HTTP/1.1 200 OK
	Content-Type: application/json
	Content-Length: xxx
	
	{
	   "url" : "/api/categories/1",
	   "label" : "Food",
	   "items_url" : "/api/items?category=1",
	   "brands" : [
	         {
	            "label" : "友臣",
	            "brand_key" : "32073",
	            "url" : "/api/brands/32073"
	         }, {
	            "label" : "乐事",
	            "brand_key" : "56632",
	            "url" : "/api/brands/56632"
	         }
	         ...
	   ]
	}

看这个响应,包含了http里面的状态码等信息。还会有http的一些报头。

Authorization 认证报头

Cache-Control 缓存报头

Cnotent-Type 消息体类型报头

REST 系统的特征

  1. 客户-服务器(Client-Server),提供服务的服务器和使用服务的客户需要被隔离对待。
  2. 无状态(Stateless),来自客户的每一个请求必须包含服务器处理该请求所需的所有信息。换句话说,服务器端不能存储来自某个客户的某个请求中的信息,并在该客户的其他请求中使用。
  3. 可缓存(Cachable),服务器必须让客户知道请求是否可以被缓存。(Ross:更详细解释请参考 理解本真的REST架构风格 以及 StackOverflow 的这个问题 中对缓存的解释。)
  4. 分层系统(Layered System),服务器和客户之间的通信必须被这样标准化:允许服务器和客户之间的中间层(Ross:代理,网关等)可以代替服务器对客户的请求进行回应,而且这些对客户来说不需要特别支持。
  5. 统一接口(Uniform Interface),客户和服务器之间通信的方法必须是统一化的。(Ross:GET,POST,PUT.DELETE, etc)
  6. 支持按需代码(Code-On-Demand,可选),服务器可以提供一些代码或者脚本(Ross:Javascrpt,flash,etc)并在客户的运行环境中执行。这条准则是这些准则中唯一不必必须满足的一条。(Ross:比如客户可以在客户端下载脚本生成密码访问服务器。)

三.REST风格使用

配置PUT和DELETE支持

为什么需要支持

在form表单默认情况下,是不支持Put和Delete请求的。

配置支持

在spring3.0添加了一个过滤HiddenHttpMethodFilter,可以将post请求转换为PUT或DELETE请求。直接在web.xml中配置如下代码即可

  
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

使用示范

先配置一个form表单,其中注意,表单在发送post请求的时候,如果你要使用PUT或者DELETE请求需要在表单里面加一个字段,不能再get请求里面添加。一个隐藏的字段,这个隐藏字段要求name必须得叫_method,value里面跟上请求的方式,你是put请求或者delete请求。
下面就是发送一个/rest/testform/100请求,100是我控制器里面接收的参数

  
    <form action="/rest/testform/100" method="post">    
       <input type="hidden" name="_method" value="put">
       <input type="submit" value="提交请求">
    </from>

接下来定义一个方法接收参数,在控制台中输出传过来的参数,然后在返回给second页面,second页面只是单纯的实现跳转,里面没写任何内容。先做一个错误的示范如下:
其中method= RequestMethod.PUT表示我只接收put请求,其他请求都访问不到该方法。 @PathVariable Integer id用来接收{id}里面的值。


    @RequestMapping(value = "rest/testform/{id}",method = RequestMethod.PUT)
    public String testForm(@PathVariable Integer id){
        System.out.println(id);
        return "second";
    }

启动服务器出现以下界面,点击提交请求按钮
在这里插入图片描述
你会发现,会报一个错误
在这里插入图片描述
这个错误是为什么呢?我明明已经配置支持为什么还会报错。看错误信息,他报了一个“JSP 只允许 GET、POST 或 HEAD。Jasper 还允许 OPTIONS”这一的错误,这个意思是说这个jsp他支持get、head、和post,不支持put和delete。
然后检查发现,之前页面传过来的参数100,在控制台中已经打印了出来。说明从发送请求到接收请求再到进入控制器方法打印输出这一步上是没有问题的,问题就出现在return "second"这行代码上。**原因是因为在java中,转发是一次请求,在表单中你发送的请求,最终会被认为提交到一个jsp,但是jsp在接收的时候不支持put和delete。**是什么时候开始不支持的呢?从tamcat8开始。
那么在开发过程中是如何解决这个问题的呢?简单的来讲,就是你发送请求的时候这个put或者delete请求到控制器方法里面就结束了,然后通过控制器重定向到你的最终jsp上,也就是说要专门另外做一个跳转请求。正确做法实现如下:


    @RequestMapping(value = "testform/{id}",method = RequestMethod.PUT)
    public String testForm(@PathVariable Integer id){
        System.out.println(id);
        return "redirect:/rest/tosecond";
    }
    @RequestMapping("tosecond")
    public String tosecond(){
        return "second";
    }

运行结果:
在这里插入图片描述

HiddenHttpMethodFilter过滤器源码分析

成员变量

这小节主要说明,为什么之前隐藏组件里面name一定是_method。 因为该方法里面就定义了_method变量,用来接收页面传递来的请求类型。


    public static final String DEFAULT_METHOD_PARAM = "_method";
    private String methodParam = "_method";

###成员方法
这里主要说明为什么必须要使用post的时候才能在form里面写put或者delete。我认为可以简单的这样理解,read对应get来读取数据,create对应post来创建数据,update对应put来修改数据,delete就对应本身。post、update、delete都会改变数据库,所以都必须在使用post的时候来实现这两个请求。


        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取request对象
        HttpServletRequest requestToUse = request;
        //判断是否是post请求,并且没有异常
        if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
            //从请求中获取参数,methodParam就是上面讲的_method
            String paramValue = request.getParameter(this.methodParam);
            //判断取的这个值有没有
            if (StringUtils.hasLength(paramValue)) {
                //把取的值全部给你转换成大写
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                //在允许的方法中进行查找
                if (ALLOWED_METHODS.contains(method)) {
                    //允许的话内部对这个Http进行一个包装
                    requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                }
            }
        }
        //包装完之后最后给你放行
        filterChain.doFilter((ServletRequest)requestToUse, response);
       }

通过@RequestHeader注解获取请求头信息

打开页面中请求头信息

在之前测试页面右键–检查,然后点击提交按钮。图只截取了一部分,从里面可以看出,你可以通过Host获取主机地址。
在这里插入图片描述

注解示范


    @RequestMapping(value = "testform/{id}",method = RequestMethod.PUT)
    public String testForm(@PathVariable Integer id, @RequestHeader String Host){
        System.out.println(id);
        System.out.println("主机地址:"Host);
        return "redirect:/rest/tosecond";
    }

运行结果:
在这里插入图片描述

请求头中获取cookie


    @RequestMapping(value = "testform/{id}",method = RequestMethod.PUT)
    public String testForm(@PathVariable Integer id,
                           @RequestHeader("Cookie") String cookie){
        System.out.println(id);
        System.out.println("输出cookie:"+cookie);
        return "redirect:/rest/tosecond";
    }

在这里插入图片描述
从输出结果中可以看出,输出了一个JSESSIONID,因为我们在访问jsp的时候,会默认给我们创建一个session。创建session的时候会默认把sessionid写入cookie中。
获取的cookie是把cookie里面存放的所有信息都获取了,如何只获取某一条内容的值呢?这里我们可以通过@CookieValue(value=“JSESSIONID”)注解来获取,只需要把key值写到value里面就可以了。


    @RequestMapping(value = "testform/{id}",method = RequestMethod.PUT)
    public String testForm(@PathVariable Integer id,
                           @CookieValue("JSESSIONID") String cookie){
        System.out.println(id);
        System.out.println("输出cookie:"+cookie);
        return "redirect:/rest/tosecond";
    }

四.RESTful API 设计指南

协议

API与用户的通信协议,总是使用HTTPs协议。

域名

应该尽量将API部署在专用域名之下。
https://api.example.com
如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。
https://example.org/api/

版本(Versioning)

应该将API的版本号放入URL。
https://api.example.com/v1/
另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用这种做法。

路径(Endpoint)

路径又称"终点"(endpoint),表示API的具体网址。

在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。
一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。
举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。
https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees

HTTP动词

对于资源的具体操作类型,由HTTP动词表示。

常用的HTTP动词有下面五个(括号里是对应的SQL命令)。

GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
DELETE(DELETE):从服务器删除资源。
还有两个不常用的HTTP动词。
HEAD:获取资源的元数据。
OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。

下面是一些例子。
GET /zoos:列出所有动物园
POST /zoos:新建一个动物园
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

过滤信息(Filtering)

如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。

下面是一些常见的参数。

?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件
参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。
比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。

状态码(Status Codes)

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - []:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
401 Unauthorized - [
]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [
]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

错误处理(Error handling)

如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。


	{
	    error: "Invalid API key"
	}

返回结果

针对不同操作,服务器向用户返回的结果应该符合以下规范。

GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档

Hypermedia API

RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。


	{"link": {
	  "rel":   "collection https://www.example.com/zoos",
	  "href":  "https://api.example.com/zoos",
	  "title": "List of zoos",
	  "type":  "application/vnd.yourformat+json"
		}
	}

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。

Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。


	  {
	  "current_user_url": "https://api.github.com/user",
	  "authorizations_url": "https://api.github.com/authorizations",
	  // ...
	  }

从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。


	{
	  "message": "Requires authentication",
	  "documentation_url": "https://developer.github.com/v3"
	}

上面代码表示,服务器给出了提示信息,以及文档的网址。

其他

(1)API的身份认证应该使用OAuth 2.0框架。

(2)服务器返回的数据格式,应该尽量使用JSON,避免使用XML。

五.OAuth 2.0框架

应用场景

为了理解OAuth的适用场合,让我举一个假设的例子。

有一个"云冲印"的网站,可以将用户储存在Google的照片,冲印出来。用户为了使用该服务,必须让"云冲印"读取自己储存在Google上的照片。

云冲印

问题是只有得到用户的授权,Google才会同意"云冲印"读取这些照片。那么,"云冲印"怎样获得用户的授权呢?

传统方法是,用户将自己的Google用户名和密码,告诉"云冲印",后者就可以读取用户的照片了。这样的做法有以下几个严重的缺点。

(1)"云冲印"为了后续的服务,会保存用户的密码,这样很不安全。

(2)Google不得不部署密码登录,而我们知道,单纯的密码登录并不安全。

(3)"云冲印"拥有了获取用户储存在Google所有资料的权力,用户没法限制"云冲印"获得授权的范围和有效期。

(4)用户只有修改密码,才能收回赋予"云冲印"的权力。但是这样做,会使得其他所有获得用户授权的第三方应用程序全部失效。

(5)只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有被密码保护的数据泄漏。

OAuth就是为了解决上面这些问题而诞生的。

名词定义

在详细讲解OAuth 2.0之前,需要了解几个专用名词。它们对读懂后面的讲解,尤其是几张图,至关重要。

(1) Third-party application:第三方应用程序,本文中又称"客户端"(client),即上一节例子中的"云冲印"。

(2)HTTP service:HTTP服务提供商,本文中简称"服务提供商",即上一节例子中的Google。

(3)Resource Owner:资源所有者,本文中又称"用户"(user)。

(4)User Agent:用户代理,本文中就是指浏览器。

(5)Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。

(6)Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。

知道了上面这些名词,就不难理解,OAuth的作用就是让"客户端"安全可控地获取"用户"的授权,与"服务商提供商"进行互动。

OAuth的思路

OAuth在"客户端"与"服务提供商"之间,设置了一个授权层(authorization layer)。“客户端"不能直接登录"服务提供商”,只能登录授权层,以此将用户与客户端区分开来。"客户端"登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。

"客户端"登录授权层以后,"服务提供商"根据令牌的权限范围和有效期,向"客户端"开放用户储存的资料。

简化模式

(A)客户端将用户导向认证服务器。

(B)用户决定是否给于客户端授权。

(C)假设用户给予授权,认证服务器将用户导向客户端指定的"重定向URI",并在URI的Hash部分包含了访问令牌。

(D)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。

(E)资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。

(F)浏览器执行上一步获得的脚本,提取出令牌。

(G)浏览器将令牌发给客户端。

总结

总结来源于《大雄和静香的故事》,https://zhuanlan.zhihu.com/p/30396391?group_id=937244108725641216
在这里插入图片描述

Level0和Level1最大的区别,就是Level1拥有了Restful的第一个特征——面向资源,这对构建可伸缩、分布式的架构是至关重要的。同时,如果把Level0的数据格式换成Xml,那么其实就是SOAP,SOAP的特点是关注行为和处理,和面向资源的RESTful有很大的不同。

Level0和Level1,其实都很挫,他们都只是把HTTP当做一个传输的通道,没有把HTTP当做一种传输协议。

Level2,真正将HTTP作为了一种传输协议,最直观的一点就是Level2使用了HTTP动词,GET/PUT/POST/DELETE/PATCH…,这些都是HTTP的规范,规范的作用自然是重大的,用户看到一个POST请求,就知道它不是幂等的,使用时要小心,看到PUT,就知道他是幂等的,调用多几次都不会造成问题,当然,这些的前提都是API的设计者和开发者也遵循这一套规范,确保自己提供的PUT接口是幂等的。

Level3,关于这一层,有一个古怪的名词,叫HATEOAS(Hypertext As The Engine Of Application State),中文翻译为“将超媒体格式作为应用状态的引擎”,核心思想就是每个资源都有它的状态,不同状态下,可对它进行的操作不一样。理解了这一层,再来看看REST的全称,Representational State Transfer,中文翻译为“表述性状态转移”,是不是好理解多了?

Level3的Restful API,给使用者带来了很大的便利,使用者只需要知道如何获取资源的入口,之后的每个URI都可以通过请求获得,无法获得就说明无法执行那个请求。

现在绝大多数的RESTful接口都做到了Level2的层次,做到Level3的比较少。当然,这个模型并不是一种规范,只是用来理解Restful的工具。所以,做到了Level2,也就是面向资源和使用Http动词,就已经很Restful了。Restful本身也不是一种规范,我比较倾向于用“风格"来形容它。如果你想深入了解Level3,可以阅读《Rest in Practice》第五章。

猜你喜欢

转载自blog.csdn.net/superyayaya/article/details/88931650
今日推荐