Let’s talk about user authentication solutions in microservice architecture

Traditional user authentication scheme

Let’s get straight to the point, what is user authentication? For most user-related operations, the software system first needs to confirm the user's identity and therefore provides a user login function. The user enters user name, password and other information, and the back-end system verifies it is user authentication. There are many forms of user authentication. The most common ones include entering username and password, mobile phone verification code, face recognition, fingerprint recognition, etc., but their purpose is to confirm the user's identity and provide services.

User Authentication

In the traditional single-point application era, we will develop user authentication service classes. The user name, password and other information submitted from the login interface are verified through the user authentication class, and then the user object is obtained and saved in Tomcat's Session. ,As follows:

Single Point Application Certification Scheme

As system traffic increases, single-point applications cannot support business operations, and applications experience high latency, downtime, etc. At this time, many companies will change applications to Nginx soft load clusters to improve system performance through horizontal expansion, so applications The architecture has become like this.

Java Web Application Cluster

Although the system performance has been significantly improved after the transformation, have you discovered that because the session data of previous user logins are stored locally, when Nginx forwards the request to other nodes, because other nodes do not have this session data, the system will think that there is no such session data. If you have logged in, the requested business will be rejected. From the user's perspective, the system will ask me to log in again as soon as I refresh the page. This user experience is very bad.

Let's analyze it. The root cause of this problem is that using Session to save user data locally will make Java Web applications stateful. In a cluster environment, the session state of each Tomcat node must be consistent to avoid problems. Therefore, the distributed session storage solution based on Redis emerged as the times require. A Redis server is added to the backend of the original architecture to uniformly transfer user sessions to Redis. Because the session data is stored centrally, there will be no data consistency issues. .

Redis unified storage of user sessions

However, traditional solutions will encounter bottlenecks in the Internet environment. Redis serves as the session data source, which also means that Redis bears all external pressure. With the huge user base of hundreds of millions on the Internet, if an emergency occurs During traffic peaks, whether Redis can withstand the test will become a key risk to the system. If there is a slight deviation, the system will collapse.

So how to solve it? In fact, there is another ingenious design. After the user authentication is successful, the user data is no longer stored in the backend, but is instead stored on the client. Every time the client sends a request, the user data is attached to the Web application, and the Java application reads it. User data is processed for business, because user data is stored dispersedly in the client, so it does not impose additional burden on the backend. At this time, the authentication architecture will become the following situation.

Client stores user information

When the user authentication is successful, the current user data will be held in the client's Cookie and LocalStorage. After Tomcat receives the request, the user data can be obtained for business processing. But if you are careful, you will definitely find that the user's sensitive data is not encrypted, and there is a risk of leakage at any time during storage and transmission. Plain text must not be used and must be encrypted.

So how to perform encryption? Of course, you can write your own encryption and decryption classes, but a more common approach is to use a standard encryption scheme such as JWT for data storage and transmission.

Introduction to Json Web Token (JWT)

Whether it is a microservice architecture or a front-end and back-end separation application, there is a common solution for storing and encrypting data on the client: Json Web Token (JWT). JWT is an encrypted, time-sensitive and fixed token that contains user information. Format string. Below this is a standard JWT string.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoyLFwidXNlcm5hbWVcIjpcImxpc2lcIixcIm5hbWVcIjpcIuadjuWbm1wiLFwiZ3JhZGVcIjpcInZpcFwifSJ9.NT8QBdoK4S-PbnhS0msJAqL0FG2aruvlsBSyG226HiU

This encrypted string consists of three parts, separated by dots "." in the middle. The specific meaning is as follows.

  • The first part of the header (Header): The header usually consists of two parts: the type of token (i.e. JWT) and the signature algorithm used, such as HMAC SHA256 or RSA. The following is the original text of the header:

{
  "alg": "HS256",
  "typ": "JWT"
}

This JSON is then Base64 encoded to form the first part of the JWT.

eyJhbGciOiJIUzI1NiJ9
  • The second part of the payload (Payload): The payload is the actual user data and other custom data. The original text of the payload is as follows.

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

The original text is then Base64 encoded to form the second part of the JWT.

eyJzdWIiOiJ7XCJ1c2VySWRcIjoyLFwidXNlcm5hbWVcIjpcImxpc2lcIixcIm5hbWVcIjpcIuadjuWbm1wiLFwiZ3JhZGVcIjpcInZpcFwifSJ9
  • The third part of the signature (Sign): The signature is to generate a special string used to verify whether the JWT is valid through the previous two parts of header + payload + private key and the specified algorithm. The signature generation rules are as follows.

HMACSHA256(base64UrlEncode(header) + "." +  base64UrlEncode(payload),  secret)

The generated signature string is:

NT8QBdoK4S-PbnhS0msJAqL0FG2aruvlsBSyG226HiU

Connecting the above three parts together with "." is the standard format of JWT.

