前言:
眼前我们对“nginx 鉴权”大约比较重视,看官们都需要分析一些“nginx 鉴权”的相关知识。那么小编在网上搜集了一些有关“nginx 鉴权””的相关文章,希望各位老铁们能喜欢,看官们快快来了解一下吧!## Basic Auth
Nginx 的 basic auth , 基于 ngx_http_auth_basic_module 模块。该模块是 builtin 模块,就是你安装 Nginx 的时候,它就一起装好了。
Basic Auth ,是一种简单的鉴权方式,不怎么安全。一般的 Basic Auth header 长这样:
Authorization: Basic $(base64_encode(username:password))
客户端通过对 username 和 password 进行 base64 加密, 放入 header 中。服务端获取 header 中的 Authorization , 然后 decode 鉴权。
在 nginx 中,通过 auth_basic 和 auth_basic_user_file 进行配置,该语法支持的上下文有
http, server, location, limit_except
具体配置如下:
server { listen 8088; location / { auth_basic "closed site"; auth_basic_user_file conf/htpasswd; root /xxx/html; index index.html index.htm; } error_page 404 /404.html; access_log logs/blog.access.log;}
关于 auth_basic 后面的字符串,要看各大浏览器是不是给面子了。我用 chrome 就不会显示这个,用 edge 会显示。
auth_basic_user_file 是用来存储账号密码的。官方支持使用 "HTTP Basic Authentication" 协议来验证用户名和密码。
emmm...
支持通过 crypt() 方法加密: 通过 htpasswd 或者 openssl passwd命令apache 基于 MD5 的 apr1 加密方式基于 RFC 2307 的实现
现在我们通过 htpasswd 生成一个:
# 直接在控制带输出htpasswd -nbd iyuhp admin# 输出到文本htpasswd -bdc passwd iyuhp admin# 再次追加到文本htpasswd -nbd iyuhp admin | tee -a passwd
这个时候,优雅的重启一波 nginx:
sudo nginx -s reload
然后访问一波, 发现已经会弹出一个用来登录的弹窗了。
如果通过命令行访问,可以直接通过 username:password@url访问:
curl 'localhost:8088' --header 'Authorization: Basic aXl1aHA6YWRtaW4='// orcurl iyuhp:admin@localhost:8088Auth Request
可能我有一个后端程序,所以我完全可以把这些 "繁重" 的用户密码管理,交给后台就好了。
哦,我并不是说搞个程序来生成账号密码,然后写到 auth_basic_user_file 文件中。我的意思是,这个校验的工作,让我来吧,Nginx 你去搞别的去!
这样做的好处是, 我可以方便的管理我的账号体系(这里所谓的账号体系,是一个夸张的说法,你一个小小的 blog ,还账号体系...)
emmm... 不管怎样,这看起来是件有趣的事情。
Nginx 自是能考虑到这些,已然提供了一个语法 auth_request , 意思是,你如果要访问这里,可以,得先通过我的验证才行。
auth_request uri | off; # off 表示关闭上文的验证,就是我这里我说了算,上级不要说话了# 该语法支持的上下文为:http, server, location
那么我们要怎么搞呢? 别急, 我先整张图:
client 向 Nginx 请求资源, 因为配置了 auth_request , Nginx 会先先 /auth 请求权限/auth 将请求转发给 BackendBackend 懵了, 你啥都没有,请求啥? 麻溜的返回 401 。注意 , 这里必须返回 401 (403 应该也可以,没有试... ),返回之前, 需要添加 http header:Header().Add("WWW-Authenticate", "Basic realm=\"up to you\"")第四步, 第五步,第六步,则是将最终的结果反馈给 client。 client 收到 Basic Auth , 弹出弹窗,让你填用户名密码。于是 client 这次携带 basic auth 再次请求 Nginx , 同样, 该请求最终被 Backend 处理。 Backend 一看,呦,原来是哥们啊, 得, 给你个 http code 200 吧。location /auth 一看, http code 200 , 赶紧缓存起来。 proxy_cache_valid 200 2h; 的意思就是对 http code 为 200 的请求,缓存 2h 。location / 一看,行, 你小子通过 auth 验证了, 这次的资源请求就放行吧。
通过上述步骤, 我们成功通过和 Backend 交互,并获得了指定资源的访问权。
关于 auth_request 的更多内容请看 这里 。 附上一份配置:
location / { auth_request /auth; root /xxx/html}location /auth { proxy_pass ; # 处理 auth 的后端 URL # 关闭不必要的数据传递 proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URI $request_uri; # proxy cache 设置 proxy_cache auth_cache; proxy_cache_valid 200 2h; proxy_cache_key $host$request_uri$cookie_session;}
使用 proxy_cache 前,需要先定义下 proxy_cache_path , 该语法的上下文是 http ,也就说,你需要在 http 层先定义该值, 比如:
proxy_cache_path path levels=1:2 keys_zone=one:10m;path : 指定 cache 缓存的位置levels[1] : 指定缓存的层级, 上面的配置可能最终长这样: path/a/bc/xxxbcakeys_zone : 相当于这个 cache 的 alias , 使用时, 就用这个名称。 10m 即缓存的大小。WeChat
我现在又觉得, 搞啥账号体系,我一个小小的 blog , 太耗时间了!
于是你想到了用验证码来搞好了。
那自然而然的想到了微信,想到利用公众号, 来搞个自动获取验证码呗。整个流程如下:
client 发起一个需要鉴权的资源请求Nginx 通过 auth_request 配置,转发给 ServerServer 告诉 Nginx , 需要重定向到 login 界面获取验证码,然后再提交Nginx 告诉 client ,同时 client 展示 login 界面client 通过扫码关注公众号,输入预设指令(如: yzm 等) ,获取验证码微信把获取验证码的请求转发给 server , server 生成并存储该验证码(设置验证码失效时间等),同时发送给微信client 获取验证码, 填写提交,server 验证通过, 告知 Nginx 放行
下面再来看下更详细的流程图:
这张图就很详细了,这里主要说几个注意的点:
3 - 4 步, 这里,返回 401 后,需要在 Nginx 中定义 error_page:error_page 401 =200 /login;
# 所以也可以这么做, 返回 403
error_page 401, 403 =200 /login;这样, 你返回 401 或者 403 http code 后, Nginx 就帮你重定向到了 /login4 - 5 步,这里, 你需要把原始的 URI 交给 server 记录下来,用于登录成功后的跳转:location /login {
proxy_pass ;
proxy_set_header X-Original-URI $request_uri; # 原始的 URI 请求, 交由 server 记录
}
9 - 10 步, server 端需要记录下自己生成的 code ,之后验证 code 需要12 - 14 步, 这里 client 请求的,其实是另一个 URI (如 POST /login), 这个 URI 是可以公开访问,不需要鉴权的15 步, 这里要注意, 鉴权成功后,要设置 header ,添加 Location URI , 告诉浏览器要跳转。这里有个坑 [2] ,返回的 http code 需要注意。16 - 22 , 事实上就是前面介绍的两个部分的内容,不再赘述
贴下具体的配置:
location / { auth_request /auth; error_page 401 =200 /login; root /xxx/html;}location /auth { proxy_pass ; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URI $request_uri; proxy_cache auth_cache; proxy_cache_valid 200 2h; # proxy key, 参见 reference [3] proxy_cache_key $host$request_uri$cookie_session; # 如果有 session 丢失的问题, 可以参考这条配置 # proxy_cookie_path ~*^/.* /;}location /login { proxy_pass ; proxy_set_header X-Original-URI $request_uri;}
WeChat 对接
关于 测试账号 的东西,我也了解的不多,因为我只对接了一个 。
登录微信公众号后, [开发] - [基本配置] - [服务器配置] ,配置服务器地址、令牌、消息加解密密钥(optional)
URL 需要是一个公网 ip 或者域名Token 你可以随意填写,只要你 server 端验证的 Token 和此处填写一致, 就好了。
提交时, 微信会先发一个验证请求,看看你的 server 是不是好的:
GET /handle?signature=xxx&echostr=123455×tamp=123456&nonce=1234
验证部分的文档, 在 这里 。主要步骤是:
token, timestamp, nonce 字典排序并拼接成一个字符串sha1 加密与 signature 比较是否一致
# golang 部分代码ary := []string(token, timestamp, nonce)sort.Strings(ary)encryptor := sha1.New()io.WriteString(encryptor, strings.Join(ary,""))mySignature := fmt.Sprintf("%x", encryptor.Sum(nil))// compare
文档说的是, 如果一致,就把 URI 中 echostr 原样返回。
嗯, 我返回了, 你特么说我验证失败, Token 有问题啥的???
为啥, 因为我返回了一个 string 类型的字符串! F**k , 这导致微信接收的时候, 在原值上多了一对双引号。
哎, 我最终返回了一个 int 类型的 echostr ,虽然我不知道这个 echostr 是不是会存在 str ...
这一步完成后, 当你通过公众号发送消息时, 微信就会把这个消息转发给你的 server 。它的格式如下:
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[this is a test]]></Content> <MsgId>1234567890123456</MsgId></xml>
这就是在 server 端定义几个结构体,解析下就好了:
type CdataString struct { Value string `xml:",cdata"`}type MsgXml struct { XMLName xml.Name `xml:"xml"` ToUserName CdataString `xml:"ToUserName"` FromUserName CdataString `xml:"FromUserName"` CreateTime int64 `xml:"CreateTime"` MsgType CdataString `xml:"MsgType"` Content CdataString `xml:"Content"` MsgId int64 `xml:"MsgId,omitempty"`}
需要注意三个点:
你的 xml 的 root tag 需要是 xml几乎所有的字段,都需要用 cdata 格式包裹如果你在上面选择了对消息加密,则给你的数据包,可能是长这样的:
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <Encrypt> <![CDATA[QC1jqzkTmPbaCkcB7ruVHe6k=...]]> </Encrypt></xml>
你需要用你的 EncodingAESKey 先解密才行, 这里 是微信的文档, 不再详述。至此, 你就可以愉快的通过微信公众号,来玩转你的 blog 了。是不是很有趣? 可怜我在五一的美好假期里,熬了一个通宵搞这东西...
Referenct[1]
请参考 proxy_cache_path
[2]
请参考 这里
301 :http 1.0 ,永久移除, POST --> GET302 : http 1.0 ,暂时移除, POST -> POST (我用的 chrome 会如此)303 : see other , POST -> GET307 : 临时重定向, POST -> POST308 : 永久重定向, POST -> POST
开始通过 POST /login 进行重定向时, 总是用 POST 方法调用 Location 的地址,导致 Method Not Allowed. 此时使用的 http code 是 307,后来用 302 , 最后改为 303 搞定
[3]
对于使用哪种变量作为 proxy_cache 的 key ,我想了很久。
最开始用一堆诸如 $host$remote_addr$request_uri , 后来发现一个问题,对于同一个局域网下的用户, 只要 $request_uri 一致, 那这个 cache 就是公用的, 这明显不科学啊!
后来 google 了一把, 决定用 session 作为 proxy_cache 的 key 。这要求你在鉴权的时候,在 cookie 中写入你的 session,然后通过 $cookie_session 获取即可。
当然,你可以往 cookie 里写 anything else ,如 wtf ,然后你就可以用 $cookie_wtf 作为你的 proxy key 了, 完美。
所以, 这里的 cookie_xxx , 值得是 client 的端 cookie 中的一个 key 。
标签: #nginx 鉴权