使用 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 地址,效果如下: