“This article has participated in the good article call order activity, click to see: back end, big front end double track submission, 20,000 yuan prize pool for you to challenge!”

preface

As a JAVA developer, THERE have been several interviews before, the interviewer asked me, JAVAWeb master, I did not know how to answer, Web, daily development, what is used? Today we’re going to talk about the top three things you should know about JAVAWeb.

The development course

1. Once upon a time, the Web was basically just browsing documents, and since browsing was the case, as a server, there was no need to keep track of who was browsing what documents at any given time. Every request was a new HTTP protocol, request plus response, especially when I didn’t have to remember who had just sent the HTTP request. Every request is new to me.

2, but with the rise of interactive Web applications like online shopping website, need to log in to the site, etc., will soon face a problem, that is to manage session, must remember who login system, who puts items to their shopping cart, that is to say I must distinguish everyone, this is a big challenge, Since HTTP requests are stateless, the idea was to give everyone a session ID, which is basically a random string that everyone gets different, and send it to me every time they make an HTTP request to me, so I can tell who’s who.

3, so everyone happy, but the server is not happy, everyone only need to save their own session ID, while the server to save everyone’s session ID! If you have more access to the server, you have to have thousands, even hundreds of thousands.

For example, I use two machines to form A cluster, and little F logs in to the system through machine A, so the session ID will be saved on machine A. What if little F’s next request is forwarded to machine B? Machine B doesn’t have little F’s session ID.

Sometimes will use A little trick: session sticky, is to let the small F request has been sticky on the machine A, but this also does not work, if the machine A hung up, still have to turn to the machine B.

So I’m going to have to do a copy of the session, and I’m going to have to move the session ID between the two machines.

Then there was the idea of Memcached: Storing session ids in one place, where all machines can access the data, makes replication unnecessary, but increases the possibility of a single point of failure. If the machine that is responsible for the session fails, everyone has to log in again.

I’m also trying to cluster this single point machine to increase reliability, but anyway, this little session is a huge burden for me, right

4 So some people have been thinking, why should I save this stupid session, only let each client to save the good?

However, if I do not save these session ids, how can I verify that the session ID sent by the client is actually generated by me? Without verification, we don’t even know if they’re a legitimate user, so the bad guys can just fake session ids and do whatever they want.

Well, the key is verification!

For example, if little F has logged in to the system, I send him a token, which contains the user ID of little F. The next time little F requests to access me through Http, he can bring this token over the Http header.

But it’s no different than a session ID, anyone can forge it, so I have to figure out a way to make it impossible for anyone to forge it.

Let’s make a signature for the data. For example, I use hMAC-SHA256 algorithm and add a key that only I know to make a signature for the data. I use the signature and the data as a token.

I do not save this token. When little F sends this token to me, I use the same HMAC-SHA256 algorithm and the same key to calculate the signature of the data again and compare it with the signature in the token. If it is the same, I know that little F has logged in. And you can directly get the user ID of small F. If it is different, the data must have been tampered with by someone. I will tell the sender: Sorry, there is no authentication.

The data in the Token is stored in plain text (although I would encode it in Base64, it’s not encryption) and can still be seen by others, so I can’t store sensitive information like passwords in it.

Of course, if a person’s token is stolen by someone else, then I can’t help it, I will also think that the thief is a legitimate user, which is actually the same as a person’s session ID stolen by someone else.

In this way, I don’t save the session ID, I just generate the token, and then verify the token, I use my CPU to calculate the time to get my session storage space!

With the session ID burden removed, my cluster of machines can now easily scale horizontally, increasing user visits, and just add machines. This stateless feeling is so good!

Cookie

1. What are cookies

Cookie, translated into Chinese meaning ‘Cookie’, is a mechanism proposed by the W3C and developed by the Netscape community. At present, Cookie has become the standard, and all the major browsers such as IE, Netscape, Firefox, Opera and so on support Cookie.

The server has no way of knowing the identity of the client from the network connection alone. What to do? Just issue a pass to each client, one for each client, and whoever visits must carry their own pass. So the server can identify the client from the pass. That’s how cookies work.

Cookie is a mechanism for the client to save user information. It is used to record some user information and also a way to implement Session. Cookies store a limited amount of data and are stored in the client browser. Different browsers have different storage sizes, but generally no more than 4KB. So using cookies can actually only store a small amount of text information (key-value format).

2. The mechanism of cookies

When a user first visits and logs in to a website, the cookie setting and sending process goes through the following four steps:

  1. The client sends a request to the server;

  2. The server sends an HttpResponse response to the client containing the header of the set-cookie;

  3. The client saves the cookie and later sends a request to the server, the HttpRequest request will contain a cookie header;

  4. The server returns the response data.

To explore this process, code was written and tested as follows:

So what I’ve done in the doGet method is I’ve added a new Cookie object and added it to the HttpResponse object

