nginx + aeslua 实现简单的资源权限管理网关

    最近公司有一个小需求,所有的图片都是储存在阿里云的oss服务器上,现在要通过浏览器能够访问这些图片资源,并且需要将过期的会话和非法的连接排除掉。目前配合白名单能够实现基本的安全认证,但也还仅是个demo,仍然存在很多性能和安全的问题。需求很简单:

  1. 用户登陆之后,会产生一个名为session_login的cookie,它的值是一个十分简单的json,例如:{last_alive_time: 15234566734},这个值是通过AES加密之后再转为16进制的一个字符串;
  2. 浏览器通过img标签请求图片资源的时候,会将这个cookie带给web应用,web应用原封不动地转发到nginx上;
  3. nginx上通过lua脚本进行解密,判断该用户会话是否合法(现在只是一个demo,所以暂时只判断last_alive_time的值与当前时间的差是否在30000毫秒内,如果在的话,表示用户活跃,允许访问图片资源);
  4. lua脚本中通过http获取图片http服务上的资源,如果获取不到,返回一个默认图片;如果获取成功,将图片信息返回;直至浏览器能展示

nginx + aeslua 实现简单的资源权限管理网关_第1张图片

1、Web应用中创建cookie和加密

1.1 创建AES相关密码机

String pw = "密钥";
//This class specifies an initialization vector (IV). Examples which use IVs are ciphers in feedback mode, e.g., DES in CBC mode and RSA ciphers with OAEP encoding operation.
byte[] ivBytes = "初始化向量,不是很懂IvParameterSpec的作用,但是看上面官方解释,意思是初始化向量"

Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec key = new SecretKeySpec(pw.getBytes(), "AES");
IvParameterSpec iv = new IvParameterSpec(ivBytes);

aesCBC.init(Cipher.ENCRYPT_MODE, key, iv);

1.2 OpenSslAES 类

class OpenSslAES {

    public static byte[] encrypt(byte[] data) {

        return aesCBC.doFinal(data);  //aesCBC为1.1代码中生成的密码机

    }

}

1.3 加密部分

public static String encrypt(){
    String content = String.format("{\"last_alive_time\":%s}", System.currentTimeMillis());
    try {
        byte[] result = OpenSslAES.encrypt(content.getBytes());
        return bytesToHex(result);
    } catch (Exception e) {
        throw new RESTFulTradeErrorCodeException(ManagementErrorCode.ENCRYPT_ERROR.getCode(), e, ManagementErrorCode.ENCRYPT_ERROR.getErrMessage());
    }
}

1.4 二进制转十六进制方法bytesToHex

private static String str[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};

static String bytesToHex(byte[] ch) {

    StringBuilder ret = new StringBuilder("");

    for (int i = 0; i < ch.length; i++){

        ret.append(str[ch[i] >> 4 & 0xF] + str[ch[i] & 0xF]);

    }

    return ret.toString();

}

2、Nginx部分

2.1 nginx.conf 主要配置

location /images_proxy {
            default_type text/html;
            lua_code_cache on;
            access_by_lua_file /nginx/lualib/aeslua/aeslua.lua;
  }

2.2 aeslua.lua脚本

需要实现安装openresty

local aes = require "resty.aes"  
local cjson = require "cjson"

local hash = {
    iv = "与上述Java程序中的iv值保持一致",
    method = nil
}
local salt = "1234567890"
local aes_128_cbc, err = aes:new("与上述Java程序中的pw密钥的值保持一致", salt, aes.cipher(128, "cbc"), hash)
if err then
    ngx.say(err)
    ngx.exit(200)  //如果创建AES密码机失败,则返回
end

//该函数用户将十六进制字符串转化为二进制,在lua中好像没有字节数组的数据类型,所以这里直接拼接字符串

function hex_to_str(str)
    return (str:gsub('..', function(cc)
        return string.char(tonumber(cc, 16))
    end))
end

//从cookie中获取session_login,如果不存在,则说明用户未登录,直接返回

local session_str = ngx.var.cookie_session_login
if session_str == nil then
    ngx.say("not login.")
    ngx.exit(401)
    return
end

//将获取到的cookie以json进行处理,如果其中last_alive_time的值不存在或者超期,则直接返回

session_json=cjson.decode(session_info)
if session_json == nil or session_json['last_alive_time']==nil then
    ngx.say("not login.")
    ngx.exit(401)
    return
end
if ngx.now()*1000-session_json['last_alive_time'] > 300000 then
    ngx.say("login expired.")
    ngx.exit(401)
    return
end
 

//仅允许GET请求,其他一切method方式全部返回

local action = ngx.var.request_mthod;
if action == "POST" then
    ngx.say("only support GET!");
    ngx.exit(403);
    return;
end
 

//创建http客户端,并尝试访问图片资源服务

local http = require "resty.http";
local httpc = http.new();

local res,err = httpc:request_uri("图片服务器域名 + 原请求中的图片路径,例如/2018/11/1.jpg",{ method ="GET"});

ngx.header.content_type="image/gif";

//如果访问图片服务器失败,则返回本地一张默认图片,否则就将从图片服务器读取到的数据返回回去

if err then
    local f = io.open("/nginx/html/image-not-found.png", "rb");
    local content = f:read("*all");
    f:close();
    ngx.print(content);
else
    ngx.print(res.body);
end
 

你可能感兴趣的:(java)