摘要:我的提供者類使用了來發(fā)送認證的錯誤信息,我們也需要在我們的腳本中為其提供支持現在我們解決了基本的錯誤情況,我們要為訪問令牌設置。現在我們只需要通過一些請求頭信息告知我們當前的應用誰登錄了就行了。
前言:我們使用Nginx的Lua中間件建立了OAuth2認證和授權層。如果你也有此打算,閱讀下面的文檔,實現自動化并獲得收益。
SeatGeek 在過去幾年中取得了發(fā)展,我們已經積累了不少針對各種任務的不同管理接口。我們通常為新的展示需求創(chuàng)建新模塊,比如我們自己的博客、圖表等。我們還定期開發(fā)內部工具來處理諸如部署、可視化操作及事件處理等事務。在處理這些事務中,我們使用了幾個不同的接口來認證:
Github/Google Oauth
我們SeatGeek內部的用戶系統
基本認證
硬編碼登錄
顯然,實際應用中很不規(guī)范。多個認證系統使得難以對用于訪問級別和通用許可的各種數據庫進行抽象。
單系統認證我們也做了一些關于如何設置將解決我們問題的研究。這促使了Odin的出現,它在驗證谷歌應用的用戶方面工作的很好。不幸的是它需要使用Apache,而我們已和Nginx結為連理并把它作為我們的后端應用的前端。
幸運的是,我看了mixlr的博客并引用了他們Lua在Nginx上的應用:
修改響應頭
重寫內部請求
選擇性地基于IP拒絕主機訪問
最后一條看起來很有趣。它開啟了軟件包管理的地獄之旅。
構建支持Lua的NginxLua for Nginx沒有被包含在Nginx的核心中,我們經常要為OSX構建Nginx用于開發(fā)測試,為Linux構建用于部署。
為OSX定制Nginx對于OSX系統,我推薦使用Homebrew進行包管理。它初始的Nginx安裝包啟用的模塊不多,這有非常好的理由:
關鍵在于NGINX有著如此之多的選項,如果把它們都加入初始包那一定是瘋了,如果我們只把其中一些加入其中就會迫使我們把所有都加入,這會讓我們瘋掉的。
--- Charlie Sharpsteen, @sharpie
所以我們需要自己構建。合理地構建Nginx可以方便我們以后繼續(xù)擴展。幸運的是,使用Homebrew進行包管理十分方便快捷。
我們首先需要一個工作空間:
cd ~ mkdir -p src cd src
之后,我們需要找到初始安裝信息包。你可以通過下面任何一種方式得到它:
找到HOMEBREW_PREFIX目錄,通常在/usr/local下,在其中找到nginx.rb文件
從下列地址取得https://raw.github.com/mxcl/homebrew/master/Library/Formula/nginx.rb
使用如下命令 brew cat nginx > nginx.rb
此時如果我們執(zhí)行 brew install ./nginx.rb 命令, 它會依據其中的信息安裝Nginx。既然現在我們要完全定制Nginx,我們要重命名信息包,這樣之后通過 brew update 命令進行更新的時候就不會覆蓋我們自定義的了:
mv nginx.rb nginx-custom.rb cat nginx-custom.rb | sed "s/class Nginx/class NginxCustom/" >> tmp rm nginx-custom.rb mv tmp nginx-custom.rb
我們現在可以將我們需要的模塊加入安裝信息包中并開始編譯了。這很簡單,我們只要將所有我們需要的模塊以參數形式傳給 brew install 命令,代碼如下:
# Collects arguments from ARGV def collect_modules regex=nil ARGV.select { |arg| arg.match(regex) != nil }.collect { |arg| arg.gsub(regex, "") } end # Get nginx modules that are not compiled in by default specified in ARGV def nginx_modules; collect_modules(/^--include-module-/); end # Get nginx modules that are available on github specified in ARGV def add_from_github; collect_modules(/^--add-github-module=/); end # Get nginx modules from mdounin"s hg repository specified in ARGV def add_from_mdounin; collect_modules(/^--add-mdounin-module=/); end # Retrieve a repository from github def fetch_from_github name name, repository = name.split("/") raise "You must specify a repository name for github modules" if repository.nil? puts "- adding #{repository} from github..." `git clone -q git://github.com/#{name}/#{repository} modules/#{name}/#{repository}` path = Dir.pwd + "/modules/" + name + "/" + repository end # Retrieve a tar of a package from mdounin def fetch_from_mdounin name name, hash = name.split("#") raise "You must specify a commit sha for mdounin modules" if hash.nil? puts "- adding #{name} from mdounin..." `mkdir -p modules/mdounin && cd $_ ; curl -s -O http://mdounin.ru/hg/#{name}/archive/#{hash}.tar.gz; tar -zxf #{hash}.tar.gz` path = Dir.pwd + "/modules/mdounin/" + name + "-" + hash end
上面這個輔助模塊可以讓我們指定想要的模塊并檢索模塊的地址。現在,我們需要修改nginx-custom.rb文件,使之包含這些模塊的名字并在包中檢索它們,在58行附近:
nginx_modules.each { |name| args << "--with-#{name}"; puts "- adding #{name} module" } add_from_github.each { |name| args << "--add-module=#{fetch_from_github(name)}" } add_from_mdounin.each { |name| args << "--add-module=#{fetch_from_mdounin(name)}" }
現在我們可以編譯我們重新定制的nginx了:
brew install ./nginx-custom.rb --add-github-module=agentzh/chunkin-nginx-module --include-module-http_gzip_static_module --add-mdounin-module=ngx_http_auth_request_module#a29d74804ff1
你可以方便地在seatgeek/homebrew-formulae找到以上信息包。
為Debian定制Nginx我們通常都會部署到Debian的發(fā)行版-通常是Ubuntu上作為我們的產品服務器。如果是這樣,那將會非常簡單,運行 dpkg -i nginx-custom 安裝我們的定制包。這步驟如此簡單你一運行它就完成了。
一些在搜索定制debian/ubuntu包時的筆記:
你可以通過 apt-get source PACKAGE_NAME來獲取debian安裝包。
Debian安裝包受控于一個 rules文件,你需要sed-fu來操作它。
你可以通過編輯 control 文件來更新 deb包的依賴。注意這里指定了一些元依賴(meta-dependency)你不要去刪除它,但是這些很容易分辨出來。
新的發(fā)布必須要在changelog里注明,否則包有可能不會被升級因為它可能已經被安裝過了。你需要在表單里使用 +tag_name來指明哪些是你自己在baseline上新加的改動。我會額外加上一個數字-從0開始-指示出包的發(fā)布編號。
大多數的改動可以以某種方式自動更改,但是似乎沒有一個簡單的命令行工具可以創(chuàng)建定制的發(fā)布包。這也正是我們感興趣的地方,如果你知道什么的話,請給我們給我們提供一些鏈接,工具或方法。
在運行這個偉大過程的同時,我構建了一個小的批處理腳本來自動化這個過程的主要步驟,你可以在 gist on github: gist.github.com/4126937 上找到它。
在我意識到這個過程可以被腳本化之前僅僅花費了90個nginx包的構建時間。
現在可以測試并部署嵌入Nginx的Lua腳本了,讓我們開始Lua編程。
nginx-lua模塊提供了一些輔助功能和變量來訪問Nginx的絕大多數功能,顯然我們可以通過access_by_lua中該模塊提供的指令來強制打開OAuth認證。
當使用*_by_lua_file指令后,必須重載nginx來使其起作用。
我用 NodeJS 為 SeatGeek 創(chuàng)建了一個簡單的 OAuth2 提供者類。這部分內容很簡單,你也很容易獲得你是通用語言的響應版本。
接下來,我們的OAuth API使用JSON來處理令牌(token)、訪問級別(access level)和重新認證響應(re-authentication responses)。所以我們需要安裝lua-cjson模塊。
# install lua-cjson if [ ! -d lua-cjson-2.1.0 ]; then tar zxf lua-cjson-2.1.0.tar.gz fi cd lua-cjson-2.1.0 sed "s/i686/x86_64/" /usr/share/lua/5.1/luarocks/config.lua > /usr/share/lua/5.1/luarocks/config.lua-tmp rm /usr/share/lua/5.1/luarocks/config.lua mv /usr/share/lua/5.1/luarocks/config.lua-tmp /usr/share/lua/5.1/luarocks/config.lua luarocks make
我的 OAuth 提供者類使用了 query-string 來發(fā)送認證的錯誤信息,我們也需要在我們的Lua腳本中為其提供支持:
local args = ngx.req.get_uri_args() if args.error and args.error == "access_denied" then ngx.status = ngx.HTTP_UNAUTHORIZED ngx.say("{"status": 401, "message": ""..args.error_description..""}") return ngx.exit(ngx.HTTP_OK) end
現在我們解決了基本的錯誤情況,我們要為訪問令牌設置cookie。在我的例子中,cookie會在訪問令牌過期前過期,所以我可以利用cookie來刷新訪問令牌。
local access_token = ngx.var.cookie_SGAccessToken if access_token then ngx.header["Set-Cookie"] = "SGAccessToken="..access_token.."; path=/;Max-Age=3000" end
現在,我們解決了錯誤響應的api,并儲存了access_token供后續(xù)訪問。我們現在需要確保OAuth認證過程正確啟動。下面,我們想要:
如果沒有access_token已經或將要存儲,開啟OAuth認證
如果query string的參數中有OAuth訪問代碼(access code),使用OAuth API檢索用戶的access_token
拒絕使用非法訪問代碼用戶的請求
閱讀nginx-Lua函數和變量的相關文檔可以解決一些問題,或許還能告訴你訪問特定請求/響應信息的各種方法。
此時,我們需要從我們的api接口獲取一個TOKEN。nginx-lua提供了 ngx.location.capture 方法,支持發(fā)起一個內部請求到redis,并接收響應。這意味著,我們不能直接調用類似于 http://seatgeek.com/ncaa-football-tickets,但我們可以用 proxy_pass 把這種外部鏈接包裝成內部請求。
我們通常約定給這樣的內部請求前面加一個_(下劃線), 用來阻止外部直接訪問。
-- 第一步,從api獲取獲取token if not access_token or args.code then if args.code then -- internal-oauth:1337/access_token local res = ngx.location.capture("/_access_token?client_id="..app_id.."&client_secret="..app_secret.."&code="..args.code) -- 終止所有非法請求 if res.status ~= 200 then ngx.status = res.status ngx.say(res.body) ngx.exit(ngx.HTTP_OK) end -- 解碼 token local text = res.body local json = cjson.decode(text) access_token = json.access_token end -- cookie 和 proxy_pass token 請求失敗 if not access_token then -- 跟蹤用戶訪問,用于透明的重定向 ngx.header["Set-Cookie"] = "SGRedirectBack="..nginx_uri.."; path=/;Max-Age=120" -- 重定向到 /oauth , 獲取權限 return ngx.redirect("internal-oauth:1337/oauth?client_id="..app_id.."&scope=all") end end
此時在Lua腳本中,應該已經有了一個可用的 access_token。我們可以用來獲取任何請求需要的用戶信息。在本文中,返回401表示沒有權限,403表示token過期,并且授權信息用簡單數字打包成json響應。
-- 確保用戶有訪問web應用的權限 -- internal-oauth:1337/accessible local res = ngx.location.capture("/_user", {args = { access_token = access_token } } ) if res.status ~= 200 then -- 刪除損壞的 token ngx.header["Set-Cookie"] = "SGAccessToken=deleted; path=/; Expires=Thu, 01-Jan-1970 00:00:01 GMT" -- 如果 token 損壞 ,重定向 403 forbidden 到 oauth if res.status == 403 then return ngx.redirect("https://seatgeek.com/oauth?client_id="..app_id.."&scope=all") end -- 沒有權限 ngx.status = res.status ngx.say("{"status": 503, "message": "Error accessing api/me for credentials"}") return ngx.exit(ngx.HTTP_OK) end
現在,我們已經驗證了用戶確實是經過身份驗證的并且具有某個級別的訪問權限,我們可以檢查他們的訪問級別,看看是否同我們所定義的任何當前端點的訪問級別有沖突。我個人在這一步刪除了SGAccessToken,以便用戶擁有使用不同的用戶身份登錄的能力,但這一做法用不用由你決定。
local json = cjson.decode(res.body) -- Ensure we have the minimum for access_level to this resource if json.access_level < 255 then -- Expire their stored token ngx.header["Set-Cookie"] = "SGAccessToken=deleted; path=/; Expires=Thu, 01-Jan-1970 00:00:01 GMT" -- Disallow access ngx.status = ngx.HTTP_UNAUTHORIZED ngx.say("{"status": 403, "message": "USER_ID"..json.user_id.." has no access to this resource"}") return ngx.exit(ngx.HTTP_OK) end -- Store the access_token within a cookie ngx.header["Set-Cookie"] = "SGAccessToken="..access_token.."; path=/;Max-Age=3000" -- Support redirection back to your request if necessary local redirect_back = ngx.var.cookie_SGRedirectBack if redirect_back then ngx.header["Set-Cookie"] = "SGRedirectBack=deleted; path=/; Expires=Thu, 01-Jan-1970 00:00:01 GMT" return ngx.redirect(redirect_back) end
現在我們只需要通過一些請求頭信息告知我們當前的應用誰登錄了就行了。您可以重用REMOTE_USER,如果你有需求的話,就可以用這個取代基本的身份驗證,而除此之外的任何事情都是公平的游戲。
-- Set some headers for use within the protected endpoint ngx.req.set_header("X-USER-ACCESS-LEVEL", json.access_level) ngx.req.set_header("X-USER-EMAIL", json.email)
我現在就可以像任何其它的站點那樣在我的應用程序中訪問這些http頭了,而不是用數百行代碼和大量的時間來重新實現身份驗證。
Nginx 和 Lua, 放在樹結構里面在這一點上,我們應該有一個可以用來阻擋/拒絕訪問的LUA腳本。我們可以將這個腳本放到磁盤上的一個文件中,然后使用access_by_lua_file配置來將它應用在我們的nginx站點中。在SeatGeek中,我們使用Chief來模板化輸出配置文件,雖然你可以使用Puppet,Fabric,或者其它任何你喜歡的工具。
下面是你可以用來使所有東西都運行起來的最簡單的nginx的網站。你也可能會想要檢查下access.lua - 在這里:gist.github.com/4196901 - 它是上面的lua腳本編譯后的文件。
# The app we are proxying to upstream production-app { server localhost:8080; } # The internal oauth provider upstream internal-oauth { server localhost:1337; } server { listen 80; server_name private.example.com; root /apps; charset utf-8; # This will run for everything but subrequests access_by_lua_file "/etc/nginx/access.lua"; # Used in a subrequest location /_access_token { proxy_pass http://internal-oauth/oauth/access_token; } location /_user { proxy_pass http://internal-oauth/user; } location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_max_temp_file_size 0; if (!-f $request_filename) { proxy_pass http://production-app; break; } } }進一步思考
雖然此設置運行的比較好,但是我想指出一些缺點:
上面的代碼是我們access_by_lua腳本的簡化。我們也處理保存POST提交的請求,JS加入到到頁面更新會話自動處理的令牌更新等,你可能不需要這些功能,而事實上,我不認為我需要它們,直到我們開始了我們在內部系統進行系統測試。
我們有一些結點,可以通過一定的后臺任務基本認證。這些被修改,數據是從一個外部存儲中檢索,如S3。注意,這并不總是可能的,所以使用的可能不是你想要的答案。
Oauth2只是我選擇的標準。在理論上,你可以使用facebook授權來實現類似的結果。你也可以將這種方法限速,或存儲在數據庫中的不同的訪問級別如在你的Lua腳本方便操作和檢索使用。如果你真的很無聊,你可以重新實現基本認證在Lua,這只需要你。
有沒有測試控制系統等。測試者會害怕當他們意識到這將是一段時間的集成測試。你可以重新運行上面的嘲笑為全球范圍內注入變量以及執(zhí)行腳本,但它不是理想的設置。
你還需要修改應用程序識別你的新的訪問標頭。內部工具將是最簡單的,但你可能需要為供應商軟件作出一定的讓步。
鏈接一些博客中的講解及研究實例
鏈接
SeatGeek Homebrew Formulae with customizable nginx
nginx_release.sh for building nginx debs
access.lua and nginx-site
另一些講解閱讀
HttpLuaModule
proxy_pass, like mod_proxy, but for nginx
Lua usage at Mixlr
OAuth for Apache
Homebrew OS X Package Manager
原文:Yak Shaving: Adding OAuth Support to Nginx via Lua
轉載自:開源中國 - Garfielt, LeoXu, DrZ, BoydWang, 學習者8, FreeZ
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/39051.html
摘要:最近為了分析公司的一個的應用性能,正好需要用到春神的那套的分析腳本,因此就立馬去搭建下環(huán)境依賴項依賴于注上面安裝的時候,的版本不能太高,不然會報錯,安裝失敗,目前使用的版本是該文的前提是你已經編譯安裝好了支持的版本,以及你已經 最近為了分析公司的一個 nginx + lua 的應用性能,正好需要用到春神的那套 nginx-lua 的分析腳本,因此就立馬去搭建下 環(huán)境: CentO...
摘要:最近為了分析公司的一個的應用性能,正好需要用到春神的那套的分析腳本,因此就立馬去搭建下環(huán)境依賴項依賴于注上面安裝的時候,的版本不能太高,不然會報錯,安裝失敗,目前使用的版本是該文的前提是你已經編譯安裝好了支持的版本,以及你已經 最近為了分析公司的一個 nginx + lua 的應用性能,正好需要用到春神的那套 nginx-lua 的分析腳本,因此就立馬去搭建下 環(huán)境: CentO...
摘要:用于方便地搭建能夠處理超高并發(fā)擴展性極高的動態(tài)應用服務和動態(tài)網關。安裝安裝依賴庫下載及安裝激活組件被用于構建。大部組件默認是激活的,也有部件不是。您需要通過以下選項在編譯的時候將它們各自激活,和。 OpenResty簡介 OpenResty 是一個基于 Nginx 與 Lua 的高性能 Web 平臺,其內部集成了大量精良的 Lua 庫、第三方模塊以及大多數的依賴項。用于方便地搭建能夠處...
閱讀 2283·2021-09-30 09:47
閱讀 2211·2021-09-26 09:55
閱讀 2938·2021-09-24 10:27
閱讀 1535·2019-08-27 10:54
閱讀 960·2019-08-26 13:40
閱讀 2486·2019-08-26 13:24
閱讀 2411·2019-08-26 13:22
閱讀 1721·2019-08-23 18:38