@RestController
public class TestController {

    @GetMapping(value = "/doGet")
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Cookie cookie = new Cookie("jiangwang",System.currentTimeMillis()+"");
        // Set the life cycle to MAX_VALUEcookie.setMaxAge(Integer.MAX_VALUE); resp.addCookie(cookie); }}Copy the code

The browser enters the address for access, and the result is as shown in the figure below:

It can be seen that Response Headers contains set-cookie Headers, while Request Headers contains Cookie Headers. Name and value are set as described above.

3. The attribute of the Cookie

Expires

This property is used to set the validity period of the Cookie. The maxAge in the Cookie is used to represent this attribute in seconds. The Cookie uses getMaxAge() and setMaxAge(int maxAge) to read and write the property. MaxAge has three values, positive, negative, and 0.

If the maxAge attribute is positive, the Cookie will automatically expire after maxAge seconds. Cookies with a positive maxAge are persisted by the browser, that is, written to the corresponding Cookie file (which is stored in different locations for each browser). The Cookie is still valid when the client logs in to the site, whether the browser is closed or the computer is closed, as long as it is logged in before maxAge seconds. The Cookie information in the following code will always be valid.

Cookie cookie = new Cookie("jiangwang",System.currentTimeMillis()+"");
// Set the life cycle to MAX_VALUE, which is valid forever
cookie.setMaxAge(Integer.MAX_VALUE);
resp.addCookie(cookie);
Copy the code

When the maxAge attribute is negative, it indicates that the Cookie is only a temporary Cookie and is not persisted. It is valid only in the browser window or the open child window of the window. The Cookie is immediately invalid after the browser is closed.

Cookie cookie = new Cookie("jiangwang",System.currentTimeMillis()+"");
// Set the life cycle to MAX_VALUE, which is valid forever
cookie.setMaxAge(-1);
resp.addCookie(cookie);
Copy the code

When maxAge is 0, the Cookie is deleted immediately.

Cookie[] cookies = req.getCookies();
Cookie cookie = null;

// get Cookie
for (Cookie ck : cookies) {
    if ("jiangwang".equals(ck.getName())) {
        cookie = ck;
        break; }}if (null! = cookie) {// Delete a cookie
    cookie.setMaxAge(0);
    resp.addCookie(cookie);
}
Copy the code

Modify or delete cookies

HttpServletResponse provides only a Cookie operation addCookie(Cookie Cookie), so to modify the Cookie can only use a Cookie with the same name to override the original Cookie. If you want to delete a Cookie, you just need to create a new Cookie with the same name, set maxAge to 0, and override the original Cookie.

In addition to value and maxAge, the attributes of the new Cookie, such as name, path, and domain, must be consistent with the original ones to achieve the effect of modification or deletion. Otherwise, the browser will treat two different cookies as not overwritten.

The Cookie domain

Cookies are not allowed to cross domain names. The privacy security mechanism prohibits websites from illegally obtaining cookies from other websites.

Under normal circumstances, two sub-domains under the same sub-domain name, such as a1.jiangwang.com and a2.jiangwang.com, cannot interact with each other because their domain names are different. Jiangwnag.com. Set the domain parameter to. Jiangwang.com. Using a1.jiangwang.com and a2.jiangwang.com, you can access the same cookie

A level-1 domain name is also called a top-level domain name. It consists of a string and a suffix. Familiar with the first level domain name baidu.com, QQ.com. Com, cn, net, etc are common suffixes. The second level domain name is derived from the first level domain name. For example, if the first level domain name is abc.com, blog.abc.com and www.abc.com are derived from the second level domain name.

The path of the Cookie

The path attribute determines the path to allow access to the Cookie. For example, a “/” is used to allow cookies for all paths

4. The application

The most typical application of Cookies is to determine whether the registered user has logged in to the website. The user may be prompted whether to retain the user information when entering the website next time to simplify the login procedure. These are the functions of Cookies. Another important application is for things like “shopping carts.” Users may select different items from different pages on the same site over a period of time, with Cookies written into the information to be extracted at the time of final payment.

Session

1. What is Session

In WEB development, the server can create a session object (session object) for each user’s browser. Note that one session object is exclusive to each browser (by default). Therefore, when the user needs to save the user data, the server program can write the user data to the user’s browser-exclusive session, and when the user uses the browser to access other programs, the other programs can extract the user’s data from the user’s session and serve the user.

2.Session implementation principle

When the server creates a session, it sends the session ID back to the client in the form of a cookie, so that whenever the client’s browser is off, when they visit the server again, they come in with the session ID, and the server sees that the client’s browser has come in with the session ID, It will use the corresponding session in memory to service it. This can be proved with the following code:

@RestController
public class TestController {

