会话与API安全 - 下

前后端分离之后 前后端分离之后,在部署上通过一个反向代理就可以实现动静态分离,跨域问题的解决等。但是一旦引入鉴权,则又会产生新的问题。通常来说,鉴权是对于后台API/API背后的资源的保护,即未经授权的用户不能访问受保护资源。 要实现这个功能有很多种方式,在应用程序之外设置完善的安全拦截器是最常见的方式。不过有点不够优雅的是,一些不太纯粹的、非功能性的代码和业务代码混在同一个代码库中。 另一方面,各个业务系统都可能需要某种机制的鉴权,所以很多企业都会搭建SSO机制,即Single Sign-On。这样可以避免人们在多个系统创建不同账号,设置不同密码,不同的超时时间等等。如果SSO系统已经先于系统存在了很久,那么新开发的系统完全不需要自己再配置一套用户管理机制了(一般SSO只会完成鉴权中鉴别的部分,授权还是需要各个业务系统自行处理)。 本文中,我们使用基础设施(反向代理)的一些配置,来完成保护未授权资源的目的。在这个例子中,我们假设系统由这样几个服务器组成: 系统组成 这个实例中,我们的系统分为三部分 kanban.com:8000(业务系统前端) api.kanban.com:9000(业务系统后端API) sso.kanban.com:8100 (单点登录系统,登陆界面) 前端包含了HTML/JS/CSS等资源,是一个纯静态资源,所以本地磁盘即可。后端API则是一组需要被保护的API(比如查询工资详情,查询工作经历等)。最后,单点登录系统是一个简单的表单,用户填入用户名和密码后,如果登录成功,单点登录会将用户重定向到登录前的位置。 我们举一个具体场景的例子: 未登录用户访问http://kanba.com:8000/index.html 系统会重定向用户到http://sso.kanban.com:8100/sso?return=http://kanba.com:8000/index.html 用户看到登录页面,输入用户名、密码登录 用户被重定向回http://kanba.com:8000/index.html 此外,index.html页面上的app.js对api.kanban.com:9000的访问也得到了授权 环境设置 简单起见,可以通过修改/etc/hosts文件来设置服务器环境: 127.0.0.1 sso.kanban.com 127.0.0.1 api.kanban.com 127.0.0.1 kanban.com nginx及auth_request 反向代理nginx有一个auth_request的模块。在一个虚拟host中,每个请求会先发往一个内部location,这个内部的location可以指向一个可以做鉴权的Endpoint。如果这个请求得到的结果是200,那么nginx会返回用户本来请求的内容,如果返回401,则将用户重定向到一个预定义的地址: server { listen 8000; server_name kanban.com; root /usr/local/var/www/kanban/; error_page 401 = @error401; location @error401 { return 302 http://sso.kanban.com:8100/sso?return=$scheme://$http_host$request_uri; } auth_request /api/auth; location = /api/auth { internal; proxy_pass http://api.kanban.com:9000; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URI $request_uri; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; if ($http_cookie ~* "w3=(\w+)") { set $token "$1"; } proxy_set_header X-KANBAN-TOKEN $token; } } 比如上面这个例子中,auth_request的URL为/api/auth,它是一个内部的location,外部无法访问。在这个locaiton中,请求会被转发到http://api....

May 12, 2016 · 2 min · 邱俊涛 | Juntao Qiu

site_prism中的Page Object

PageObject简介 PageObject是编写UI测试时的一种模式。简而言之,你可以将所有知道页面细节的部分放入到这个对象上,对于编写测试的人来说,一个PageObject代表了一个页面,或者页面上的一个区域(比如搜索框,搜索结果,侧边栏等都可能是一个独立的Object)。这样做的好处分为两个方面: 封装了所有的实现细节(内部的HTML是如何组织的) 对外的接口非常清晰,从而代码更加语义化 我们这里列举一个简单的例子来说明: 我们要测试的场景是:我们在一个搜索应用中,用户输入了ThoughtWorks,我们来判断搜索结果的第一页有10条结果。如果使用原生的capybara,代码大致会如下: visit '/search' fill_in 'Search', :with => 'ThoughtWorks' click_button '#search' expect(find('#result').find('.tips')).to have_content("10") 首先我们进入/search页面,然后在Search中输入了ThoughtWorks关键字,然后点击#search按钮,最后判断#result .tips下有10的字样。 如果使用PageObject,代码则会变成(这个是伪代码): search = SearchBox.new result = SearchResult.new search.type "ThoughtWorks" expect(result.count).to eq(10) site_prism 简介 site_prism是一个构建在capybara之上的用于建模Page Object的gem。使用site_prism可以很语义化的编写Page Object,可以使代码非常易读。 位于顶层的Page对象可以拥有多个Section对象,每个Section可以对应页面上的一些逻辑上的块,比如内容区域,边栏等。对于现在流行的SPA,我们只需要一个Page和若干个Section就足够了。 class MovingHome < SitePrism::Page set_url 'http://localhost:8100/bundles/moving-home' element :container, "#tmsCheckout" section :personal, PersonalSection, "#acc-personal" section :contact, ContactSection, "#acc-contact" end set_url方法制定了如何到达当前页,也是webdriver会实际发送请求的URL。页面本身上可以用element方法来声明一个元素,以及该元素对应的CSS选择器,这样就可以通过元素的名称来访问该选择器对应的HTML元素了。 比如上例中的container,我们在测试中就可以这样来访问它: @moving = MovingHome.new @moving.load @moving.container.should be_visible 而对应的section元素,则声明了一个块的名称,块的类和块的选择器。这样我们就可以通过名称来应用该块了: @moving.personal.name.set "Juntao" expect(@moving).to have_personal expect(@moving).to have_contact have_前缀加块的名称,用来判断该块是否可见(比如display: block)。...

