centos测试
使用阿里云的yum repo
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d.bak/CentOS-Base.repo
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
yum clean all
yum makecache
yum repolist
需要更新yum组件,否则安装openssl-devel提示冲突
yum update
安装所需yum组件
#luaossl的依赖项
yum install epel-release
yum install openssl-devel
#lua包管理器
yum install luarocks
yum install lua-devel
安装所需lua组件
#ssl组件
luarocks install luaossl
#base64组件,openresty镜像中不用执行该命令,如执行会提示Error: No results matching query were found.
luarocks install mime
生成证书命令
openssl genrsa -out private.key 2048
openssl rsa -in private.key -pubout -out public.pem
openssl rsa -in private.key -outform der -out private.der
验证签名lua脚本
local pkey = require("openssl.pkey")
local digest = require("openssl.digest")
local mime = require("mime")
-- 读取pem格式公钥证书文件
local pk_cert_data = assert(io.open("/root/public.pem", "rb")):read("*a")
local pkey =pkey.new()
pkey:setPubinsiglicKey(pk_cert_data)
-- 原始数据
local data = "Hello, World!"
-- 创建SHA256摘要对象
local digest_data = digest.new("sha256")
print(digest_data)
-- 更新摘要
digest_data:update(data)
--使用java项目中签名方法生成的签名
local signatures ="fwOupXYu/6bl8RqMJWztrU6trsKkGXSh2nyQyHHmiuqlyCXWm5sE3zCzqoYVB5ppYIwzYGWYmhEH9jRzmB6R7oZXDLKnom/BaRnUMmrHjmgaI5AL9jAAyLKckNlOl1ptS35a7A3PyFDTBkOxq6gEZjJhMHxJVe4W98JJMpZqzZ5nM5nOVDKRsUrziSO0M7u/nDt03IoxB8wN34ljkjhXbT8v6my85QYTH3e0Pj9dcBiUV9Sv04GOCEr+eE9HznWirb169+MsulGERi2ILZAf+kTWJ/I5TYrGvbviowhtvRU4j1zJvnVRE1rjOEZpvxmBUAoSXpWOSUcqlVKAwsLbaQ=="
local signature =mime.unb64(signatures)
-- 打印签名
print("Signature:", signature)
-- 验证签名
local verify_result = pkey:verify(signature, digest_data)
-- 判断验签结果
if verify_result == true then
print("Signature is valid.")
elseif verify_result == false then
print("Signature is invalid.")
else
print("Verification failed.")
end
输出
userdata: 0x21973e8
Signature: ��v.�����%l��N��¤t��|��q���%֛��0����i`�3`e���4s���W
���o�i�2jǎh#�
�0
Signature is valid.
生成签名的方法(AuthUtil)
public static void main(String[] args) {
String json2String = "Hello, World!";
String sign = RsaUtil.standardSign(RsaUtil.getPrivateKeyFromDer("/opt/private.der"), json2String);
System.out.println(sign);
PublicKey publicKey = RsaUtil.getPublicKeyFromPem("/opt/public.pem");
AuthUtil authUtil = new AuthUtil();
boolean b = authUtil.standardVerifyMsgByPemPubKey(sign, json2String, publicKey);
System.out.println(b);
}
/**
* 获取私钥
*
* @param filename 私钥路径
* @return 私钥
*/
@SneakyThrows
public static PrivateKey getPrivateKeyFromDer(String filename) {
Security.addProvider(new BouncyCastleProvider());
byte[] keyBytes = Files.readAllBytes(Paths.get(filename));
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance(KEY_ALGORITHM);
return kf.generatePrivate(spec);
}
/**
* 生成签名
*/
public static String standardSign(PrivateKey priKey, String tobeSigned) {
try {
Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);
sign.initSign(priKey);
sign.update(tobeSigned.getBytes(StandardCharsets.UTF_8));
byte[] signed = sign.sign();
return Base64.encodeAsString(signed);
} catch (Exception e) {
throw new BizException(407, "签名失败");
}
}
/**
* 获取公钥
*
* @param fileName 公钥路径
* @return 公钥
*/
@SneakyThrows
public static PublicKey getPublicKeyFromPem(String fileName) {
File file = new File(fileName);
String parentPath = file.getParent();
String filename = file.getName();
byte[] key = Files.readAllBytes(Paths.get(parentPath, filename));
Security.addProvider(new BouncyCastleProvider());
final PemObject pemObject;
try (PemReader pemReader = new PemReader(new InputStreamReader(new ByteArrayInputStream(key)))) {
pemObject = pemReader.readPemObject();
}
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(pemObject.getContent());
KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
return factory.generatePublic(pubKeySpec);
}
/**
* 签名验证
*
* @param sign 待验证码
* @param param 文本
* @param pubKey 公钥
* @return 验证结果
*/
@SneakyThrows
public boolean standardVerifyMsgByPemPubKey(String sign, String param, PublicKey pubKey) {
log.info("sign: {}", sign);
log.info("param: {}", param);
Signature verify = Signature.getInstance(SIGNATURE_ALGORITHM);
verify.initVerify(pubKey);
verify.update(param.getBytes(StandardCharsets.UTF_8));
boolean verify1 = verify.verify(Base64.getDecoder().decode(sign));
log.info(" verify result : {}", verify1);
return verify1;
}
输出
fwOupXYu/6bl8RqMJWztrU6trsKkGXSh2nyQyHHmiuqlyCXWm5sE3zCzqoYVB5ppYIwzYGWYmhEH9jRzmB6R7oZXDLKnom/BaRnUMmrHjmgaI5AL9jAAyLKckNlOl1ptS35a7A3PyFDTBkOxq6gEZjJhMHxJVe4W98JJMpZqzZ5nM5nOVDKRsUrziSO0M7u/nDt03IoxB8wN34ljkjhXbT8v6my85QYTH3e0Pj9dcBiUV9Sv04GOCEr+eE9HznWirb169+MsulGERi2ILZAf+kTWJ/I5TYrGvbviowhtvRU4j1zJvnVRE1rjOEZpvxmBUAoSXpWOSUcqlVKAwsLbaQ==
18:23:19.195 [main] INFO com.rkg.test.AuthUtil - sign: fwOupXYu/6bl8RqMJWztrU6trsKkGXSh2nyQyHHmiuqlyCXWm5sE3zCzqoYVB5ppYIwzYGWYmhEH9jRzmB6R7oZXDLKnom/BaRnUMmrHjmgaI5AL9jAAyLKckNlOl1ptS35a7A3PyFDTBkOxq6gEZjJhMHxJVe4W98JJMpZqzZ5nM5nOVDKRsUrziSO0M7u/nDt03IoxB8wN34ljkjhXbT8v6my85QYTH3e0Pj9dcBiUV9Sv04GOCEr+eE9HznWirb169+MsulGERi2ILZAf+kTWJ/I5TYrGvbviowhtvRU4j1zJvnVRE1rjOEZpvxmBUAoSXpWOSUcqlVKAwsLbaQ==
18:23:19.199 [main] INFO com.rkg.test.AuthUtil - param: Hello, World!
18:23:19.199 [main] INFO com.rkg.test.AuthUtil - verify result : true
true
参考文件
https://github.com/zhaozg/lua-openssl#documentation
参考附件.User Guide to luaossl, Comprehensive OpenSSL Module for Lua
构建ssl验签镜像
基础镜像制作
启动容器
docker run -it openresty/openresty:latest bash
安装依赖
#进入openresty容器
#更新包索引
apt update
#安装openssl lua
apt install -y openssl libssl-dev luarocks lua5.4
#ssl组件
luarocks install luaossl
#安装resty-http组件
luarocks install lua-resty-http
#创建机构证书存放路径,请注意这个路径是固定的不要修改,如果修改下面proxy_route_gateway_rsa.lua脚本中publicCertFolder值也要一并修改
mkdir -p /usr/local/openresty/nginx/gateway-public-cert
#更改文件夹所属用户为nobody,否则下面脚本创建证书文件时提示没有权限操作
chown nobody /usr/local/openresty/nginx/gateway-public-cert
制作镜像
#提交镜像
docker commit 上面的容器id openresty/openresty:lua-openssl-base
#推送到harbor,如果harbor已存在该镜像为了避免出现其他问题,建议先删除harbor上的同名镜像,其他机器使用时一样,如果有同名镜像建议先删除
docker push openresty/openresty:lua-openssl-base
打开nginx日志开关
打开nginx开关,方便排查日志,修改nginx.conf打开error_log的注释
#如果需要查看lua脚本输出的日志打开这里的配置
error_log logs/error.log;
修改docker-compose.yml
添加command命令 ["/usr/local/openresty/bin/openresty", "-g", "daemon off;"],必须要添加这个,不添加容器启动不了
version: '3.4'
services:
insightext:
image: openresty/openresty:lua-openssl-base
container_name: insightext
hostname: insightext
restart: always
ports:
- 1443:1443
- 9443:9443
- 19001:19001
- 19002:19002
- 19003:19003
- 19101:19101
volumes:
#设置时区使用宿主机时区,防止时间少8小时
- ./localtime:/etc/localtime:ro
#替换nginx.conf
- ./nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf
# 日志
- ./logs:/usr/local/openresty/nginx/logs
# https 证书
- ./config/ssl:/usr/local/openresty/nginx/ssl
# 子配置文件
- ./config/conf.d:/usr/local/openresty/nginx/conf/conf.d
# lua文件夹目录
- ./lua/:/etc/lua
command: ["/usr/local/openresty/bin/openresty", "-g", "daemon off;"]
networks:
test:
ipv4_address: 172.18.0.211
networks:
test:
external: true
修改java相关的http接口引用proxy_route_gateway_rsa.lua
修改nginx conf文件中,java使用的http端口中content_by_lua_file的文件名为proxy_route_gateway_rsa.lua;
server {
listen 1443 ssl;
server_name localhost;
add_header X-XSS-Protection 1;
add_header X-Content-Type-Options "nosniff";
add_header Content-Security-Policy "script-src 'self';";
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
add_header Set-Cookie “Secure”;
proxy_intercept_errors on;
large_client_header_buffers 4 1024k;
client_max_body_size 1024m;
ssl_certificate /usr/local/openresty/nginx/ssl/http/server.crt;
ssl_certificate_key /usr/local/openresty/nginx/ssl/http/server.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2;
location / {
content_by_lua_file /etc/lua/proxy_route_gateway_rsa.lua;
}
}
创建proxy_route_gateway_rsa.lua
在lua文件夹中创建proxy_route_gateway_rsa.lua,内容如下
gatewayUrl是后端提供了一个接口,返回ssl证书,这里可以根据需要自定义
--后端gateway地址,注意结尾没有/,如果和gateway在同一个docker网络中,可以直接写http://insightgateway:10000
local gatewayUrl = "http://10.10.1.26:10000"
--路由转发自定义请求头
local header_name = 'proxy'
--路由转发的实际地址,header_name中json的哪个key
local route_name = 'route1'
-- Lua数组表示不需要登录认证的URL路径
ignoredUrlArr = {
"/auth/auth/login",
"/auth/auth/aes/key",
"/base/admin/versions"
}
--------------------------------下面内容,不要随意修改,如需修改请联系任凯歌--------------------------------------------
--------------------------------字符串切割函数----------------------------------------------------------------------
function split(input, delimiter)
local arr = {}
string.gsub(input, '[^' .. delimiter .. ']+', function(w) table.insert(arr, w) end)
return arr
end
--------------------------------字符串切割函数----------------------------------------------------------------------
--------------------------------检查变量suffix是否以数组元素结尾-----------------------------------------------------------
function endsWith(str, suffix)
return string.sub(str, -string.len(suffix)) == suffix
end
--------------------------------检查变量a是否以数组元素结尾-----------------------------------------------------------
--机构公钥存放路径,注意必须写/usr/local/openresty/nginx/gateway-public-cert/,这个目录在基础镜像中手动创建并chown nobody gateway-public-cert了
local publicCertFolder="/usr/local/openresty/nginx/gateway-public-cert/"
--------------------------------获取请求头信息----------------------------------------------------------------------
--获取机构信息
local originInstitutionHeader = ngx.req.get_headers()["originInstitution"] or ''
--截取机构信息
local originInstitutionHeaderArr = split(originInstitutionHeader, ",")
--获取机构id
local institutionId = originInstitutionHeaderArr[1]
--获取superAdmin
local superAdminHeader = ngx.req.get_headers()["superAdmin"] or ''
--获取签名
local signHeader = ngx.req.get_headers()["sign"] or ''
--------------------------------获取请求头信息----------------------------------------------------------------------
--------------------------------请求后端gateway生成证书文件放到本地--------------------------------------------------
function createInstitutionPublicCert(institutionId)
if (institutionId ~= nil and institutionId ~= '') then
local url = gatewayUrl .. "/publickey?institutionId=" .. institutionId
local httpc = require("resty.http").new()
-- Single-shot requests use the `request_uri` interface.
local res, err = httpc:request_uri(url, {
method = "GET",
body = "",
-- 禁用证书验证
ssl_verify = false,
headers = {
},
})
if not res then
ngx.log(ngx.ERR, "Failed to request gateway get institution public cert", err)
return false
end
--获取证书
local body = res.body
if (body == nil or body == '') then
ngx.log(ngx.ERR, "unattained institutionId:" ..
institutionId .. " public cert from insightgateway,please check")
end
--注意必须写到/usr/local/openresty/nginx/gateway-public-cert/下,这个目录在基础镜像中手动创建并chown nobody gateway-public-cert
local fileName = publicCertFolder .. institutionId .. ".pem"
local function write_to_file(str)
local file, err = io.open(fileName, "w")
if file then
file:write(str)
file:close()
end
end
-- 将字符串写入文件
write_to_file(body)
return true
end
ngx.log(ngx.ERR, "Failed to request gateway get institution public cert error, institutionId is null, please check")
return false
end
--------------------------------请求后端gateway生成证书文件放到本地--------------------------------------------------
--------------------------------端口转发,实现proxy_pass功能----------------------------------------------------------
function proxyPass(proxy_target)
-- 获取原始请求的URI
local uri = ngx.var.request_uri
local http = require "resty.http"
local httpc = http.new()
ngx.req.read_body()
local res, err = httpc:request_uri(proxy_target .. uri, {
method = ngx.req.get_method(),
headers = ngx.req.get_headers(),
-- 禁用证书验证
ssl_verify = false,
body = ngx.req.get_body_data(),
})
if not res then
ngx.log(ngx.ERR,"Failed to request upstream server: ", err)
ngx.header.content_type = 'application/json'
ngx.status = 500
ngx.print('{"status":500,"data":"Failed to request upstream server"}')
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
ngx.status = res.status
-- 设置代理响应的Content-Type头
ngx.header["Content-Type"] = res.headers["Content-Type"]
--ngx.header["Content-Type"] = "application/json"
ngx.print(res.body)
end
--------------------------------端口转发,实现proxy_pass功能----------------------------------------------------------
--------------------------------数字签名校验,等同于gateway中的InstitutionTypeFilterHandler(没加时间校验)---------------
function checkSign(instId,data, signatures)
local fileName = publicCertFolder .. instId .. ".pem"
ngx.log(ngx.INFO,"fileName="..fileName)
-- 读取pem格式公钥证书文件
local pk_cert_data = io.open(fileName, "rb"):read("*a")
local pkey = require("openssl.pkey")
local digest = require("openssl.digest")
local pkey = pkey.new()
pkey:setPublicKey(pk_cert_data)
-- 创建SHA256摘要对象
local digest_data = digest.new("sha256")
-- 更新摘要
digest_data:update(data)
--使用java项目中签名方法生成的签名
--local signatures ="fwOupXYu/6bl8RqMJWztrU6trsKkGXSh2nyQyHHmiuqlyCXWm5sE3zCzqoYVB5ppYIwzYGWYmhEH9jRzmB6R7oZXDLKnom/BaRnUMmrHjmgaI5AL9jAAyLKckNlOl1ptS35a7A3PyFDTBkOxq6gEZjJhMHxJVe4W98JJMpZqzZ5nM5nOVDKRsUrziSO0M7u/nDt03IoxB8wN34ljkjhXbT8v6my85QYTH3e0Pj9dcBiUV9Sv04GOCEr+eE9HznWirb169+MsulGERi2ILZAf+kTWJ/I5TYrGvbviowhtvRU4j1zJvnVRE1rjOEZpvxmBUAoSXpWOSUcqlVKAwsLbaQ=="
local signature =ngx.decode_base64(signatures)
-- 验证签名
return pkey:verify(signature, digest_data)
end
--------------------------------数字签名校验,等同于gateway中的InstitutionTypeFilterHandler(没加时间校验)--------------
--------------------------------代理转发----------------------------------------------------------------------------
function proxyRoute()
local cjson = require "cjson.safe"
local user_header_str = ngx.req.get_headers()[header_name] or ''
--ngx.log(ngx.INFO,"proxy:",user_header_str)
--如果需要查看lua脚本输出的日志打开这里的配置
--ngx.log(ngx.INFO, " rkg-test-headers: ", user_header_str)
if (user_header_str == nil or user_header_str == '') then
ngx.header.content_type = 'application/json'
ngx.print('{"status":400,"data":"Missing Or Wrong request header name ' .. header_name .. '"}')
ngx.log(ngx.ERR,"Missing Or Wrong request header name ",header_name)
return ngx.exit(ngx.HTTP_BAD_REQUEST)
end
--自定义请求头转换为json对象
local user_header_json = cjson.decode(user_header_str)
--判断自定义请求头是否为table,json的type是table
local user_header_json_type = type(user_header_json)
if user_header_json_type == "table" then
--获取代理转发地址
local proxy_url = user_header_json[route_name] or ''
--调用路由转发函数
proxyPass(proxy_url)
else
ngx.header.content_type = 'application/json'
ngx.print('{"status":400,"data":"request header proxy is not json,please check"}')
ngx.log(ngx.ERR,"request header proxy is not json")
return ngx.exit(ngx.HTTP_BAD_REQUEST)
end
end
--------------------------------代理转发----------------------------------------------------------------------------
--------------------------------主逻辑----------------------------------------------------------------------------
--superAdmin校验
if (superAdminHeader == "superAdmin") then
--如果是superAdmin则直接放行,不走签名校验逻辑
return proxyRoute()
end
--是否忽略url
local isIgnore = false
local reqUrl = ngx.var.uri
for _, str in ipairs(ignoredUrlArr) do
if endsWith(reqUrl,str) then
isIgnore = true
break
end
end
if isIgnore then
--如果是忽略的url则直接放行
ngx.log(ngx.INFO,"Ignore signature verification,url:",reqUrl)
return proxyRoute()
else
if (institutionId == nil or institutionId == '') then
ngx.header.content_type = 'application/json'
ngx.print('{"status":400,"data":"Missing Or Wrong request header name originInstitution,please check"}')
ngx.log(ngx.ERR, "Missing Or Wrong request header name originInstitution")
return ngx.exit(ngx.HTTP_BAD_REQUEST)
end
--非superAdmin从后端gateway获取证书,写到本地文件夹
if (createInstitutionPublicCert(institutionId)) then
--签名校验
if (checkSign(institutionId,originInstitutionHeader, signHeader)) then
--签名校验通过,执行代理转发逻辑
--ngx.log(ngx.INFO,"check sign success")
return proxyRoute()
else
--签名校验失败
ngx.header.content_type = 'application/json'
ngx.print('{"status":400,"data":"Missing Or Wrong request header name sign,invalid signature,please check"}')
ngx.log(ngx.ERR, "Missing Or Wrong request header name sign,invalid signature")
return ngx.exit(ngx.HTTP_BAD_REQUEST)
end
else
ngx.header.content_type = 'application/json'
ngx.status = 500
ngx.print('{"status":500,"data":"Failed to request gateway get institution public cert"}')
ngx.log(ngx.ERR, "Failed to request gateway get institution public cert,please check")
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
end
--------------------------------主逻辑----------------------------------------------------------------------------