对码当歌,猿生几何?

Nginx 使用 Lua 模块校验 Token

Nginx 使用 Lua 模块校验 Token

前言

最近在折腾 FastDFS 系统,用 FastDFS 来存放一些小文件(在之前学习的一个商城项目中,用来做图片服务器,存放商品的图片)。当然,一般情况下,别人都是可以直接访问的。不过后来又想,能不能添加一个验证,对用户的权限进行校验是否可以访问。

尝试过使用 FastDFS 内置防盗链功能,不过这样子每台 FastDFS 服务器都需要配置一下。于是乎在想,能不能在统一入口的 Nginx 服务器上进行校验了。这样子是不是省事一些,而且之后也可以用在其它地方。

于是乎,就发现了一个 Nginx 的第三方模块 —— lua-nginx-module。将 lua 语言嵌入到 Nginx 配置中,从而增强了 Nginx 的能力(即使从来未接触过 Lua,不过多看看别人的代码,然后不断地实践,总能把问题解决的)。于是乎,就使用 Nginx + lua-nginx-module 来验证 Token 信息。


环境说明

系统环境

  • 虚拟机工具 :VMware Workstations 14 Pro

  • 操作系统 :CentOS 7 64位

  • IP 地址:192.168.229.165

相关安装包

将以上安装包下载后,拷贝到系统 /root 目录下


Nginx 安装与配置

Nginx 及模块安装

# 安装 Nginx 依赖环境yum install gcc gcc-c++ make automake autoconf libtool pcre* zlib openssl openssl-devel# 解压安装包cd /root
tar zxvf LuaJIT-2.0.5.tar.gztar zxvf lua-nginx-module-0.10.13.tar.gztar zxvf nginx-1.15.1.tar.gz# LuaJIT 安装cd /root/LuaJIT-2.0.5make && make install# Nginx 添加 lua_nginx_module 模块安装cd /root/nginx-1.15.1./configure --add-module=../lua-nginx-module-0.10.13/
make && make install# 查看 Nginx 是否安装成功/usr/local/nginx/sbin/nginx -v# nginx version: nginx/1.15.1 则表示安装成功# 可能出现以下错误,则需要建立软链接:/usr/local/nginx/sbin/nginx: error while loading shared libraries: libluajit-5.1.so.2: cannot open shared object file: No such file or directoryln -s /usr/local/lib/libluajit-5.1.so.2 /lib64/libluajit-5.1.so.2

配置 Nginx

vi /usr/local/nginx/conf/nginx.conf

在 server 中添加以下代码

# 设置转发的 url
location @fastDFS {
    proxy_pass http://192.168.229.166:80;
}
location ~/group([0-9])/M([0-9])([0-9]) {
    access_by_lua '
        -- 获取请求路径,不包括参数。例如:/group1/M00/00/00/wKjlpltF-K-AZQQsAABhhboA1Kk469.png        local uri = ngx.var.uri;
        -- 获取请求参数        local args = ngx.req.get_uri_args();
        -- 获取请求参数中时间戳信息,传入的是毫秒        local ts  = args["ts"];
        -- 获取请求参数中 token 信息        local token1 = args["token"];

        -- 更新系统缓存时间戳        ngx.update_time();
        -- 获取当前服务器系统时间,ngx.time() 获取的是秒        local getTime = ngx.time() * 1000;
        -- 计算时间差        local diffTime = tonumber(ts) - getTime;
        -- md5 加盐加密        local token2 = ngx.md5(tostring(uri) .. "salt" .. tostring(ts));

        -- 判断时间是否有效        if (tonumber(diffTime) > 0) then
            -- 校验 token 是否相等            if token1 == token2 then
                -- 校验通过则转发请求                ngx.exec("@fastDFS");
            end
        end
    ';
}

启动 Nginx:/usr/local/nginx/sbin/nginx


Java 代码生成可访问的 Url 链接

@Testpublic void getUrl() {// 获取当前系统所在服务器的时间,毫秒单位// 注意,当前系统不一定是 Nginx 所在服务器long milliseconds = System.currentTimeMillis();// 添加有效期时间,假设该链接有效期为 1 天,即 86400000// 自己测试时,为了方便,可以设置为 1 分钟之类的System.out.println("当前系统时间:" + milliseconds);
    milliseconds += 1 * 24 * 60 * 60 * 1000;// milliseconds += 60 * 1000;// 计算 token 信息// 请求的资源路径String requestResources = "/group1/M00/00/00/wKjlpltF-K-AZQQsAABhhboA1Kk469.png";// “盐” 值,和 Nginx 服务器上的保持一致即可String salt = "salt";// 加密前的字符串:请求的资源路径 + “盐” 值 + 时间戳String beforeEncryptionString = requestResources + salt + milliseconds;// 这里使用 Spring 提供的 md5 加密工具进行 md5 加密String token = DigestUtils.md5DigestAsHex(beforeEncryptionString.getBytes());
    String url = requestResources + "?ts=" + milliseconds + "&token=" + token;

    System.out.println("请求的 url 为:");
    System.out.println(url);
}/*
运行结果:
---------------------------------------------------------------------
当前系统时间:1531659300558
请求的 url 为:
/group1/M00/00/00/wKjlpltF-K-AZQQsAABhhboA1Kk469.png?ts=1531745700558&token=c78bc4365723b52916a99abc628a0d25
 */

访问演示

无参数访问如下

Nginx 使用 Lua 模块校验 Token(1).png

带有效的时间戳与 token 访问

Nginx 使用 Lua 模块校验 Token(2).png

带已过期的时间戳与 token 访问
Nginx 使用 Lua 模块校验 Token(3).png


可能存在的问题 —— 系统时间不一致

请求 Nginx 服务器时,就会判断时间戳是否有效,token 值是否正确,也可以根据自己情况,进行修改。例如修改 “盐” 值,字符串的拼接方式,加密方式等等。

但是,上面的 Java 代码注释也说了,生成 Url 的系统所在服务器,不一定就是安装 Nginx 的服务器,故可能两者时间不一致。从而导致链接访问失效。

解决方法:从 Nginx 服务器中获取时间。下面说一下我的实现方式。

配置 Nginx

vi /usr/local/nginx/conf/nginx.conf

在 server 中添加以下代码

# 获取当前系统时间,并返回location /getTime {
    default_type text/html;
    content_by_lua '
        ngx.say(ngx.time() * 1000);
    ';
}

重启 Nginx 服务后(重启命令:/usr/local/nginx/sbin/nginx -s reload),访问 http://192.168.229.165/getTime,页面返回时间戳。如下图:
Nginx 使用 Lua 模块校验 Token(4).png

Java 代码修改

package com.hochenchong.learn;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.net.URL;import org.junit.Test;import org.springframework.util.DigestUtils;/**
 * @description 生成带有效期与 token 的 URL 测试
 * @author HochenChong
 * @date 2018-7-15
 * @version 0.1
 */public class NginxTest {@Testpublic void test() {// 获取 Nginx 服务器上的系统时间String requestUrl = "http://192.168.229.165/getTime";long systemTime = Long.parseLong(getURLContent(requestUrl));
        System.out.println("Nginx 服务器上系统时间:" + systemTime);// 请求的资源路径String requestResources = "/group1/M00/00/00/wKjlpltF-K-AZQQsAABhhboA1Kk469.png";
        String url = getUrl(requestResources, systemTime);
        System.out.println("请求的 url 为:");
        System.out.println("192.168.229.165" + url);
    }/**
     * 获取带时间戳与 token 的 url
     * @param requestResources 请求的资源路径,不包括 IP 地址与端口,开头有 /,例如 /group1/M00/00/00/wKjlpltF-K-AZQQsAABhhboA1Kk469.png
     * @param systemTime 系统时间
     * @return 返回请求的 url 地址,包括有效期与 token
     */public static String getUrl(String requestResources, long systemTime) {// 添加有效期时间,假设该链接有效期为 1 天,即 86400000// 有效期,单位:毫秒// 自己测试时,为了方便,可以设置为 1 分钟之类的long milliseconds = systemTime + 1 * 24 * 60 * 60 * 1000;// long milliseconds = systemTime + 60 * 1000;// 计算 token 信息// “盐” 值,和 Nginx 服务器上的保持一致即可String salt = "salt";// 加密前的字符串:请求的资源路径 + “盐” 值 + 时间戳String beforeEncryptionString = requestResources + salt + milliseconds;// 这里使用 Spring 提供的 md5 加密工具进行 md5 加密String token = DigestUtils.md5DigestAsHex(beforeEncryptionString.getBytes());
        String url = requestResources + "?ts=" + milliseconds + "&token=" + token;return url;
    }/**
     * 获取请求 url 返回的文本
     * @param requestUrl 请求的 url
     * @return */public static String getURLContent(String requestUrl) {
        URL url = null;
        BufferedReader in = null;
        StringBuffer sb = new StringBuffer(); try {
            url = new URL(requestUrl);     
            in = new BufferedReader(new InputStreamReader(url.openStream())); 
            String str = null;  while ((str = in.readLine()) != null) {
                sb.append(str);     
            } 
        } catch (Exception e) {
            e.printStackTrace();
        } finally{   // 关闭资源try {if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }return sb.toString();
    }
}

运行结果

Nginx 服务器上系统时间:1531661817000请求的 url 为:192.168.229.165/group1/M00/00/00/wKjlpltF-K-AZQQsAABhhboA1Kk469.png?ts=1531748217000&token=8950061a099c83e59316afb8e3319c8b

后记

配置完 Nginx 环境后,也可以直接自己手动拼接字符串,再到 在线 MD5加密/解密 网站进行 md5 加密尝试一番。

有些东西,虽然不完全没接触过(例如上面的 Lua),需要用到时,就去看别人怎么写的,自己尝试的写,有啥问题就去查一下,慢慢地就能把问题解决了。

在实践中成长!

HochenChong

2018-7-15

阅读更多