January 2, 2015 · 2 min · 邱俊涛 | Juntao Qiu

中午吃啥 - 一个简单的脚本

千古谜题 — 中午吃啥? 如果要列出一些日常最频繁的会问/被问的问题的一个列表,吃啥?绝对会排在前三位,对于程序员来说,一样频繁的还有诸如这是谁写的?,***这尼玛啥意思啊?***之类。 吃啥作为一个每天都会面对的问题,我们自然而言会想很多办法,比如随大流,其他人去哪儿我们跟着就行,但是这种方法最大的问题是:大部分人其实都没有很好的想法,大家都很迷茫。作为程序员,一个非常直观的想法就是找出一个列表,然后随机/伪随机的从这个列表中拿出一条来作为推荐。 基本思路 一个基本的思路是这样的,或者说,要开发的软件应该满足这几个基本的需求 维护一个饭店/饭菜的列表 随机的从这个列表中取出一项 每天定时的触发,比如11:30准时提醒 这个工具最终要以弹出窗口等方式来提醒 饭店/饭菜的列表比较容易,比如一个静态的JSON文件: [ { "name": "关中客大碗面" }, { "name": "王华峰肉夹馍" }, { "name": "傻得帽冒菜" }, { "name": "蒸饺" }, { "name": "樊家肉夹馍" }, { "name": "马奴哈羊肉泡馍" }, { "name": "子午路张记肉夹馍" }, { "name": "东滩水盆" } ] 然后我们需要一个小程序来读取这段JSON,并已随机/伪随机的方式返回一个推荐: # encoding: UTF-8 require 'json' def first JSON.parse(File.open("food.json").read).shuffle[0]["name"] end puts "今天去吃#{first}吧?" 测试一下,将上边这个ruby程序运行几次,可以得到一下结果: $ ruby lunch.rb 今天去吃东滩水盆吧? $ ruby lunch.rb 今天去吃子午路张记肉夹馍吧? $ ruby lunch....

September 18, 2014 · 1 min · 邱俊涛 | Juntao Qiu

Ruby里的元编程

一个场景 元编程在所有的Lisp系语言中应该都是一个必备的feature,coommon lisp, scheme等包含该功能自然不在话下,而比较主流的编程语言如JavaScript,python之流,也或多或少的受到了lisp得影响,在面向对象的同时,也嵌入了一些元编程的特性。 而元编程在ruby中,虽然不如在lisp的宏那样灵活/强大,但是对于被“主流”编程语言影响很久的程序员 – 如我,来说,已经非常震撼了。 很多ruby程序员都是通过rails才慢慢接触到ruby本身的,在rails中,ORM是通过强大到无穷大得ActiveRecord来完成的。 一个简单的示例如: class Person < ActiveRecord::Base end 对应的,数据库中有一个Person的表: CREATE TABLE person ( id int(11) NOT NULL auto_increment, name varchar(255), age int, email varchar(255), PRIMARY KEY (id) ); 这样,在使用模型Person的地方,可以很容易的编写这样的代码: juntao = Person.new juntao.name = 'juntao' juntao.age = 28 juntao.email = 'juntao.qiu@gmail.com' juntao.save 也就是说,开发者仅仅需要简单的创建一个与数据库同名的ruby类,然后这个类(Person)只需要继承自ActiveRecord::Base,那么它就自动的获得了很多的功能。这些神奇的功能就是通过ruby的元编程来完成的。 一个ActiveRecord的拙劣模仿 我们在这里将编写一个简单的类InactiveRecord,当有其他类继承自此类时,会完成如ActiveRecord那样的功能,当然第一步我们并没有数据校验之类的功能,只是简单的将数据存储起来即可: 在person.rb文件中 class Person < InactiveRecord::Base end 在address.rb中: class Address < InactiveRecord::Base end 而在使用他们的地方: require './person' require './address' def test juntao = Person....

December 15, 2013 · 3 min · 邱俊涛 | Juntao Qiu

使用Grape快速开发API

Grape简介 Grape是一个基于Rack的非常轻量级的框架,用于快速的开发API。一般来说,Rails对于单独的API来说,太过于重量级;而Sinatra虽然足够小巧,但是又没有为开发API提供足够的默认支持(如果从可控制性,灵活性上来说,Sinatra可能更好一些,但是如果有专门的更好用的工具,为什么不用呢?)。 安装非常简便: $ gem install grape 或者使用在自己的Gemfile中,与其他的gem一起搭建API: gem 'grape' 为既有系统添加API 简单一试 之前的一篇介绍ActiveRecord在既有系统中使用的文章中,我使用ActiveRecord为既有的数据库visitor中的三个表(visitor, listGroup, listGroupItem)建立了ruby对应的模型。现在我们可以为这些模型包装一组API,以方便客户端(消费者)可以通过web来访问。 module MySys class API < Grape::API format :json resource :visitors do desc "get all visitor information" get do Visitor.limit(20) end end end end 首先,MySys::API扩展了Grape::API。format定义我们的API会产生JSON格式的输出,resource定义了这一组API是为资源visitors提供的,因此访问API的url为: http://localhost:9292/visitors/ 当然,grape提供一个很方便的设置prefix,可以使得API的路径更有意义: format :json prefix "mysys" url则相应地变为: http://localhost:9292/mysys/visitors/ 处理参数 在对参数的处理上,grape也非常灵活,比如接上例,我们想要获取某一个具体的用户的信息: http://localhost:9292/visitors/8a9d82b13b9786e1013b978766150001 我们可以添加一个新的endpoint: desc "return a visitor" params do requires :visitor_uid, :type => String, :desc => "visitor id" end route_param :visitor_uid do get do Visitor....

August 4, 2013 · 1 min · 邱俊涛 | Juntao Qiu