Immediate Time-The Beauty of Design Patterns in Practice II (Part 1): How to do an object-oriented analysis of the development of such a function as interface authentication?

Object-oriented analysis (OOA), object-oriented design (OOD), and object-oriented programming (OOP) are the three main links of object-oriented development. In the previous chapters, my explanation of the three is more theoretical and general. The purpose is to give you a general understanding of what OOA, OOD, and OOP are. However, it is not enough to know the "what". It is more important for us to know the "how", that is, how to conduct object-oriented analysis, design and programming.

In my past work, I found that many engineers, especially junior engineers, did not have much project experience themselves, or the projects they participated in were all based on the development framework to fill in code like CRUD templates, resulting in a lack of analysis and design capabilities. When they get a more general development requirement, they often don't know where to start.

For "how to do requirements analysis, how to divide responsibilities? What classes need to be defined? What attributes and methods should each class have? How to interact between classes? How to assemble classes into an executable program?" The problem is that there is no clear idea, let alone the use of mature design principles, ideas or design patterns to develop code with high cohesion, low coupling, easy expansion, easy to read and other outstanding characteristics.

Therefore, I plan to use two classes to combine a real development case, starting from basic requirements analysis, responsibilities division, class definition, interaction, assembly and operation, and the most basic object-oriented analysis, design, and programming. The routine will be explained clearly for you to lay a solid foundation for later learning design principles and design patterns.

Not much to say, let's officially start today's learning!

Case introduction and difficulty analysis

Suppose, you are participating in the development of a microservice. Microservices expose interfaces to other systems through the HTTP protocol. To put it bluntly, other systems call the interfaces of microservices through URLs. One day, your leader found you and said, "In order to ensure the security of interface calls, we hope to design and implement an interface call authentication function. Only the authenticated system can call our interface, and the system that has not been authenticated can call ours. The interface will be rejected. I hope you will be responsible for the development of this task and strive to go online as soon as possible."

The leader left these words and left. What should you do at this time? Does it feel like there is a cloud of mud in your mind and you can't start for a while? Why do you feel this way? I personally think there are two main reasons.

1. Unclear requirements

The requirements given by the leader are too vague, general, not specific and detailed enough, and there is still a certain distance from landing to design and coding. The human brain is not good at thinking about such overly abstract problems. This is what distinguishes real software development from exam-oriented education. Exam questions in exam-oriented education are generally very specific questions, so we can just answer them. In real software development, almost all requirements are not clear.

As we said before, the main analysis object of object-oriented analysis is "requirements", therefore, object-oriented analysis can be roughly regarded as "requirements analysis". In fact, whether it is requirements analysis or object-oriented analysis, the first thing we have to do is to refine the general requirements to be clear and executable. Through communication, mining, analysis, hypothesis, and sorting, we need to figure out what specific needs are, which are to be done now, which may be done in the future, and which are not to be considered.

2. Lack of exercise

Compared with pure business CRUD development, the development task of authentication is more difficult. As a function that has nothing to do with specific business, authentication can be developed as an independent framework and integrated into many business systems. As a general framework reused by many systems, we have higher requirements on the code quality of the framework than ordinary business code.

The development of such a universal framework requires engineers to have relatively high requirements for analysis capabilities, design capabilities, coding capabilities, and even logical thinking capabilities. If you usually do simple CRUD business development, you will definitely not have a lot of exercise in this area. Therefore, once you encounter this kind of development needs, it is easy to lose your mind due to lack of exercise and do not know where to start. Ideas.

Need analysis of the case

In fact, the work of requirements analysis is very trivial, and there are not too many fixed rules to find. Therefore, I do not intend to list those methodologies that sound useful but actually useless. Instead, I hope to use this example of authentication. Let me show you what my complete thinking path looks like when facing demand analysis. I hope you can experience it for yourself and apply analogy to the needs analysis of other projects.

Although for the development of non-business systems such as frameworks, components, and libraries, we must have component awareness, framework awareness, and abstraction awareness. The things developed must be universal enough and not limited to a single business requirement. It doesn't mean that we can break away from specific application scenarios and slap our heads for demand analysis. Talk to the business team more, and even participate in the development of several business systems. Only in this way can we truly know the pain points of the business system and analyze the most valuable needs. However, for the development of the authentication function, the biggest demander is ourselves. Therefore, we can also start with meeting the needs of our own system, and then iteratively optimize.

Now, let's take a look, for the development of this function of authentication, how do we do demand analysis?

In fact, this is similar to doing algorithmic problems. First think of the simplest solution and then optimize. Therefore, I divided the entire analysis process into four gradual rounds. Each round is an iterative optimization of the previous round, and finally a list of executable and landable requirements is formed.

1. The first round of fundamental analysis

The simplest solution to the problem of how to do authentication is to do authentication through user name and password. We distribute an application name (or application ID, AppID) and a corresponding password (or secret key) to each caller who is allowed to access our service. Every time the caller makes an interface request, it carries its AppID and password. After the microservice receives the interface call request, it will parse out the AppID and password, and compare them with the AppID and password stored in the microservice. If they are consistent, the authentication is successful and the interface call request is allowed; otherwise, the interface call request is rejected.

