First of all, we need to be clear that the front-end and back-end interaction protocols we use are the HTTP protocol, and the HTTP protocol is a stateless protocol. The so-called stateless means that each request is independent, and the next request will not carry the previous request. data. The interaction between the browser and the server is based on the HTTP protocol, which means that now we access the login interface through the browser and realize the login operation. Next, when we perform other business operations, the server does not know Has this employee logged in? Because the HTTP protocol is stateless and the two requests are independent, it is impossible to judge whether the employee has logged in or not.
At this time, session tracking is needed.
Session Tracking: A method of maintaining browser state. The server needs to identify whether multiple requests come from the same browser in order to share data between multiple requests in the same session.
There are two types of session tracking technologies:
-
Cookies (client session tracking technology)
-
Data is stored in the client browser
-
-
Session (server-side session tracking technology)
-
Data is stored on the server side
-
-
token technology
Option 1: Cookie
Cookie is a client session tracking technology, which stores shared data in the client browser.
-
The server will automatically respond to the cookie to the browser.
-
After the browser receives the response data, it will automatically store the cookie locally in the browser.
-
In subsequent requests, the browser will automatically carry the cookie to the server.
Why is this all automatic?
It is because cookie is a technology supported by the HTP protocol, and all major browser manufacturers support this standard. The HTTP protocol officially provides us with a response header and a request header:
-
Response header Set-Cookie: Set Cookie data
-
Request header Cookie: carrying Cookie data
Advantages: technologies supported in the HTTP protocol
Cons: Cookies cannot be used in mobile apps
Unsafe, stored locally, and can also be manually disabled by the user
Cookies cannot cross domains
Scheme 2 Session session
Session, it is a server-side session tracking technology, so it stores shared data on the server side.
When the browser requests a session for the first time, the session object does not exist. At this time, the server will automatically create a session object, and each object will have an ID. Then when the server responds to the browser with data, it will respond to the browser with the session ID through the cookie.
Next, in each subsequent request, the cookie data will be obtained and carried to the server. Next, the server gets the value of the cookie JSESSIONID, which is the ID of the Session. After getting the ID, it will find the session object Session corresponding to the current request from among the many Sessions.
In this way, we can share data between multiple requests in the same session through the Session session object.
Scheme 3 jwt token
The token is actually an identity for the user, which consists of three parts: the header, the payload (carrying the required business data), and the signature.
Track sessions through token tracking technology. When requesting a login interface, if the login is successful, we can generate a token, which is the user’s legal identity credential. When we respond to data, we can respond the token to the front end.
The token can store this identity credential in the place specified by itself, and the response body/header can be used. Next, the token must be carried to the server in each request to verify the validity of the token. This allows data to be shared.
Pros and Cons
-
advantage:
-
Support PC and mobile
-
Solve the authentication problem in the cluster environment
-
Reduce the storage pressure on the server (no need to store on the server side)
-
-
Disadvantages: You need to implement it yourself (including token generation, token transfer, and token verification)
Generate and Validate
First you need to reference dependencies in pom:
<!-- JWT dependency --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
generate jwt token
@Test public void genJwt(){ Map<String,Object> claims = new HashMap<>(); claims. put("id",1); claims. put("username","Tom"); String jwt = Jwts. builder() .setClaims(claims) //custom content (payload) .signWith(SignatureAlgorithm.HS256, "iheima") //signature algorithm .setExpiration(new Date(System.currentTimeMillis() + 24*3600*1000)) //validity period .compact(); System.out.println(jwt); }
parsing token
@Test public void parseJwt(){ Claims claims = Jwts. parser() .setSigningKey("iheima")//Specify the signing key (must ensure that the same signing key is used when generating the token) .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjcyNzI5NzMwfQ.fHi0Ub8npbyt71UqLXDdLyipptLgxBUg_mSuGJtXtBk") .getBody(); System.out.println(claims); }
And we can use it directly in the tool class
public class JwtUtils { private static String signKey = "itheima";//signature key private static Long expire = 43200000L; //valid time /** * Generate JWT token * @param claims The content stored in the payload of the second part of the JWT load * @return */ public static String generateJwt(Map<String, Object> claims){ String jwt = Jwts. builder() .addClaims(claims)//custom information (payload) .signWith(SignatureAlgorithm.HS256, signKey)//signature algorithm (header) .setExpiration(new Date(System.currentTimeMillis() + expire))//expiration time .compact(); return jwt; } /** * Parse the JWT token * @param jwt JWT token * @return The content stored in the second part of JWT load payload */ public static Claims parseJWT(String jwt){ Claims claims = Jwts. parser() .setSigningKey(signKey)//Specify the signature key .parseClaimsJws(jwt)//Specify token Token .getBody(); return claims; } }
After we use the token, we will find that in order to verify the validity of the token, it must be verified on each interface, which is too complicated and troublesome, so we only need to intercept it and verify it uniformly. There are two methods here: filter Filter and interceptor Interceptor.
Filter Filter
It is to use filters to perform unified token verification. Only after passing the verification will it be released and let it continue to call the interface method.
Quick Start
-
Step 1, define the filter: 1. Define a class, implement the Filter interface, and rewrite all its methods.
-
Step 2, configure the filter: Add the @WebFilter annotation to the Filter class to configure the path to intercept resources. Add @ServletComponentScan to the boot class to enable Servlet component support.
@WebFilter(urlPatterns = "/*") //Configure the request path to be intercepted by the filter ( /* means to intercept all browser requests) public class DemoFilter implements Filter { @Override //Initialization method, only called once public void init(FilterConfig filterConfig) throws ServletException { System.out.println("init initialization method executed"); } @Override //Call after intercepting the request, call multiple times public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("Demo intercepted the request...logic before release"); // release chain. doFilter(request, response); } @Override //Destroy method, only called once public void destroy() { System.out.println("destroy method executed"); } }
The main one is the doFilter() method, and the login verification is also mainly in this method.
@Slf4j @WebFilter(urlPatterns = "/*") //Intercept all requests public class LoginCheckFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { //Preposition: Mandatory conversion to the request object and response object of the http protocol (conversion reason: to use the unique method in the subclass) HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; //1. Get the request url String url = request. getRequestURL(). toString(); log.info("Request path: {}", url); //Request path: http://localhost:8080/login //2. Determine whether login is included in the request url, if it is included, it means login operation, let it go if(url. contains("/login")){ chain.doFilter(request, response);//Release request return;//End the execution of the current method } //3. Get the token in the request header (token) String token = request. getHeader("token"); log.info("The token obtained from the request header: {}", token); //4. Determine whether the token exists, if not, return an error result (not logged in) if(!StringUtils.hasLength(token)){ log.info("Token does not exist"); Result responseResult = Result. error("NOT_LOGIN"); //Convert the Result object to a JSON format string (fastjson is a conversion tool class provided by Alibaba for implementing objects and json) String json = JSONObject.toJSONString(responseResult); response.setContentType("application/json;charset=utf-8"); //response response.getWriter().write(json); return; } //5. Parse the token, if the parsing fails, return an error result (not logged in) try { JwtUtils. parseJWT(token); }catch (Exception e){ log.info("Token parsing failed!"); Result responseResult = Result. error("NOT_LOGIN"); //Convert the Result object to a JSON format string (fastjson is a conversion tool class provided by Alibaba for implementing objects and json) String json = JSONObject.toJSONString(responseResult); response.setContentType("application/json;charset=utf-8"); //response response.getWriter().write(json); return; } //6. Release chain.doFilter(request, response); } }
Among them, we use the tool class fastjson that converts objects into json strings, which requires us to introduce dependencies
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> </dependency>
Interceptor Interceptor
-
Is a mechanism for dynamically intercepting method calls, similar to filters.
-
Interceptors are provided in the Spring framework to dynamically intercept the execution of controller methods.
The role of the interceptor:
-
Intercept requests, and execute preset codes according to business needs before and after the specified method call.
Quick Start
Let’s learn the basic use of the interceptor through the quick start program. The steps to use an interceptor are similar to filters, and are also divided into two steps:
-
define interceptor
-
Register configuration interceptor
//custom interceptor @Component public class LoginCheckInterceptor implements HandlerInterceptor { //Execute before the target resource method is executed. Return true: let go Return false: don't let go @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle .... "); return true; //true means release } // Execute after the target resource method is executed @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle ... "); } // Execute after the view is rendered, and finally execute @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion .... "); } } //Register configuration interceptor @Configuration public class WebConfig implements WebMvcConfigurer { //custom interceptor object @Autowired private LoginCheckInterceptor loginCheckInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { //Register custom interceptor object registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");//Set the request path intercepted by the interceptor ( /** means to intercept all requests) } }
in:
preHandle method: Executed before the target resource method is executed. Return true: let go Return false: don’t let go
postHandle method: Executed after the target resource method is executed
afterCompletion method: Executed after the view is rendered, and finally executed
The interceptor /**must be two**, otherwise it will only intercept /emp or /dept cannot intercept the multi-level path of /emp/1.
Finally, both interception methods can achieve the final verification, which one to use?