Master Unified Login, Exception, and Response Handling in Spring Boot with AOP and Interceptors
This article walks through building a Spring Boot application that centralizes user login verification, exception handling, and response formatting by leveraging AOP, custom HandlerInterceptors, @ControllerAdvice, and ResponseBodyAdvice to create clean, maintainable backend code.
Introduction
The following sections implement three unified functions for a Spring Boot project: user login permission verification, unified data format return, and unified exception handling.
Unified user login permission verification
Unified data format return
Unified exception handling
1. User Login Permission Verification
Initially each controller method performed its own login check, leading to duplicated code, higher maintenance cost, and no clear separation from business logic.
1.1 Initial Login Verification
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/m1")
public Object method(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
return true; // logged in
} else {
return false; // not logged in
}
}
@RequestMapping("/m2")
public Object method2(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
return true;
} else {
return false;
}
}
// other methods ...
}Problems of this approach:
Each method must repeat the same login check logic.
Adding more controllers increases modification and maintenance cost.
The login check is unrelated to business logic but must be written in every method.
A common AOP method is therefore required.
1.2 Spring AOP Login Verification Issues
Using a Spring AOP @Before or @Around advice seems natural, but two issues arise:
HttpSession cannot be obtained inside the advice.
Excluding specific endpoints (e.g., login and registration) from interception is cumbersome.
1.3 Spring Interceptor
Spring provides HandlerInterceptor to solve the above problems. Implementation steps:
Create a custom interceptor that implements HandlerInterceptor and overrides preHandle for login checks.
Register the interceptor in a class that implements WebMvcConfigurer via addInterceptors, specifying path patterns to include and exclude.
1.3.1 Preparation
package com.example.demo.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@RequestMapping("/login")
public boolean login(HttpServletRequest request, String username, String password) {
if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) {
if ("admin".equals(username) && "admin".equals(password)) {
HttpSession session = request.getSession();
session.setAttribute("userinfo", "admin");
return true;
} else {
return false;
}
}
return false;
}
@RequestMapping("/getinfo")
public String getInfo() {
log.debug("executed getinfo method");
return "executed getinfo method";
}
@RequestMapping("/reg")
public String reg() {
log.debug("executed reg method");
return "executed reg method";
}
}1.3.2 Custom Interceptor
package com.example.demo.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* Login interceptor
*/
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
return true;
}
log.error("Current user has no access permission");
response.setStatus(401);
return false;
}
}1.3.3 Register Interceptor
package com.example.demo.config;
import org.springframework.beans.factory.annotation.Autowired;
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 MyConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/user/login", "/user/reg");
}
}The flow before a controller is invoked now includes the interceptor's preHandle method.
2. Unified Exception Handling
Use @ControllerAdvice together with @ExceptionHandler to capture exceptions globally and return a consistent JSON structure.
package com.example.demo.config;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
@ControllerAdvice
public class ErrorAdvice {
@ExceptionHandler(Exception.class)
@ResponseBody
public HashMap<String, Object> handleException(Exception e) {
HashMap<String, Object> result = new HashMap<>();
result.put("code", "-1");
result.put("msg", e.getMessage());
return result;
}
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public HashMap<String, Object> handleArithmetic(ArithmeticException e) {
HashMap<String, Object> result = new HashMap<>();
result.put("code", "-2");
result.put("msg", e.getMessage());
return result;
}
}When a controller throws an exception, the appropriate method in the advice class returns a predefined error map.
3. Unified Data Return Format
Wrap all controller responses into a standard JSON object using @ControllerAdvice + ResponseBodyAdvice.
package com.example.demo.common;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.HashMap;
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof HashMap) {
return body; // already wrapped
}
if (body instanceof String) {
// special handling for String return type
return "{\"state\":1,\"msg\":\"\",\"data\":" + body + "}";
}
HashMap<String, Object> result = new HashMap<>();
result.put("state", 1);
result.put("msg", "");
result.put("data", body);
return result;
}
}For convenience, a utility class AjaxResult can be used to build the standard map manually.
package com.example.demo.common;
import java.util.HashMap;
public class AjaxResult {
public static HashMap<String, Object> success(Object data) {
HashMap<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("msg", "");
result.put("data", data);
return result;
}
public static HashMap<String, Object> fail(int code, String msg) {
HashMap<String, Object> result = new HashMap<>();
result.put("code", code);
result.put("msg", msg);
result.put("data", "");
return result;
}
public static HashMap<String, Object> fail(int code, String msg, Object data) {
HashMap<String, Object> result = new HashMap<>();
result.put("code", code);
result.put("msg", msg);
result.put("data", data);
return result;
}
}These components together provide a clean, maintainable way to enforce consistent login checks, error handling, and response structures across the entire Spring Boot application.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java High-Performance Architecture
Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
