Implementation of JWT verification by nginx based on openresty

Time:2020-9-27

introduce

Authority authentication is an inevitable problem in interface development, which includes two aspects

  1. The interface needs to know who the user is calling
  2. The interface needs to know whether the user has permission to call

The first problem is architecture oriented, and the second is more business oriented. Therefore, consider solving the first problem in the architecture layer to achieve the following purposes

  1. All requests for protected interfaces are guaranteed to be legal (authenticated users)
  2. The interface can get the current user information from the request header
  3. Each request has a UUID for identification

JWT (JSON web token) is currently the most widely used interface permission schemeStateless,Cross system,Multi language and multi platform supportIf we can implement JWT verification in the gateway layer, we can not only avoid code intrusion, but also provide a unified solution for the whole background. At present, the customer gateway uses nginx, but there is no JWT module in the community version of nginx, so it is unrealistic to implement it by ourselves. Therefore, we choose openresty as the gateway layer, Openresty? Is a high-performance web platform based on nginx and Lua, which integrates a large number of sophisticated Lua libraries, third-party modules and most of the dependencies. It is used to build dynamic web applications, web services and dynamic gateways that can handle super high concurrency and high scalability. In essence, it is an integrated software of nginx + Lua
The overall structure is as follows:
Implementation of JWT verification by nginx based on openresty

realization

Environmental Science

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

Install openresty

Opentry installation is very simple. You can find the installation documents of different versions of the operating system here. The environment used this time is CentOS Linux release 7.4

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

The system is installed in the/usr/local/openresty/Directory, the version is as follows

[[email protected] openresty]# cd /usr/local/openresty/bin/
[[email protected] bin]# ./openresty -v
nginx version: openresty/1.13.6.2

You can add the openresty directory to thePATHInside, convenient to use

modifynginx.confIs the file test successfully installed

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
[[email protected] conf]# openresty -s reload
[[email protected] conf]# curl localhost:8080
<p>hello, world</p>

Installing the JWT module

The Lua implementation library recommended by JWT is used here. The project address is https://github.com/SkyLothar/… Interestingly, this is the introduction to this projectJWT For The Great OpenrestyIt seems to be customized for openresty. There are installation tutorials on GitHub, but on the one hand, some third-party library installation documents are not mentioned, and on the other hand, some contents can be skipped when installing. Here, the complete installation steps are rearranged

  1. On the release page https://github.com/SkyLothar/… Download the source code of the project, so far the latest version isv0.1.11
  2. downloadhmacSource code. Up to now, the project has not been released. You can only download the source files in the project https://github.com/jkeys089/l…
  3. Create directory on server/usr/local/openresty/nginx/jwt-lua/resty, compress the directory in the package in step 1lua-resty-jwt-0.1.11/lib/resty/All Lua files under and in step 2hmac.luaFile copy to the directory, the file list is as follows
[[email protected] resty]# pwd
/usr/local/openresty/nginx/jwt-lua/resty
[[email protected] 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

modifynginx.confValidation is effective

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
[[email protected] 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"}

After verification, JWT module is installed

Custom validation logic

The JWT module above can not be used in the production environment, and there are several problems to be solved

  1. The JWT token is currently written in the configuration file, and the production needs to start from the headerAuthorizationIn the
  2. Verification failed. The current return is yes200Production needs to be returned401
  3. You need to configure the reverse proxy and put the user information on the proxy header

create a file/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

Modify configuration filenginx.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;
        }
    }
}

There’s one in the backgroundtomcatAnd set the listening port to80An example war package is deployed on Tomcat. The code logic is simple, that is to output all headers. The code is as follows:

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();
    }
}

Restart the openresty test. If the JWT token information is not specified, 401 is returned

[[email protected] 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>

Specify JWT token

[[email protected] 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

From the result, the background service has been obtaineduidThis header
As for the request, JWT token can be generated from any platform as long as it is guaranteedsecretAccording to the introduction on the official website, the library currently supports JWT generation algorithm, as shown in the following figure:

Implementation of JWT verification by nginx based on openresty

UUID generation

Generating unique UUID code for each request can associate the request on gateway layer with that on application layer, which is very useful for troubleshooting and interface statistics

create a file/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
Modify configuration filenginx.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;
        }
    }
}

Restart openresty, test

[[email protected] conf]# openresty -s reload
[[email protected] 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

As you can see, there is one moreuuidRequest header for

Java example generated by JWT token

Here is a java example of generating JWT token

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: 9:56 pm on July 5, 2018
 * @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>

Write it at the end

This only solves the first problem mentioned at the beginning of the article. The interface needs to know who called the interface. The second problem is whether the user can call the interface. At present, AOP is considered to be implemented in the application layer and will be updated in the future

Recommended Today

Explain idea git branch backoff specified historical version

scene When I submitted this modification to the local and remote branches, I found that there were still some changes missing in this submission, or this modification was totally wrong, but I also pushed it to the remote repository. How to go back? problem How can the content that has been submitted to the repository […]