介绍

权限认证是接口开发中不可避免的问题,权限认证包括两个方面

  1. 接口需要知道调用的用户是谁
  2. 接口需要知道该用户是否有权限调用

第1个问题偏向于架构,第2个问题更偏向于业务,因此考虑在架构层解决第1个问题,以达到以下目的

  1. 所有请求被保护的接口保证是合法的(已经认证过的用户)
  2. 接口可以从请求头中获取当前用户信息
  3. 每个请求都有uuid用于标识

JWT(JSON Web Token)目前是应用最广的接口权限方案,具有无状态,跨系统,多语言多平台支持等特点,如果能在网关层实现JWT验证不仅可以避免代码入侵还能为整个后台提供统一的解决方案,目前客户网关使用Nginx,但社区版Nginx中没有JWT模块,自己实现不现实,因此选择OpenResty作为网关层, 据官网介绍,OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。本质上就是一个Nginx+Lua的集成软件.
整体架构如图:

实现

环境

[root@docker ~]# cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)
[root@docker ~]# more /proc/version
Linux version 3.10.0-693.el7.x86_64 (builder@kbuilder.dev.centos.org) (gcc version 4.8.5 20150623 (Red Hat 4.
8.5-16) (GCC) ) #1 SMP Tue Aug 22 21:09:27 UTC 2017

安装OpenResty

OpenRestry安装很简单,可以在这里找到不同版本操作系统安装文档,本次使用的环境是CentOS Linux release 7.4

[root@docker ~]# yum install yum-utils
[root@docker ~]# yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
[root@docker ~]# yum install openresty
[root@docker ~]# yum install openresty-resty

系统默认安装在/usr/local/openresty/目录下,版本如下

[root@docker openresty]# cd /usr/local/openresty/bin/
[root@docker bin]# ./openresty -v
nginx version: openresty/1.13.6.2

可以将OpenResty目录加到PATH里,方便使用.

修改nginx.conf文件测试是否安装成功

tee /usr/local/openresty/nginx/conf/nginx.conf <<-'EOF'
worker_processes 1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
server {
listen 8080;
location / {
default_type text/html;
content_by_lua '
ngx.say("<p>hello, world</p>")
';
}
}
}
EOF
[root@docker conf]# openresty -s reload
[root@docker conf]# curl localhost:8080
<p>hello, world</p>

安装JWT模块

这里使用JWT官方推荐Lua实现库,项目地址为https://github.com/SkyLothar/...,有趣的是,这个项目的介绍是这么写的JWT For The Great Openresty,看来是为OpenResty量身定做.github上有安装教程,但一方面有些第三方库的安装文档没有提及,另一方面有些内容没有用到安装的时候可以跳过,这里将完整安装步骤重新整理了下.

  1. 在release页面https://github.com/SkyLothar/...下载项目源码,截止到目前最新版为v0.1.11
  2. 下载hmac源码,截止到目前项目还未release,只能下载项目里的源文件https://github.com/jkeys089/l...
  3. 在服务器创建目录/usr/local/openresty/nginx/jwt-lua/resty,将第1步压缩包中目录lua-resty-jwt-0.1.11/lib/resty/下的所有lua文件和第2步中的hmac.lua文件拷贝到该目录下,文件列表如下.
[root@docker resty]# pwd
/usr/local/openresty/nginx/jwt-lua/resty
[root@docker resty]# ll
total 60
-rwxr-xr-x. 1 root root 11592 Jul 18 10:40 evp.lua
-rw-r--r--. 1 root root 3796 Jul 18 10:40 hmac.lua
-rwxr-xr-x. 1 root root 27222 Jul 18 10:40 jwt.lua
-rwxr-xr-x. 1 root root 15257 Jul 18 10:40 jwt-validators.lua

修改nginx.conf验证是否生效

tee /usr/local/openresty/nginx/conf/nginx.conf <<-'EOF'
worker_processes 1;
error_log logs/error.log info;
events {
worker_connections 1024;
}
http {
lua_package_path "/usr/local/openresty/nginx/jwt-lua/?.lua;;";
server {
listen 8080;
default_type text/plain;
location = / {
content_by_lua '
local cjson = require "cjson"
local jwt = require "resty.jwt" local jwt_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" ..
".eyJmb28iOiJiYXIifQ" ..
".VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY"
local jwt_obj = jwt:verify("lua-resty-jwt", jwt_token)
ngx.say(cjson.encode(jwt_obj))
';
}
location = /sign {
content_by_lua '
local cjson = require "cjson"
local jwt = require "resty.jwt" local jwt_token = jwt:sign(
"lua-resty-jwt",
{
header={typ="JWT", alg="HS256"},
payload={foo="bar"}
}
)
ngx.say(jwt_token)
';
}
}
}
EOF
[root@docker resty]# curl localhost:8080
{"signature":"VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY","reason":"everything is awesome~ :p","valid":true,"raw_header":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9","payload":{"foo":"bar"},"header":{"alg":"HS256","typ":"JWT"},"verified":true,"raw_payload":"eyJmb28iOiJiYXIifQ"}

验证通过,jwt模块安装完毕

自定义验证逻辑

上面jwt模块还无法用于生产环境,有几个问题没解决

  1. jwt token目前是写死在配置文件里,生产需要从header Authorization中获取
  2. 验证失败目前返回是200生产需要返回401
  3. 需要配置反向代理并且将用户信息放在代理header上

创建文件/usr/local/openresty/nginx/jwt-lua/resty/nginx-jwt.lua

local jwt = require "resty.jwt"
local cjson = require "cjson"
--your secret
local secret = "5pil6aOO5YaN576O5Lmf5q+U5LiN5LiK5bCP6ZuF55qE56yR" local M = {} function M.auth(claim_specs)
-- require Authorization request header
local auth_header = ngx.var.http_Authorization if auth_header == nil then
ngx.log(ngx.WARN, "No Authorization header")
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end ngx.log(ngx.INFO, "Authorization: " .. auth_header) -- require Bearer token
local _, _, token = string.find(auth_header, "Bearer%s+(.+)") if token == nil then
ngx.log(ngx.WARN, "Missing token")
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end ngx.log(ngx.INFO, "Token: " .. token) local jwt_obj = jwt:verify(secret, token)
if jwt_obj.verified == false then
ngx.log(ngx.WARN, "Invalid token: ".. jwt_obj.reason)
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end ngx.log(ngx.INFO, "JWT: " .. cjson.encode(jwt_obj)) -- write the uid variable
ngx.var.uid = jwt_obj.payload.sub
end return M

修改配置文件nginx.conf

worker_processes  1;
error_log logs/error.log info;
events {
worker_connections 1024;
}
http {
upstream tomcat{
server localhost:80;
}
lua_package_path "/usr/local/openresty/nginx/jwt-lua/?.lua;;";
server {
listen 8080;
set $uid '';
location / {
access_by_lua '
local jwt = require("resty.nginx-jwt")
jwt.auth()
';
default_type application/json;
proxy_set_header uid $uid;
proxy_pass http://tomcat;
}
}
}

这里后台启动了一台tomcat并设置监听端口为80,tomcat上部署了一个示例的war包,代码逻辑较简单,就是输出所有的header,代码如下:

package asan.demo;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.*;
import javax.servlet.http.*; public class JWTDemoService extends HttpServlet {
private static final String CONTENT_TYPE = "text/html; charset=UTF-8"; public void init(ServletConfig config) throws ServletException {
super.init(config);
} public void service(HttpServletRequest request,
HttpServletResponse response) throws ServletException,
IOException {
response.setContentType(CONTENT_TYPE);
PrintWriter out = response.getWriter();
Enumeration em=request.getHeaderNames();
while(em.hasMoreElements()){
String key=(String)em.nextElement();
String value=(String)request.getHeaders(key).nextElement();
out.println(String.format("%s ==> %s", key,value));
}
out.close();
}
}

重启OpenResty测试,如果没有指定jwt token信息返回401

[root@docker conf]# curl http://localhost:8080/jwtdemo/service
<html>
<head><title>401 Authorization Required</title></head>
<body bgcolor="white">
<center><h1>401 Authorization Required</h1></center>
<hr><center>openresty/1.13.6.2</center>
</body>
</html>

指定jwt token

[root@docker conf]# curl -i http://localhost:8080/jwtdemo/ -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJ5YXlhIiwiaWF0IjoxNTMxODkyNzE3LCJpc3MiOiJ5YXlhIiwic3ViIjoieWF5YSIsImV4cCI6MTUzMTkyODcxN30.W5UXlwKHSrpUAYbfoF-fTBTS9Enm1wsvCKNQm0yLSfQ'
HTTP/1.1 200
Server: openresty/1.13.6.2
Date: Wed, 18 Jul 2018 05:52:13 GMT
Content-Type: text/html;charset=UTF-8
Content-Length: 298
Connection: keep-alive uid ==> yaya
host ==> tomcat
connection ==> close
user-agent ==> curl/7.29.0
accept ==> */*
authorization ==> Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJ5YXlhIiwiaWF0IjoxNTMxODkyNzE3LCJpc3MiOiJ5YXlhIiwic3ViIjoieWF5YSIsImV4cCI6MTUzMTkyODcxN30.W5UXlwKHSrpUAYbfoF-fTBTS9Enm1wsvCKNQm0yLSfQ

从结果上看,后台服务已经获取到uid这个header
至于请求用到jwt token可以从任意平台生成只要保证secret一样即可,根据官网介绍,该库目前支持到jwt生成算法如图:

uuid生成

为每个请求生成唯一的uuid码可以将网关层上的请求和应用层的请求关联起来,对排查问题,接口统计都非常有用.

创建文件/usr/local/openresty/nginx/jwt-lua/resty/uuid.lua
local M  = {}
local charset = {} do -- [0-9a-zA-Z]
for c = 48, 57 do table.insert(charset, string.char(c)) end
for c = 65, 90 do table.insert(charset, string.char(c)) end
for c = 97, 122 do table.insert(charset, string.char(c)) end
end
function M.uuid(length)
local res = ""
for i = 1, length do
res = res .. charset[math.random(1, #charset)]
end
return res
end
return M
修改配置文件nginx.conf
worker_processes  1;
error_log logs/error.log info;
events {
worker_connections 1024;
}
http {
upstream tomcat{
server localhost:80;
}
lua_package_path "/usr/local/openresty/nginx/jwt-lua/?.lua;;";
server {
listen 8080;
set $uid '';
set $uuid '';
location / {
access_by_lua '
local jwt = require("resty.nginx-jwt")
jwt.auth()
local u = require("resty.uuid")
ngx.var.uuid = u.uuid(64)
';
default_type application/json;
proxy_set_header uid $uid;
proxy_set_header uuid $uuid;
proxy_pass http://tomcat;
}
}
}

重启OpenResty,测试

[root@docker conf]# openresty -s reload
[root@docker conf]# curl -i http://localhost:8080/jwtdemo/ -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJ5YXlhIiwiaWF0IjoxNTMxODk0MDA3LCJpc3MiOiJ5YXlhIiwic3ViIjoieWF5YSIsImV4cCI6MTUzMTkzMDAwN30.vQvpQpIHCmK5QBgIoRR8jhIGeYlHOMYySIr4gHvoZFE'
HTTP/1.1 200
Server: openresty/1.13.6.2
Date: Wed, 18 Jul 2018 08:05:45 GMT
Content-Type: text/html;charset=UTF-8
Content-Length: 372
Connection: keep-alive uid ==> yaya
uuid ==> nhak5eLjQZ73yhAyHLTgZnSBeDa8pa1p3pcpBFvJ4Mv1fkY782UgVr8Islheq03l
host ==> tomcat
connection ==> close
user-agent ==> curl/7.29.0
accept ==> */*
authorization ==> Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJ5YXlhIiwiaWF0IjoxNTMxODk0MDA3LCJpc3MiOiJ5YXlhIiwic3ViIjoieWF5YSIsImV4cCI6MTUzMTkzMDAwN30.vQvpQpIHCmK5QBgIoRR8jhIGeYlHOMYySIr4gHvoZFE

可以看到,多了一个uuid的请求头

jwt token生成java示例

这里提供一个生成jwt token的java示例

package com.yaya;

import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map; /**
* @Description:
* @author: jianfeng.zheng
* @since: 2018/7/5 下午9:56
* @history: 1.2018/7/5 created by jianfeng.zheng
*/
public class JWTDemo { public static final String SECRET="5pil6aOO5YaN576O5Lmf5q+U5LiN5LiK5bCP6ZuF55qE56yR"; public static String createJWT(String uid, long ttlMillis) throws Exception {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
Key signingKey = new SecretKeySpec(SECRET.getBytes(), signatureAlgorithm.getJcaName()); Map<String,Object> header=new HashMap<String,Object>();
header.put("typ","JWT");
header.put("alg","HS256");
JwtBuilder builder = Jwts.builder().setId(uid)
.setIssuedAt(now)
.setIssuer(uid)
.setSubject(uid)
.setHeader(header)
.signWith(signatureAlgorithm, signingKey);
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}
return builder.compact();
} public static void main(String[]cmd) throws Exception {
String s=createJWT("yaya",36000000);
System.out.println("Bearer "+s);
} }

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.yaya</groupId>
<artifactId>jwtdemo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
</dependencies>
</project>

写在最后

这里只是解决了文章开头提到的第一个问题,接口需要知道是谁调用了接口,第二个问题,用户能不能调接口目前考虑用aop在应用层实现,后续也会继续更新.

Nginx实现JWT验证-基于OpenResty实现的更多相关文章

  1. Nginx集群之基于Redis的WebApi身份验证

    目录 1       大概思路... 1 2       Nginx集群之基于Redis的WebApi身份验证... 1 3       Redis数据库... 2 4       Visualbox ...

  2. 基于OpenResty与Consul实现服务网格ServiceMesh

    一.逻辑架构 1.基于OpenResty开发智能代理: 利用其动态可编程特性,动态化配置nginx服务路由: 2.需要向OpenResty添加weibo开源的upsync服务发现模块: 3.基于con ...

  3. Polaristech 刘洋:基于 OpenResty/Kong 构建边缘计算平台

    2019 年 3 月 23 日,OpenResty 社区联合又拍云,举办 OpenResty × Open Talk 全国巡回沙龙·北京站,Polaristech 技术专家刘洋在活动上做了<基于 ...

  4. golang学习笔记10 beego api 用jwt验证auth2 token 获取解码信息

    golang学习笔记10 beego api 用jwt验证auth2 token 获取解码信息 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放 ...

  5. 基于openresty的https配置实践

    最近机器人项目的子项目,由于和BAT中的一家进行合作,人家要求用HTTPS连接,于是乎,我们要改造我们的nginx的配置,加添HTTPS的支持. 当然了,HTTPS需要的证书,必须是认证机构颁发的,这 ...

  6. 打破基于OpenResty的WEB安全防护(CVE-2018-9230)

    原文首发于安全客,原文链接:https://www.anquanke.com/post/id/103771 0x00 前言 ​ OpenResty® 是一个基于 Nginx 与 Lua 的高性能 We ...

  7. 基于 OpenResty 实现一个 WS 聊天室

    基于 OpenResty 实现一个 WS 聊天室 WebSocket WebSocket 协议分析 WebSocket 协议解决了浏览器和服务器之间的全双工通信问题.在WebSocket出现之前,浏览 ...

  8. 基于OpenResty和Node.js的微服务架构实践

    什么是微服务? 传统的单体服务架构是单独服务包,共享代码与数据,开发成本较高,可维护性.伸缩性较差,技术转型.跨语言配合相对困难.而微服务架构强调一个服务负责一项业务,服务可以单独部署,独立进行技术选 ...

  9. 基于 OpenResty 的动态服务路由方案

    2019 年 5 月 11 日,OpenResty 社区联合又拍云,举办 OpenResty × Open Talk 全国巡回沙龙武汉站,又拍云首席布道师在活动上做了< 基于 OpenResty ...