    @GetMapping(value = "/doGet")
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=UTF-8");
        // Use getSession() of the request object to get a session and create one if one does not exist
        HttpSession session = request.getSession();
        // Store data in the session
        session.setAttribute("mayun"."马云");
        // Obtain the session Id
        String sessionId = session.getId();
        // Check whether the session is newly created
        if (session.isNew()) {
            response.getWriter().print("Session created successfully, session ID is:"+sessionId);
        }else {
            response.getWriter().print("The server already has this session and the session ID is:"+sessionId); }}}Copy the code

On the first visit, the server creates a new Sesion and sends the session Id as a cookie to the client browser, as shown below:

When you request the server again, you can see that when the browser requests the server again, it will pass the session Id stored in the cookie to the server, as shown in the figure below:

3. Session creation and destruction

The first call to request.getsession () creates a new Session. You can use the isNew() method to determine if the Session isNew

// Use getSession() of the request object to get a session and create one if one does not exist
HttpSession session = request.getSession();
// Obtain the session Id
String sessionId = session.getId();
// Check whether the session is newly created
if (session.isNew()) {
    response.getWriter().print("Session created successfully, session ID is:"+sessionId);
}else {
    response.getWriter().print("The server already has a session, session ID is:"+sessionId);
}
Copy the code

If the session object is not used for 30 minutes by default, the server will automatically destroy the session. You can also manually set the session expiration time, for example:

session.setMaxInactiveInterval(10*60);// The session expires after 10 minutes
Copy the code

When you need to manually invalidate a Session in a program, you can manually call the session.invalidate method to destroy the Session.

HttpSession session = request.getSession(); // Destroy session session.invalidate() by calling session.invalidate() manually;Copy the code

Interview question: When the browser closes, the session is destroyed? Not right.

After a Session is generated, the server updates the last access time of the Session and maintains the Session as long as the user continues to access it. To prevent memory overflow, the server will delete any Session that has not been active for a long time from memory. This is the timeout of the Session. If you do not access the server after the timeout period, the Session will automatically expire.

Token

1. What is a Token

Token means “token”. It is a string of characters generated by the server and used as an identifier for the client to make a request.

After the user logs in for the first time, the server generates a token and returns it to the client. In the future, the client only needs to bring the token to request data, without the need to bring the user name and password again.

Simple token composition; Uid (the unique identity of the user), Time (the timestamp of the current time), and sign (the signature). The first few bits of the token are hexadecimal characters of a certain length compressed by the hash algorithm. To prevent token disclosure).

2. The principle of the Token

  1. The user sends the request with a user name and password
  2. Routine check
  3. The program returns a Token to the client
  4. The client stores the Token and carries it every time it sends a request
  5. The server authenticates the Token and returns the data

3. The use of Token

Spring Boot and Jwt integration examples

The project relies on POM.xml

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.247.</version>
</dependency>

    <dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.103.</version>
</dependency>
Copy the code

Custom annotations

// An annotated LoginToken that requires login to operate
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginToken {
    boolean required(a) default true;
}
Copy the code
// The PassToken used to skip validation
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required(a) default true;
}
Copy the code

User entity class, and query Service

public class User {
    private String userID;
    private String userName;
    private String passWord;

    public String getUserID(a) {
        return userID;
    }

    public void setUserID(String userID) {
        this.userID = userID;
    }

    public String getUserName(a) {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassWord(a) {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord; }}Copy the code
@Service
public class UserService {
 
    public User getUser(String userid, String password){
        if ("admin".equals(userid) && "admin".equals(password)){
            User user=new User();
            user.setUserID("admin");
            user.setUserName("admin");
            user.setPassWord("admin");
            return user;
        }
        else{
            return null; }}public User getUser(String userid){
        if ("admin".equals(userid)){
            User user=new User();
            user.setUserID("admin");
            user.setUserName("admin");
            user.setPassWord("admin");
            return user;
        }
        else{
            return null; }}}Copy the code

Token is generated

@Service
public class TokenService {
    /** * Expiration time 10 minutes */
    private static final long EXPIRE_TIME = 10 * 60 * 1000;
 
    public String getToken(User user) {
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        String token="";
        token= JWT.create().withAudience(user.getUserID()) // Save the user ID to the token
            .withExpiresAt(date) // The token will expire in 10 minutes
            .sign(Algorithm.HMAC256(user.getPassWord())); // Use password as the token key
        returntoken; }}Copy the code

The interceptor intercepts the token

package com.jw.interceptor;
 
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.jw.annotation.LoginToken;
import com.jw.annotation.PassToken;
import com.jw.entity.User;
import com.jw.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
 

public class JwtInterceptor implements HandlerInterceptor{
 
