使用 Spring Security 时,经常会看见 403(无权限),默认情况下显示的效果如下图:

而在实际项目中可能是一个异步请求,因此显示上述效果对于用户就不是特别友好了。为了避免这种情况,Spring Security 支持用户自定义 403 权限受限错误。
AccessDeniedHandler 接口的作用是对用户访问受保护资源时权限不足的情况进行处理。
接口定义如下:
package org.springframework.security.web.access;
import java.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
/**
* 由 {@link ExceptionTranslationFilter} 调用,处理访问被拒绝的情况
* 当已认证用户尝试访问其权限不足的受保护资源时,会抛出 AccessDeniedException,
* 此接口负责将该异常转换为适当的 HTTP 响应(如403状态码或重定向到错误页面)
*
* @see ExceptionTranslationFilter
* @see AccessDeniedException
*/
public interface AccessDeniedHandler {
/**
* 处理访问被拒绝的请求
*
* @param request 导致 AccessDeniedException 的 HTTP 请求
* @param response 用于向客户端发送错误响应
* @param accessDeniedException 访问被拒绝的异常对象
* @throws IOException 发生 IO 异常时抛出
* @throws ServletException 发生 Servlet 异常时抛出
*/
void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)
throws IOException, ServletException;
}@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpServletResponse.setHeader("Content-Type","application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}");
out.flush();
out.close();
}
}注意,其中 mime 类型可以在 w3c 中进行查询 https://www.w3school.com.cn/media/media_mimeref.asp。
在配置类中添加异常处理器,设置访问受限后交给哪个对象进行处理。例如:
@Configuration
@EnableMethodSecurity(jsr250Enabled = true)
public class SecurityConfig {
@Autowired
private AccessDeniedHandler accessDeniedHandler;
//...
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, ObservationRegistry registry) throws Exception {
System.out.println("securityFilterChain()");
// csrfCustomizer.ignoringRequestMatchers(*) 表示所有请求地址都不使用 csrf
http.csrf(csrfCustomizer -> csrfCustomizer.ignoringRequestMatchers("*"))
// 配合注解,任何请求都需要授权才能访问
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/sys/**").hasRole("admin")
.anyRequest().authenticated())
//...
// 异常处理配置
.exceptionHandling(customizer -> customizer.accessDeniedHandler(accessDeniedHandler))
// 用于启用 HTTP 基本认证,withDefaults() 表示使用默认的 HTTP 基本认证配置。
.httpBasic(withDefaults());
return http.build();
}
}上述代码中,通过 HttpSecurity 类的 exceptionHandling() 方法配置注入。
重启项目,通过浏览器访问 http://localhost:8080/sys/get 地址,效果如下:
