📢 This article was translated by gemini-2.5-flash
Session Technology
Session: When a user opens a browser and accesses web server resources, a session is established. It ends when either side disconnects. A single session can include multiple requests and responses.
Session Tracking: A method to maintain browser state. The server needs to identify if multiple requests come from the same browser to share data across these requests within the same session.
Session Tracking Solutions:
- Client-side Session Tracking: Cookie
- Server-side Session Tracking: Session
- Token-based Solutions
Cookie
Cookie is an HTTP-supported tech. The first time a browser visits, the server sets a Cookie in the response header:
Set-Cookie: your_cookie. The browser automatically stores this Cookie locally and sends it in the request header (Cookie: your_cookie) on subsequent visits.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| @RestController
public class SessionController {
// Set Cookie
@GetMapping("/c1")
public Result cookie1(HttpServletResponse response){
response.addCookie(new Cookie("login_name", "yexca"));
return Result.success();
}
// Get Cookie
@GetMapping("/c2")
public Result cookie2(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
for (Cookie c : cookies){
if(c.getName().equals("login_name")){
System.out.println("login_name:"+c.getValue());
}
}
return Result.success();
}
}
|
But mobile clients can’t use Cookies, and Cookies don’t work cross-domain.
Same Domain: Same protocol, IP/domain, port.
Session
Implemented based on Cookies. The first browser request generates a Session, and the response header returns the Session ID Set-Cookie: JSESSIONID=session_id. The browser then automatically includes this ID in subsequent requests.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| @RestController
public class SessionController {
// Store value in HttpSession
@GetMapping("/s1")
public Result session1(HttpSession session){
log.info("HttpSession_set:{}", session.hashCode());
session.setAttribute("login_user","yexca");
return Result.success();
}
// Get value from HttpSession
@GetMapping("/s2")
public Result session2(HttpServletRequest request){
HttpSession session = request.getSession();
log.info("HttpSession_get:{}", session.hashCode());
Object loginUser = session.getAttribute("login_user");
log.info("loginUser:{}", loginUser);
return Result.success();
}
}
|
The issue with Session arises when using load balancing with multiple servers. If server one handles the first response and server two handles the second, server two won’t have the Session, making it unusable.
JWT Tokens
Token-based tech supports PCs and mobile devices. It solves authentication issues in clustered environments, reduces server storage pressure, but requires custom implementation.
JWT, or JSON Web Token. Official website:
https://jwt.io/
. It defines a compact, self-contained format for securely transmitting information between parties as JSON objects. Thanks to digital signatures, this information is reliable.
Structure:
- First Part: Header, records token type, signing algorithm, etc.
- Second Part: Payload, records custom information, default information, etc.
- Third Part: Signature, prevents Token tampering and ensures security. It’s calculated from the Header, Payload, and a secret key using a specified signing algorithm.
Add Dependency
1
2
3
4
5
| <dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
|
Generation and Parsing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| class WebApplicationTests {
// JWT Generation
@Test
public void jwtGenTest(){
Map<String,Object> claims = new HashMap<>();
claims.put("id", "233");
claims.put("user", "yexca");
String jwt = Jwts.builder()
.signWith(SignatureAlgorithm.HS256, "yexca") // Signing algorithm
.setClaims(claims) // Custom content (payload)
.setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) // Valid for 1h
.compact();
System.out.println(jwt);
}
// Output
// eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjIzMyIsImV4cCI6MTcwMjc5NzA4OCwidXNlciI6InlleGNhIn0.BqZDxEddGN4g4GyyfvkOtKYv7DVlIF6cWY9PGW2RbUU
// JWT Parsing
@Test
public void jwtParseTest(){
Claims claims = Jwts.parser()
.setSigningKey("yexca") // Secret key
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjIzMyIsImV4cCI6MTcwMjc5NzA4OCwidXNlciI6InlleGNhIn0.BqZDxEddGN4g4GyyfvkOtKYv7DVlIF6cWY9PGW2RbUU")
.getBody(); // Get the second part
System.out.println(claims);
}
// Output
// {id=233, exp=1702797088, user=yexca}
}
|
Login Authentication
Filter
Filter is one of the early JavaWeb “Big Three” components (Servlet, Filter, Listener). Filters can intercept resource requests to implement special functions like login authentication, unified encoding, or sensitive character handling.
Quick Start
Create Filter Class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| @WebFilter(urlPatterns = "/*") // Intercept path
public class DemoFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Filter.super.init(filterConfig);
System.out.println("Filter initialized");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter intercepted");
// Release
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
// Filter.super.destroy();
System.out.println("Filter destroyed");
}
}
|
Add Annotation to Application
1
2
| @ServletComponentScan // Enables support for servlet components
@SpringBootApplication
|
Intercept Paths
Adjust as needed, examples below:
| Intercept Type | urlPatterns | Meaning |
|---|
| Specific Path Interception | /login | Only /login access is intercepted |
| Directory Interception | /emps/* | All resources under /emps are intercepted, including /emps itself |
| Intercept All | /* | All resource access is intercepted |
Single Filter Execution Logic
Browser sends request -> Request intercepted -> Pre-release logic executes -> Release -> Post-release logic executes -> Browser receives response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| @WebFilter(urlPatterns = "/*") // Intercept path
public class DemoFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Filter.super.init(filterConfig);
System.out.println("Filter initialized");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter intercepted, pre-release logic");
// Release
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("Filter intercepted, post-release logic");
}
@Override
public void destroy() {
// Filter.super.destroy();
System.out.println("Filter destroyed");
}
}
|
Filter Chain
In a web application, you can configure multiple filters, which then form a filter chain.
Order: The priority of annotated filters is determined by the natural sorting of filter class names.
Logic: Browser sends request -> Request intercepted by A -> A’s pre-release logic executes -> A releases -> Request intercepted by B -> B’s pre-release logic executes -> B releases -> B’s post-release logic executes -> A’s post-release logic executes -> Browser receives response
Login Authentication
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| @Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// Get request URL
String requestURI = request.getRequestURI();
log.info("Request URL:{}",requestURI);
// Check if it contains "login"
if(requestURI.contains("login")){
log.info("Login operation, releasing");
filterChain.doFilter(servletRequest, servletResponse);
return; // Because login operation doesn't need subsequent logic
}
// Not login, get token
String jwt = request.getHeader("token");
// Check if token is valid
if(!StringUtils.hasLength(jwt)){
log.info("Request header token is empty");
// Convert object to JSON
Result error = Result.error("NOT_LOGIN");
String notLogin = JSONObject.toJSONString(error);
response.getWriter().write(notLogin);
return;
}
// Validate token
try {
JwtUtils.parseJWT(jwt);
}catch (Exception e){
e.printStackTrace();
log.info("Failed to parse token");
// Convert object to JSON
Result error = Result.error("NOT_LOGIN");
String notLogin = JSONObject.toJSONString(error);
response.getWriter().write(notLogin);
return;
}
// Release
log.info("Token is valid, releasing");
filterChain.doFilter(servletRequest,servletResponse);
}
}
|
Interceptor
Quick Start
Create Interceptor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| @Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override // Runs before target method, returns true to release, false to block
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// return HandlerInterceptor.super.preHandle(request, response, handler);
System.out.println("preHandle");
return true;
}
@Override // Runs after target method
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
System.out.println("postHandle");
}
@Override // Runs after view rendering, last to run
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
System.out.println("afterCompletion");
}
}
|
Create Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| @Configuration // Configuration class
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// WebMvcConfigurer.super.addInterceptors(registry);
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
}
}
|
Intercept Paths
| Path | Meaning | Example |
|---|
| /* | First-level Path | /emps, /login don’t match /emps/1 |
| /** | Any-level Path | /emps, /emps/1, /emps/1/2 |
| /emps/* | First-level paths under /emps | /emps/1 doesn’t match /emps, /emps/1/2 |
| /emps/** | Any-level paths under /emps | /emps, /emps/1, /emps/1/2 |
Interception Flow
If both Filter and Interceptor exist
Browser access -> Filter pre-release logic -> Filter release -> DispatcherServlet -> Interceptor preHandle -> Controller -> postHandle -> afterCompletion -> DispatcherServlet -> Filter post-release logic -> Respond to browser
Filters intercept all requests, while Interceptors only intercept resources within the Spring environment.
Login Authentication
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
| @Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override // Runs before target method, returns true to release, false to block
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// return HandlerInterceptor.super.preHandle(request, response, handler);
System.out.println("preHandle");
// Get request URL
String requestURI = request.getRequestURI();
log.info("Request URL:{}",requestURI);
// Check if it contains "login"
if(requestURI.contains("login")){
log.info("Login operation, releasing");
return true;
}
// Not login, get token
String jwt = request.getHeader("token");
// Check if token is valid
if(!StringUtils.hasLength(jwt)){
log.info("Request header token is empty");
// Convert object to JSON
Result error = Result.error("NOT_LOGIN");
String notLogin = JSONObject.toJSONString(error);
response.getWriter().write(notLogin);
return false;
}
// Validate token
try {
JwtUtils.parseJWT(jwt);
}catch (Exception e){
e.printStackTrace();
log.info("Failed to parse token");
// Convert object to JSON
Result error = Result.error("NOT_LOGIN");
String notLogin = JSONObject.toJSONString(error);
response.getWriter().write(notLogin);
return false;
}
// Release
log.info("Token is valid, releasing");
return true;
}
@Override // Runs after target method
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
System.out.println("postHandle");
}
@Override // Runs after view rendering, last to run
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
System.out.println("afterCompletion");
}
}
|