随机推荐

  1. 并发05--JAVA并发容器、框架、原子操作类

    一.ConcurrentHashMap的实现原理与使用 1.为什么要使用ConsurrentHashMap 两个原因,hashMap线程不安全(多线程并发put时,可能造成Entry链表变成环形数据结 ...

  2. postman不能启动的问题解决

    1.postman启动不了,启动时提示“postman resolving transporter buffer”,不能正常启动 第一步,删除:在chrome-更多工具-扩展程序里面删除了postma ...

  3. 调用微信内置的方法及wx.config的配置问题

    首先请看网址: https://www.w3cschool.cn/weixinkaifawendang/h8ap1qe5.html 重点说下怎么配置wx.config(为了安全,所有的参数都在服务端获 ...

  4. JSTL版本问题

    JSTL的版本有很多,和Servlet,jsp,tomcat都有版本兼容的问题,以下是收藏的相关帖子 JSTL不同版本和EL表达式的关联  https://www.cnblogs.com/yangzh ...

  5. Dubbo远程调用之公司内部提供的服务

    公司内部提供的服务 一家对外提供服务的公司,例如百度,腾讯,阿里,京东,58 同城等,公司内部有多个事业群,事业部门,每个事业部门内部又有若干个子部门,子部门里面有多个不同的小组负责各自的业务.提供对 ...

  6. H5禁止微信内置浏览器调整字体大小

    微信webview内置了调整字体大小的功能,用户可以根据实际情况进行调节.但是这也会导致字体大小改变以后,出现页面布局错乱的情况. 1.iOS的解决方案是覆盖掉微信的样式: body { /* IOS ...

  7. Python——数据库like模糊查询

    在Python中%是一个格式化字符,所以如果需要使用%则需要写成%%.将在Python中执行的sql语句改为:sql = "SELECT * FROM table_test WHERE va ...

  8. Python math 、cmath

    1.math dir(math) 2.cmath 复数运算

  9. shell专题(三):Shell脚本入门

    1.脚本格式 脚本以#!/bin/bash开头(指定解析器) 2.第一个Shell脚本:helloworld (1)需求:创建一个Shell脚本,输出helloworld (2)案例实操: [atgu ...

  10. 数据分析06 /pandas高级操作相关案例:人口案例分析、2012美国大选献金项目数据分析

    数据分析06 /pandas高级操作相关案例:人口案例分析.2012美国大选献金项目数据分析 目录 数据分析06 /pandas高级操作相关案例:人口案例分析.2012美国大选献金项目数据分析 1. ...