Creation and verification of JWT

At this point, you must have questions about how JWT is generated and how to complete validity verification? Because the format and algorithm of JWT are fixed, there are many excellent open source projects in Java that help us realize the creation and verification of JWT. The most representative product among them is JJWT. JJWT is a Java library that provides end-to-end JWT creation and verification. Its official website is: https://github.com/jwtk/jjwt. If you are interested, you can go to the official website to read its source code.

The use of JJWT is very simple. Let's use code to illustrate it below. I have commented the key code.

  • The first step is to introduce the Maven dependency of JJWT into pom.xml.

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.2</version>
</dependency>

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
  • The second step is to write a test case for creating JWT and simulate the JWT generation process after the user with UserID No. 123 logs in in the real environment.

@SpringBootTest
public class JwtTestor {
    /**
     * 创建Token
     */
    @Test
    public void createJwt(){
        //私钥字符串
        String key = "1234567890_1234567890_1234567890";
        //1.对秘钥做BASE64编码
        String base64 = new BASE64Encoder().encode(key.getBytes());
        //2.生成秘钥对象,会根据base64长度自动选择相应的 HMAC 算法
        SecretKey secretKey = Keys.hmacShaKeyFor(base64.getBytes());
        //3.利用JJWT生成Token
        String data = "{\"userId\":123}"; //载荷数据
        String jwt = Jwts.builder().setSubject(data).signWith(secretKey).compact();
        System.out.println(jwt);
    }
}

The running result produces a JWT string as follows:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoxMjN9In0.1p_VTN46sukRJTYFxUg93CmfR3nJZRBm99ZK0e3d9Hw
  • The third step is to verify the signature code and extract user data No. 123 from the JWT. It is necessary to ensure that the JWT string and key private key are consistent with those when generated. Otherwise, a JwtException will be thrown if the signature verification fails.

/**
 * 校验及提取JWT数据
 */
@Test
public void checkJwt(){
    String jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoxMjN9In0.1p_VTN46sukRJTYFxUg93CmfR3nJZRBm99ZK0e3d9Hw";
    //私钥
    String key = "1234567890_1234567890_1234567890";
    //1.对秘钥做BASE64编码
    String base64 = new BASE64Encoder().encode(key.getBytes());
    //2.生成秘钥对象,会根据base64长度自动选择相应的 HMAC 算法
    SecretKey secretKey = Keys.hmacShaKeyFor(base64.getBytes());
    //3.验证Token
    try {
        //生成JWT解析器 
        JwtParser parser = Jwts.parserBuilder().setSigningKey(secretKey).build();
        //解析JWT
        Jws<Claims> claimsJws = parser.parseClaimsJws(jwt);
        //得到载荷中的用户数据
        String subject = claimsJws.getBody().getSubject();
        System.out.println(subject);
    }catch (JwtException e){
        //所有关于Jwt校验的异常都继承自JwtException
        System.out.println("Jwt校验失败");
        e.printStackTrace();
    }
}

The running results are as follows:

{"userId":123}

The above is the JWT generation and verification code. You will find that during the encryption and decryption process, the server private key is the lifeblood of ensuring JWT security. This private key cannot be hard-coded in the production environment. Instead, it is encrypted and stored in the Nacos configuration center for unified storage. At the same time, the private key is regularly changed to prevent the leakage of key information.

At this point, you should have mastered the basic usage of JWT, but how to design a user authentication system under a microservice architecture?

Gateway-based unified user authentication

Next, we will explain the JWT authentication process under the microservice architecture based on scenarios. Here I will introduce two options:

  • Server-side independent signature verification solution;

  • API gateway unified signature verification solution.

Server-side independent signature verification solution

First, let’s look at the architecture diagram of server-side signature verification.

Server-side independent signature verification solution

First, sort out the execution process:

  • In the first step, the authentication center microservice is responsible for user authentication tasks and extracts the JWT encryption private key from the Nacos configuration center at startup;

  • In the second step, the user enters the username and password on the login page, and the client initiates an authentication request to the certification center service:

http://usercenter/login #认证中心用户认证(登录)地址
  • In the third step, the authentication center service performs authentication verification in the user database based on the input. If the verification is successful, the authentication center will generate the user's JSON data and create the corresponding JWT and return it to the client. The following is a sample of the data returned by the authentication center. ;

