点击上方蓝色“ 程序猿DD ”,选择“设为星标”
回复“ 资源 ”获取独家整理的学习资料!
最近实在比较忙,很难抽出时间来继续更 [Spring Security 实战干货系列](https://felord.cn/categories/spring-security/)。今天正好项目中 Spring Security 需要对认证授权异常的处理,就分享出来吧 。
Spring Security 中的异常主要分为两大类:一类是认证异常,另一类是授权相关的异常。
AuthenticationException
是在用户认证的时候出现错误时抛出的异常。主要的子类如下图:
根据该图的信息,系统用户不存在,被锁定,凭证失效,密码错误等认证过程中出现的异常都由 AuthenticationException
处理。
AccessDeniedException
主要是在用户在访问受保护资源时被拒绝而抛出的异常。同 AuthenticationException
一样它也提供了一些具体的子类。如下图:
AccessDeniedException
的子类比较少,主要是 CSRF
相关的异常和授权服务异常。
Http 协议对认证授权的响应结果也有规定。
HTTP 401 错误 - 未授权(Unauthorized)
一般来说该错误消息表明您首先需要登录(输入有效的用户名和密码)。如果你刚刚输入这些信息,立刻就看到一个 401
错误,就意味着,无论出于何种原因您的用户名和密码其中之一或两者都无效(输入有误,用户名暂时停用,账户被锁定,凭证失效等) 。总之就是认证失败了。其实正好对应我们上面的 AuthenticationException
。
HTTP 403 错误 - 被禁止(Forbidden)
出现该错误表明您在访问受限资源时没有得到许可。服务器理解了本次请求但是拒绝执行该任务,该请求不该重发给服务器。并且服务器想让客户端知道为什么没有权限访问特定的资源,服务器应该在返回的信息中描述拒绝的理由。一般实践中我们会比较模糊的表明原因。该错误对应了我们上面的 AccessDeniedException
。
我们在 Spring Security
实战干货系列文章中的 自定义配置类入口 WebSecurityConfigurerAdapter 一文中提到 HttpSecurity
提供的 exceptionHandling()
方法用来提供异常处理。该方法构造出 ExceptionHandlingConfigurer
异常处理配置类。该配置类提供了两个实用接口:
AuthenticationEntryPoint
该类用来统一处理
AuthenticationException
异常
AccessDeniedHandler
该类用来统一处理
AccessDeniedException
异常
我们只要实现并配置这两个异常处理类即可实现对 Spring Security 认证授权相关的异常进行统一的自定义处理。
以 json
信息响应。
import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.http.MediaType; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; /** * @author dax * @since 2019/11/6 22:11 */ public class SimpleAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { //todo your business HashMap<String, String> map = new HashMap<>(2); map.put("uri", request.getRequestURI()); map.put("msg", "认证失败"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setCharacterEncoding("utf-8"); response.setContentType(MediaType.APPLICATION_JSON_VALUE); ObjectMapper objectMapper = new ObjectMapper(); String resBody = objectMapper.writeValueAsString(map); PrintWriter printWriter = response.getWriter(); printWriter.print(resBody); printWriter.flush(); printWriter.close(); } }
同样以 json
信息响应。
import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.http.MediaType; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; /** * @author dax * @since 2019/11/6 22:19 */ public class SimpleAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { //todo your business HashMap<String, String> map = new HashMap<>(2); map.put("uri", request.getRequestURI()); map.put("msg", "认证失败"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setCharacterEncoding("utf-8"); response.setContentType(MediaType.APPLICATION_JSON_VALUE); ObjectMapper objectMapper = new ObjectMapper(); String resBody = objectMapper.writeValueAsString(map); PrintWriter printWriter = response.getWriter(); printWriter.print(resBody); printWriter.flush(); printWriter.close(); } }
其实我个人建议 Http
状态码 都返回 200
而将 401 状态在 元信息 Map
中返回。因为异常状态码在浏览器端会以 error
显示。我们只要能捕捉到 401
和 403
就能认定是认证问题还是授权问题。
实现了上述两个接口后,我们只需要在 WebSecurityConfigurerAdapter
的 configure(HttpSecurity http)
方法中配置即可。相关的配置片段如下:
http.exceptionHandling().accessDeniedHandler(new SimpleAccessDeniedHandler()).authenticationEntryPoint(new SimpleAuthenticationEntryPoint())
本文通过OpenWrite的Markdown转换工具发布
关注我,回复“ 加群 ” 加入各种主题讨论群
Spring Security 实战: Spring Boot 下的自动配置
Spring Security 实战: 路径Uri中的 Ant 风格
Spring Security 实战: 自定义配置类入口
Spring Security 实战: 搞清楚 UserDetails
Spring Security 实战: 登录成功后返回 JWT Token
Spring Security 实战:实现自定义退出登录
朕已阅