SpringSecurity Exception 处理

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捕获,而是会被全局异常捕获,至于原因,还是没有找到,如有大神了解的话,可以评论一下,让我们也都学习一下,到这,此博客基本结束了,本人只是热衷于搬砖的一个憨憨,坐标北京海淀,欢迎一起交流

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值