7 Practical Ways to Secure Spring Boot APIs with Permission Control
This article walks through seven concrete implementations for controlling access to Spring Boot 3.5.0 controller endpoints—including Spring Security, Sa‑Token, custom annotation with AOP, interceptor, URL‑based filter, custom HandlerMapping, and a Spring Cloud Gateway filter—providing code samples, configuration details, and test results to help developers choose the right approach for API security.
1. Introduction
In Spring Boot development, controlling API permissions is a core security measure. Complex role hierarchies (admin, user, guest) require fine‑grained restrictions to prevent unauthorized access and data leaks. This article presents seven mainstream ways to protect controller endpoints, balancing security and development efficiency.
2. Implementation Cases
2.1 Based on Spring Security
Dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>Static user configuration (application.yml)
spring:
security:
user:
name: pack
password: 123123
roles:
- USERS
- MGRSecurity configuration
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain packSecurityFilterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable());
http.authorizeHttpRequests(registry -> {
registry.requestMatchers("/resources/**").permitAll();
registry.requestMatchers("/api/**").authenticated();
});
http.formLogin(withDefaults());
http.exceptionHandling(ehc -> {
ehc.accessDeniedPage("/resources/noauthority.html");
});
return http.build();
}
}Controller example
@GetMapping("/query")
@PreAuthorize("hasRole('USERS')")
public ResponseEntity<String> query() {
return ResponseEntity.ok("query");
}2.2 Based on Sa‑Token
Sa‑Token is a lightweight Java permission framework that supports Spring Boot 2/3.
Dependency
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.45.0</version>
</dependency>Simulated login endpoint
@GetMapping("/login")
public ResponseEntity<String> login() {
if (!StpUtil.isLogin()) {
StpUtil.login("xxxooo", 15000);
}
return ResponseEntity.ok(StpUtil.getTokenValueByLoginId("xxxooo"));
}Custom StpInterface to provide roles
@Component
public class SaPermissionStpInterface implements StpInterface {
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
return List.of();
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
return List.of("ADMIN", "MGR");
}
}Interceptor to enforce permission
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
}Controller example
@GetMapping("/query")
@SaCheckRole(value = {"USERS"})
public ResponseEntity<String> query() {
return ResponseEntity.ok("query");
}2.3 Custom Annotation + AOP
When a heavyweight security framework is not desired, a custom annotation combined with AOP can enforce permissions.
Annotation definition
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Permission {
String[] value();
}AOP aspect
@Aspect
@Component
public class PermissionAspect {
@Before("@annotation(permission)")
public void checkPermission(JoinPoint joinPoint, Permission permission) {
String[] neededPerm = permission.value();
Set<String> permissions = UserContext.getPermissions();
if (Collections.disjoint(permissions, Set.copyOf(Arrays.asList(neededPerm)))) {
throw new AccessDeniedException("权限不足");
}
}
}
// Simulated user context
public class UserContext {
public static Set<String> getPermissions() {
return Set.of("user.create", "user.update", "user.query");
}
}Controller example
@GetMapping("/query")
@Permission({"api.query"})
public ResponseEntity<String> query() {
return ResponseEntity.ok("query");
}2.4 Custom Annotation + Interceptor
This approach moves the check to the web layer using HandlerInterceptor.
Interceptor definition
@Component
public class PermissionInterceptor implements HandlerInterceptor {
private final ObjectMapper objectMapper;
public PermissionInterceptor(ObjectMapper objectMapper) { this.objectMapper = objectMapper; }
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) return true;
HandlerMethod hm = (HandlerMethod) handler;
Permission permission = hm.getMethodAnnotation(Permission.class);
if (permission != null) {
String[] neededPerm = permission.value();
Set<String> permissions = UserContext.getPermissions();
if (Collections.disjoint(permissions, Set.copyOf(Arrays.asList(neededPerm)))) {
response.setContentType("application/json;charset=utf-8");
response.getWriter().println(objectMapper.writeValueAsString(Map.of("code", -1, "msg", "无权限")));
return false;
}
}
return true;
}
}MvcConfigurer to register the interceptor
@Configuration
public class PermissionMvcConfigurer implements WebMvcConfigurer {
private final PermissionInterceptor permissionInterceptor;
public PermissionMvcConfigurer(PermissionInterceptor permissionInterceptor) {
this.permissionInterceptor = permissionInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(permissionInterceptor).addPathPatterns("/api/**");
}
}Controller example (same as AOP case)
@GetMapping("/query")
@Permission({"api.query"})
public ResponseEntity<String> query() {
return ResponseEntity.ok("query");
}2.5 URL‑Path Matching Filter
Extending OncePerRequestFilter allows permission checks based on the request URI.
Filter implementation
@Component
public class PermissionFilter extends OncePerRequestFilter {
private static final Map<String, Set<String>> permissions = new HashMap<>();
static {
permissions.put("/api/query", Set.of("user.query"));
permissions.put("/api/create", Set.of("user.create"));
}
private final ObjectMapper objectMapper;
public PermissionFilter(ObjectMapper objectMapper) { this.objectMapper = objectMapper; }
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String requestUri = request.getRequestURI();
Set<String> userPerms = UserContext.getPermissions();
if (!Collections.disjoint(userPerms, permissions.get(requestUri))) {
filterChain.doFilter(request, response);
return;
}
response.setContentType("application/json;charset=utf-8");
response.getWriter().println(objectMapper.writeValueAsString(Map.of("code", -1, "msg", "无权限")));
}
}Controller endpoints
@GetMapping("/query")
public ResponseEntity<String> query() { return ResponseEntity.ok("query"); }
@GetMapping("/create")
public ResponseEntity<String> create() { return ResponseEntity.ok("create"); }Simulated user permissions
public class UserContext {
public static Set<String> getPermissions() {
return Set.of("user.query"); // adjust per test case
}
}2.6 Custom HandlerMapping (high‑performance option)
The article references a separate write‑up that replaces the core MVC mapping component for maximum performance. The detailed code is omitted here but can be found in the linked article.
2.7 Spring Cloud Gateway Permission Validation
A dedicated gateway service can perform token parsing and permission checks before routing to downstream services.
Dependency
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
</dependency>Gateway route configuration (application.yml)
spring:
cloud:
gateway:
server:
webflux:
routes:
- id: path_route
uri: http://localhost:8080
predicates:
- Path=/api-1/**
filters:
- StripPrefix=1GlobalFilter implementation
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class PermissionGlobalFilter implements GlobalFilter {
private static final Map<String, Set<String>> permissions = new HashMap<>();
private static final Map<String, Set<String>> path_permissions = new HashMap<>();
static {
permissions.put("xxxooo", Set.of("user.query"));
permissions.put("aaabbb", Set.of("user.create"));
path_permissions.put("/api-1/api/query", Set.of("user.query"));
path_permissions.put("/api-1/api/create", Set.of("user.create"));
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getRawPath();
String token = request.getQueryParams().getFirst("token");
if (!StringUtils.hasLength(token)) {
return writeErrorResponse(exchange, "缺少Token,请先登录");
}
Set<String> requiredPermission = path_permissions.get(path);
if (requiredPermission == null || requiredPermission.isEmpty()) {
return chain.filter(exchange);
}
Set<String> userPermissions = permissions.get(token);
if (Collections.disjoint(requiredPermission, userPermissions)) {
return writeErrorResponse(exchange, "无权限");
}
return chain.filter(exchange);
}
private Mono<Void> writeErrorResponse(ServerWebExchange exchange, String message) {
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
String result = "{\"code\": -1, \"msg\": \"" + message + "\"}";
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
return exchange.getResponse().writeWith(Mono.just(buffer));
}
}Test screenshots (omitted here) demonstrate successful token‑based access and proper error responses when permissions are insufficient.
Conclusion
The seven approaches range from using the built‑in Spring Security framework to lightweight custom solutions and a dedicated gateway filter. Developers can select a method based on project size, performance requirements, and architectural preferences.
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.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.