{
    "code": "0",
    "message": "success",
    "data": {
        "user": {
            "userId": 1,
            "username": "zhangsan",
        },
        "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoxLFwidXNlcm5hbWVcIjpcInpoYW5nc2FuXCIsXCJuYW1lXCI6XCLlvKDkuIlcIixcImdyYWRlXCI6XCJub3JtYWxcIn0ifQ.1HtfszarTxLrqPktDkzArTEc4ah5VO7QaOOJqmSeXEM"
    }
}
  • Step 4: After receiving the above JSON data, the client saves the token data in a cookie or local cache;

  • In the fifth step, the client then initiates a new request to a specific microservice. This JWT will be appended to the request header or cookie and sent to the API gateway. The gateway will forward the request and jwt data to the specific microservice according to the routing rules. The intermediate process gateway does not do any processing of JWT;

  • Step 6: After the microservice receives the request, it finds that the request comes with JWT data, so it forwards the JWT to the user authentication service again. At this time, the user authentication service verifies the JWT signature. The verification successfully extracts the user number, and queries the user authentication and Authorization detailed data, the data structure is as follows:

{
    "code": "0",
    "message": "success",
    "data": {
        "user": { #用户详细数据
            "userId": 1,
            "username": "zhangsan",
            "name": "张三",
            "grade": "normal"
            "age": 18,
            "idno" : 130.......,
            ...
        },
        "authorization":{ #权限数据
            "role" : "admin",
            "permissions" : [{"addUser","delUser","..."}]
        }
    }
}
  • In the seventh step, after the specific microservice receives the above JSON, it judges the currently executed operation and checks whether it has execution permission. The permission check is performed by executing the business code. If the permission check fails, an error response is returned.

At this point, the complete process from logging in to creating JWT to executing business code after signature verification has been completed.

Let’s talk about the second option:

API gateway unified signature verification solution

API gateway unified signature verification solution

The biggest difference between API gateway unified signature verification and server-side signature verification is that the JWT signature verification request is initiated at the API gateway level, and then the user and permission data returned from the certification center are appended during the routing process. The other operation steps are as follows: are exactly the same.

Here you may have doubts again, why should we design two different solutions? In fact, this corresponds to different application scenarios:

  • The timing of server-side signature verification is before the business code is executed, and the control granularity is finer. For example, microservice A provides two functions: "product query" and "create order". The former can be used without logging in, so there is no need to initiate additional signature verification work to the certification center; while the latter is a function after logging in, so it must It can only be executed after signature verification. Because the server-side signature verification is at the method level, it can accurately control whether the method is signed. But there are also shortcomings. Precisely because the signature verification is performed before the method, it is necessary to declare whether additional signature verification is required on all business methods. Although this work can be achieved without intrusion through Spring AOP+ annotations, it will undoubtedly require programmers. Extra attention distracts from developing the business.

  • Correspondingly, the shortcomings of server-side signature verification have become the advantages of API gateway signature verification. The API gateway does not care about the back-end service logic. As long as the request comes with JWT, it will automatically verify the signature with the certification center. This simple and crude strategy does reduce module coupling and makes it easier to handle, but it also brings performance problems, because as long as the request contains JWT, remote communication with the certification center will occur. If the front-end engineer does not accurately control JWT, it is likely to cause a large number of redundant authentication operations, and system performance will definitely be affected.

So how to choose in the project? Server-side signature verification control is more detailed and suitable for low-latency, high-concurrency applications, such as navigation, real-time trading systems, and military applications. The API unified gateway is more suitable for use in traditional enterprise applications, allowing programmers to concentrate on developing business logic, and the program is easier to maintain.

new challenges

Although JWT looks beautiful, it will also encounter some unique problems during its implementation, such as:

  • The expiration date of JWT is fixed after it is generated. In many businesses, the client needs to implement the "renewal" function of JWT without changing the JWT. However, this cannot be achieved by relying solely on the characteristics of JWT itself, because the design of JWT itself Generating identical strings is not allowed. In order to solve this problem, many projects set the generated JWT to "permanent", and architects use the Expire expiration feature of Redis to control the timeliness of JWT on the back end. Although doing so makes JWT itself stateful, this may also be the "optimal solution" after various trade-offs. Similarly, for example, forcing JWT to expire immediately and dynamic JWT validity period can be solved using this method.

A JWT expires after 3600 seconds

  • For the above two authentication schemes, there is still room for optimization. For example, after service A verifies a JWT for the first time and obtains user and permission data, the data can be stored in local memory or Redis within the validity period of the JWT. Cache, so that the next time the same JWT is accessed, it can be directly extracted from the cache, which can save a lot of communication time between services. But after introducing the cache, you must always pay attention to the consistency between the cache and user data. Whether you want performance or data reliability is another choice that architects need to face.

summary

Today we mainly cover three aspects. First, we review the session-based stateful user authentication solution. Secondly, we introduce the use of JWT and JJWT. Finally, we explain two solutions for using JWT to implement microservice architecture authentication and solve the new problems that arise. Also sorted out.

During my many years of architecture career, I have been constantly lamenting that architecture is an art of making choices. There is no perfect architecture, only suitable scenarios. I hope that in the future, students can learn more about cutting-edge technologies. Perhaps as technology develops, they may be able to catch up. You can really have both of them.

Guess you like

Origin blog.csdn.net/mxt51220/article/details/128573601