spring security zuul 处理各种exception总结
异常分类
1.AccessDeniedException,zuul携带token 访问受限资源抛出 AccessDeniedException
代码块如下
@GetMapping("/r3")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String testR3() {
return "admin";
}
@GetMapping("/r4")
@PreAuthorize("hasRole('ROLE_TEST')")
public String testR4() {
return "test";
}
访问结果如下:
{
"code": 500,
"data": "",
"msg": "运行时异常:",
"errorMsg": "全局runtime异常:message:不允许访问 ClassName:org.springframework.security.access.vote.AffirmativeBased MethodName:decide LineNumber:84",
"eternal": "",
"eternal2": ""
}
解决步骤如下:
1.zuul中定义GlobalException实现HandlerExceptionResolver,在resolveException中处理AccessDeniedException友好返回json (失败)
@Component
public class GlobalException implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
if (e instanceof AccessDeniedException) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("status", 200);
Map<String, Object> map = new HashMap<>(2);
map.put("code", 403);
map.put("msg", "当前无权限");
jsonObject.put("data", map);
httpServletResponse.setContentType("application/json;charset=UTF-8");
try {
httpServletResponse.getWriter().write(JSON.toJSONString(jsonObject));
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
return null;
}
}
2.资源服务器中定义GlobalException实现HandlerExceptionResolver,在resolveException中处理AccessDeniedException友好返回json (失败)
@Component
public class GlobalException implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
if (e instanceof AccessDeniedException) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("status", 200);
Map<String, Object> map = new HashMap<>(2);
map.put("code", 403);
map.put("msg", "当前无权限");
jsonObject.put("data", map);
httpServletResponse.setContentType("application/json;charset=UTF-8");
try {
httpServletResponse.getWriter().write(JSON.toJSONString(jsonObject));
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
return null;
}
}
3.根据网上大部分文档自定义CustomAccessDeniedHandler实现AccessDeniedHandler,注入到zuul中resource里友好处理返回json (失败)
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("status", 200);
Map<String, Object> map = new HashMap<>(2);
map.put("code", 403);
map.put("msg", "朋友你没有权限访问这个呦");
jsonObject.put("data", map);
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(jsonObject));
}
}
@Autowired
private CustomAccessDeniedHandler customAccessDeniedHandler;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore).resourceId(RESOURCE_ID)
.authenticationEntryPoint(customizeAuthenticationEntryPoint)//处理token过期或者token错误
.accessDeniedHandler(customAccessDeniedHandler) //处理
.stateless(true);
}
4.自定义CustomAccessDeniedHandler实现AccessDeniedHandler,注入到资源服务中resource里友好处理返回json (失败)
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("status", 200);
Map<String, Object> map = new HashMap<>(2);
map.put("code", 403);
map.put("msg", "朋友你没有权限访问这个呦");
jsonObject.put("data", map);
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(jsonObject));
}
}
@Autowired
private CustomAccessDeniedHandler customAccessDeniedHandler;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID) //资源id
// .tokenServices(tokenService()) //验证令牌的服务
.tokenStore(tokenStore)
.accessDeniedHandler(customAccessDeniedHandler) //权限异常处理类
.stateless(true); //会话机制stateless开启
}
5.zuul定义全局GlobalExceptionHandler,使用@RestControllerAdvice捕获AccessDeniedException (失败)
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = Throwable.class)
public JSONObject exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception e) {
if (e instanceof AccessDeniedException) {
// 业务异常信息组装
JSONObject jsonObject = new JSONObject();
jsonObject.put("status", 200);
Map<String, Object> map = new HashMap<>(2);
map.put("code", 403);
map.put("msg", "朋友你没有权限访问这个呦");
jsonObject.put("data", map);
return jsonObject;
} else {
return null;
}
}
}
6.在资源服务器端定义全局GlobalExceptionHandler,使用@RestControllerAdvice捕获AccessDeniedException (失败)
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = Throwable.class)
public JSONObject exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception e) {
if (e instanceof AccessDeniedException) {
// 业务异常信息组装
JSONObject jsonObject = new JSONObject();
jsonObject.put("status", 200);
Map<String, Object> map = new HashMap<>(2);
map.put("code", 403);
map.put("msg", "朋友你没有权限访问这个呦");
jsonObject.put("data", map);
return jsonObject;
} else {
return null;
}
}
}
7.在资源服务器中配置增强异常处理ExceptionHandler,使用@RestControllerAdvice捕获AccessDeniedException (成功)
第6中方法无法抛出AccessDeniedException,配置RunTimeExceptionHandler
/**
* runtimeException
*/
@ExceptionHandler(RuntimeException.class)
public JSONObject notFount(RuntimeException e) {
if (e instanceof AccessDeniedException || e instanceof OAuth2Exception) {
throw e;
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("status", 200);
Map<String, Object> map = new HashMap<>(2);
map.put("code", 500);
map.put("msg", e.getMessage());
jsonObject.put("data", map);
return jsonObject;
}
/**
* AccessDeniedException
*
* @param e
* @return
*/
@ExceptionHandler(AccessDeniedException.class)
public JSONObject accessDeniedExceptionHandler(AccessDeniedException e) {
log.error("操作异常:", e);
JSONObject jsonObject = new JSONObject();
jsonObject.put("status", 200);
Map<String, Object> map = new HashMap<>(2);
map.put("code", 403);
map.put("msg", "当前无权限");
jsonObject.put("data", map);
return jsonObject;
}
8.测试结果如下:
访问无权限结果:
{
"data": {
"msg": "当前无权限",
"code": 403
},
"status": 200
}
2.处理账号密码错误时获取token返回的异常信息 InvalidGrantException (成功)
由于集成zuul,我把处理的一部分步骤放在了zuul资源上,此处感谢https://www.jianshu.com/p/7fc154cc9120提供的部分解决思路
解决步骤:
1.认证
1.1 重写客户端认证过滤器,不再使用默认的OAuth2AuthenticationEntryPoint处理异常
public class CustomClientCredentialsTokenEndpointFilter extends ClientCredentialsTokenEndpointFilter {
private final AuthorizationServerSecurityConfigurer configurer;
private AuthenticationEntryPoint authenticationEntryPoint;
public CustomClientCredentialsTokenEndpointFilter(AuthorizationServerSecurityConfigurer configurer) {
this.configurer = configurer;
}
@Override
public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
super.setAuthenticationEntryPoint(null);
this.authenticationEntryPoint = authenticationEntryPoint;
}
@Override
protected AuthenticationManager getAuthenticationManager() {
return configurer.and().getSharedObject(AuthenticationManager.class);
}
@Override
public void afterPropertiesSet() {
setAuthenticationFailureHandler((request, response, e) -> authenticationEntryPoint.commence(request, response, e));
setAuthenticationSuccessHandler((request, response, authentication) -> {
});
}
}
1.2 自定义异常翻译类
public class CustomWebResponseExceptionTranslator implements WebResponseExceptionTranslator {
@Override
public ResponseEntity<JsonResult<String>> translate(Exception e) throws Exception {
log.error("认证服务器异常", e);
JsonResult<String> response = resolveException(e);
return new ResponseEntity<>(response, HttpStatus.valueOf(response.getErrorCode()));
}
/**
* 构建返回异常
*
* @param e
* @return
*/
private JsonResult<String> resolveException(Exception e) {
// 初始值 500
ResultCode returnCode = ResultCode.COMMON_FAIL;
int httpStatus = HttpStatus.UNAUTHORIZED.value();
//不支持的认证方式
if (e instanceof UnsupportedGrantTypeException) {
returnCode = ResultCode.UNSUPPORTED_GRANT_TYPE;
//用户名或密码异常
} else if (e instanceof InvalidGrantException) {
returnCode = ResultCode.USERNAME_OR_PASSWORD_ERROR;
}
JsonResult<String> failResponse = ResultTool.fail(returnCode);
failResponse.setErrorCode(httpStatus);
return failResponse;
}
}
1.3 AuthorizationServer配置类配置异常逻辑,自定义返回结果
@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return ((request, response, e) -> {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
JsonResult<String> jsonResult = ResultTool.fail(ResultCode.CLIENT_AUTHENTICATION_FAILED);
response.getWriter().write(JSON.toJSONString(jsonResult));
});
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security){
//将我们自定义的认证过滤器加进来
CustomClientCredentialsTokenEndpointFilter endpointFilter = new CustomClientCredentialsTokenEndpointFilter(security);
endpointFilter.afterPropertiesSet();
endpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint());
security.addTokenEndpointAuthenticationFilter(endpointFilter);
//此时需要注释掉allowFormAuthenticationForClients,否则自定义的过滤器不生效
security.authenticationEntryPoint(authenticationEntryPoint())
.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()")
//.allowFormAuthenticationForClients();
}
1.4 zuul上的处理
自定义CustomizeAuthenticationEntryPoint实现AuthenticationEntryPoint:
@Configuration
public class CustomizeAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final static Logger logger = LoggerFactory.getLogger(CustomizeAuthenticationEntryPoint.class);
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
Throwable cause = e.getCause();
response.setStatus(HttpStatus.OK.value());
response.setContentType("application/json;charset=UTF-8");
JSONObject jsonObject = new JSONObject();
jsonObject.put("status", 200);
Map<String, Object> map = new HashMap<>(2);
try {
if (cause instanceof AccessDeniedException) {
//资源权限不足
map.put("code", 403);
map.put("msg", "朋友你没有权限访问这个呦");
jsonObject.put("data", map);
response.getWriter().write(JSON.toJSONString(jsonObject));
} else if (cause == null || cause instanceof InvalidTokenException) {
//未带token或者token无效
//cause==null 一般可能是未带token
map.put("code", 2001);
map.put("msg", "客官你还没有登录或者登录已过期,请重新登录");
jsonObject.put("data", map);
response.getWriter().write(JSON.toJSONString(jsonObject));
}
} catch (IOException ie) {
logger.error("其他异常error", ie);
throw new RuntimeException(ie);
}
}
}
此处忽略其中的AccessDeniedException处理,直接去掉即可(卵用没有)
1.5 zuul的ResourceServerConfig中配置自定义point
@Configuration
@EnableResourceServer
public class YsCarResouceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Autowired
private CustomizeAuthenticationEntryPoint customizeAuthenticationEntryPoint;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore).resourceId(RESOURCE_ID)
.authenticationEntryPoint(customizeAuthenticationEntryPoint)//处理token过期或者token错误
.stateless(true);
}
1.6 到此基本完成常见exception的处理逻辑了,测试如下
{
"success": false,
"errorCode": 401,
"errorMsg": "用户名或密码错误",
"data": null
}
1.7 解决完成之后发现AccessDeniedException我一直没解决的问题,原来在很隐蔽的地方有一个全局的GlobalExceptionHandler,我觉得我瞎了,不过能解决就好,也更好的了解了一下SpringSecurity的ExceptionTranslationFilter的判断流程,找到了处理AccessDeniedException的类为AccessDeniedHandlerImpl,最后还是要说一下@PreAuthorize(“hasRole(‘ROLE_xxx’)”)的抛出的AccessDeniedException不会被AccessDeniedHandler捕获,而是会被全局异常捕获,至于原因,还是没有找到,如有大神了解的话,可以评论一下,让我们也都学习一下,到这,此博客基本结束了,本人只是热衷于搬砖的一个憨憨,坐标北京海淀,欢迎一起交流