2. The second round of analysis and optimization

However, in such an authentication method, the password must be transmitted in clear text every time. Passwords can be easily intercepted and are insecure. So if we use an encryption algorithm (such as SHA) to encrypt the password and then pass it to the micro server for verification, is it all right? In fact, this is also insecure, because the encrypted password and AppID can still be intercepted by unauthenticated systems (or hackers), unauthenticated systems can carry this encrypted password and the corresponding AppID, disguising as an authenticated system Come visit our interface. This is a typical replay attack .

Asking questions and then solving them is a very good iterative optimization method. For the problem just now, we can solve it with the help of OAuth authentication. The caller splices the URL of the request interface with the AppID and password, and then encrypts it to generate a token. When the caller makes an interface request, it passes this token and AppID along with the URL to the micro server. After receiving these data, the micro-server takes out the corresponding password from the database according to the AppID, and generates another token through the same token generation algorithm. Use this newly generated token to compare with the token passed by the caller. If they are consistent, the interface call request is allowed; otherwise, the interface call request is rejected.

This scheme is a bit more complicated, I have drawn a sample diagram to help you understand the whole process.

Insert picture description here

3. The third round of analysis and optimization

However, such a design still has the risk of replay attacks and is still not safe enough. The token generated by splicing AppID and password on each URL is fixed. After the unauthenticated system intercepts the URL, token, and AppID, it can still pretend to be an authentication system through replay attacks and call the interface corresponding to this URL.

To solve this problem, we can further optimize the token generation algorithm and introduce a random variable so that the token generated by each interface request is different. We can choose timestamp as a random variable. The original token was generated by encrypting the URL, AppID, and password. Now we encrypt the URL, AppID, password, and timestamp to generate the token. When the caller makes an interface request, it passes the token, AppID, and timestamp along with the URL to the micro server.

After receiving these data, the micro server will verify whether the current timestamp and the passed timestamp are within a certain time window (for example, one minute). If it exceeds one minute, the token is determined to expire and the interface request is rejected. If it does not exceed one minute, it means that the token has not expired, and then use the same token generation algorithm to generate a new token on the server, and compare it with the token passed by the caller to see if it is consistent. If they are consistent, the interface call request is allowed; otherwise, the interface call request is rejected.

The optimized authentication process is shown in the figure below.

Insert picture description here

4. Fourth round of analysis and optimization

However, you might say that this is not safe enough. The unauthenticated system can still call our interface by intercepting the request and replaying the request within the token invalidation window of this minute!

You are right. However, between offense and defense, there is no absolute security. What we can do is to maximize the cost of the attack. Although this solution still has loopholes, it is simple enough to implement and does not excessively affect the performance of the interface itself (such as response time). Therefore, considering the safety, development cost, and impact on system performance, this solution is a compromise and reasonable.

In fact, there is one more detail that we did not consider, that is, how to store the AppID and password of each authorized caller on the micro server. Of course, this problem is not difficult. The easiest solution to think of is to store it in a database, such as MySQL. However, to develop non-business functions like authentication, it is best not to be excessively coupled with specific third-party systems.

For the storage of AppID and password, we had better support various storage methods flexibly, such as ZooKeeper, local configuration files, self-developed configuration center, MySQL, Redis, etc. We do not necessarily implement code for every storage method, but at least there must be extension points to ensure that the system has sufficient flexibility and scalability to minimize code changes when we switch storage methods.

5. Finalize requirements

At this point, the requirements have been sufficiently detailed and specific. Now, we will re-describe the requirements according to the authentication process. If you are familiar with UML, you can also use sequence diagrams and flowcharts to describe. However, the description is not the key point, a clear description is the most important. Considering that in the next part of object-oriented design, I will design classes, attributes, methods, interactions, etc. based on the text version of the requirement description, so the final requirement description I give here is the text version.

● When the caller makes an interface request, the URL, AppID, password, and timestamp are spliced ​​together, the token is generated through an encryption algorithm, and the token, AppID, and timestamp are spliced ​​in the URL and sent to the microserver.

● After receiving the interface request from the caller, the micro server disassembles the token, AppID, and timestamp from the request.

● The micro server first checks whether the passed timestamp and current time are within the token expiration time window. If the expiration time has elapsed, then the interface call authentication is regarded as a failure, and the interface call request is rejected.

● If the token verification has not expired, the micro-server will take out the password corresponding to the AppID from its own storage, generate another token through the same token generation algorithm, and match it with the token passed by the caller; if it is consistent, then If the authentication is successful, the interface call is allowed, otherwise the interface call is rejected.

This is the entire thinking process of our needs analysis, starting with the roughest and most vague needs, and gradually optimizing through the "problem-solving" approach, and finally a clear enough and achievable demand description.

Guess you like

Origin blog.csdn.net/zhujiangtaotaise/article/details/110374552