    @Autowired
    private UserService userService;
 
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        String token = httpServletRequest.getHeader("token");// Fetch the token from the HTTP request header
        // If not mapped to the method directly through
        if(! (objectinstanceof HandlerMethod)){
            return true;
        }
        HandlerMethod handlerMethod=(HandlerMethod)object;
        Method method=handlerMethod.getMethod();
        // Check if there are passtoken comments. If there are passtoken comments, the authentication will be skipped
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true; }}// Check whether there are any comments that require user permissions
        if (method.isAnnotationPresent(LoginToken.class)) {
            LoginToken loginToken = method.getAnnotation(LoginToken.class);
            if (loginToken.required()) {
                // Perform authentication
                if (token == null) {
                    throw new RuntimeException("No token, please log in again.");
                }
                // Obtain the user ID in the token
                String userId;
                try {
                    userId = JWT.decode(token).getAudience().get(0);
                } catch (JWTDecodeException j) {
                    throw new RuntimeException("401");
                }
                User user = userService.getUser(userId);
                if (user == null) {
                    throw new RuntimeException("User does not exist, please log in again.");
                }
                / / authentication token
                JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassWord())).build();
                try {
                    jwtVerifier.verify(token);
                } catch (JWTVerificationException e) {
                    throw new RuntimeException("401");
                }
                return true; }}return true;
    }
 
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {}@Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {}}Copy the code

Register interceptor

package com.jw.config;
 
import com.jw.interceptor.JwtInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 

@Configuration
public class InterceptorConfig implements WebMvcConfigurer{
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor())
            .addPathPatterns("/ * *");    // Intercept all requests and determine whether a login is required by the @loginRequired annotation
 
        // Register the TestInterceptor interceptor
// InterceptorRegistration registration = registry.addInterceptor(jwtInterceptor());
// registration.addPathPatterns("/**"); // Add the intercept path
/ / registration. ExcludePathPatterns (/ / add not intercept path
// "/**/*.html", // static HTML resources
// "/**/*.js", // static resources
// "/**/*. CSS ", // CSS static resources
// "/**/*.woff",
// "/**/*.ttf",
// "/swagger-ui.html"
/ /);
    }
    @Bean
    public JwtInterceptor jwtInterceptor(a) {
        return newJwtInterceptor(); }}Copy the code

Log on to the Controller

@RestController
public class LoginController {
 
    @Autowired
    private UserService userService;
    @Autowired
    private TokenService tokenService;
 
    @PostMapping("login")
    public Object login(String username, String password){
        JSONObject jsonObject=new JSONObject();
        User user=userService.getUser(username, password);
        if(user==null){
            jsonObject.put("message"."Login failed!");
            return jsonObject;
        }else {
            String token = tokenService.getToken(user);
            jsonObject.put("token", token);
            jsonObject.put("user", user);
            returnjsonObject; }}@LoginToken
    @GetMapping("/getMessage")
    public String getMessage(a){
        return "You have passed the test."; }}Copy the code

Configure global exception catching

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ResponseBody
    @ExceptionHandler(Exception.class)
    public Object handleException(Exception e) {
        String msg = e.getMessage();
        if (msg == null || msg.equals("")) {
            msg = "Server error";
        }
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code".1000);
        jsonObject.put("message", msg);
        returnjsonObject; }}Copy the code

Postman test

Access token

Without the token to log in

A token to log in

Error token login

4. The advantages and disadvantages of Token

Advantages:

  1. Support cross-domain access: Cookie is not allowed to collapse access, token support;
  2. Stateless: Token stateless, session stateful;
  3. Decoupling: No need to bind to a particular authentication scheme. Tokens can be generated anywhere, as long as you can make a Token generation call when your API is called;
  4. More suitable for mobile applications: Cookie does not support mobile terminal access;
  5. Performance: Better performance in the process of network transmission;
  6. Based on standardization: Your API can adopt standardized JSON Web Tokens (JWT). The standard already exists in multiple back-end libraries (.NET, Ruby, Java,Python, PHP) and is supported by several companies (e.g., Firebase,Google, Microsoft).

Disadvantages:

  1. Bandwidth usage, normally larger than session_ID, takes up more traffic, takes up more bandwidth, if your site has 100,000 browsers per month, that means tens of megabits of extra traffic. It doesn’t sound like much, but it can add up over time. In fact, many people store more information in JWT;
  2. Unable to log out in the server, so long difficult to solve the hijacking problem;
  3. Performance issues. One of the selling points of JWT is the encrypted signature, because of this feature, the receiver can verify that the JWT is valid and trusted. But in most Web authentication applications, JWT is stored in a Cookie, which means you have two layers of signature. That sounds great, but it doesn’t have any advantages, and for that, you need to spend twice as much CPU to verify the signature. This is not ideal for Web applications with stringent performance requirements, especially for single-threaded environments.

At the end

I am a code is being hit is still trying to advance. If the article is helpful to you, remember to like, follow yo, thank you!