首页 > 其他分享 >SpringSecurity OAuth2异常处理OAuth2Exception

SpringSecurity OAuth2异常处理OAuth2Exception

时间:2022-12-01 17:36:06浏览次数:38  
标签:OAuth2 OAuth2Exception class SpringSecurity Override token msg 异常 public

前言

在我们使用SpringSecurity OAuth2做认证授权时,默认返回都是SpringSecurity OAuth2提供好的,返回不是很友好,本章就是针对这些异常做统一返回处理,主要解决返回格式问题,和返回提示信息重写!我们先来看看SpringSecurity OAuth2默认返回格式,如下!
SpringSecurity OAuth2异常处理OAuth2Exception_ide
本文将默认返回改造为如下格式

{
	code:xxx,
	msg:xxxx,
	data:xxx
}

SpringSecurity OAuth2异常处理呢,又分为两部分,一部分为授权服授权时异常,通常包含用户账号异常、授权类型异常、端点信息异常等。另一部分为资源服异常,通常为token异常、权限不够等!

认证服异常处理

知识铺垫
我们在做认证获取Token的时候,是调用SpringSecurity OAuth2提供的/oauth/token,我们进入到对应的Endpoint,这个Endpoint我这里不多介绍,想了解的看看我的往期文章即可!进入源码!

SpringSecurity OAuth2异常处理OAuth2Exception_restful_02
SpringSecurity OAuth2异常处理OAuth2Exception_spring_03
这个接口就是获取Token的,那么异常就再个方法抛出,对应的异常捕获SpringSecurity OAuth2 默认是被DefaultWebResponseExceptionTranslator处理,顶层接口为WebResponseExceptionTranslator,那么我们同样继承WebResponseExceptionTranslator重写对应方法即可!

Auth2ResponseExceptionTranslator
这个是我们自己定义的异常翻译器,代码如下


/**
* @description: SpringSecurity OAuth2 异常翻译器,统一捕获授权时异常信息处理
* @author TAO
* @date 2021/10/1 16:14
*/
@Slf4j
@Component
public class Auth2ResponseExceptionTranslator implements WebResponseExceptionTranslator {

    private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();

    @Override
    public ResponseEntity translate(Exception e) throws Exception {

        Throwable[] causeChain = throwableAnalyzer.determineCauseChain(e);
        Exception ase = null;// 异常栈获取 OAuth2Exception 异常

        ase=(UnsupportedGrantTypeException) throwableAnalyzer
                .getFirstThrowableOfType(UnsupportedGrantTypeException.class, causeChain);
        if (ase != null) {
            return handleOAuth2Exception(new GrantTypeException(e.getMessage(), e));
        }

        ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class,
                causeChain);
        if (ase != null) {
            return handleOAuth2Exception(new ForbiddenException(ase.getMessage(), ase));
        }

        ase = (HttpRequestMethodNotSupportedException) throwableAnalyzer
                .getFirstThrowableOfType(HttpRequestMethodNotSupportedException.class, causeChain);
        if (ase != null) {
            return handleOAuth2Exception(new MethodNotAllowedException(ase.getMessage(), ase));
        }

        ase = (OAuth2Exception) throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain);

        if (ase != null) {
            return handleOAuth2Exception((OAuth2Exception) ase);
        }

        // 不包含上述异常则服务器内部错误
        return handleOAuth2Exception(new ServerErrorException(HttpStatus.OK.getReasonPhrase(), e));

    }

    private ResponseEntity<OAuth2Exception> handleOAuth2Exception(OAuth2Exception e) {

        int status = e.getHttpErrorCode();
        HttpHeaders headers = new HttpHeaders();
        headers.set("Cache-Control", "no-store");
        headers.set("Pragma", "no-cache");
        if (status == HttpStatus.UNAUTHORIZED.value() || (e instanceof InsufficientScopeException)) {
            headers.set("WWW-Authenticate", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));
        }

        return new ResponseEntity<>(new ExtendOAuth2Exception(e.getOAuth2ErrorCode(),e.getMessage()), headers,
                HttpStatus.valueOf(status));
    }


}

这里逻辑其实非常简单,translate方法就是根据不同的异常栈返回不同的异常对象!这个方法需要提前准备一些异常类型、异常序列化器,

准备异常

ExtendOAuth2Exception

/**
* @description: 扩展OAuth2Exception
* @author TAO
* @date 2021/9/29 22:14
*/
@JsonSerialize(using = ExtendOAuth2ExceptionSerializer.class)
public class ExtendOAuth2Exception extends OAuth2Exception {

    @Getter
    private String dataMsg;

    public ExtendOAuth2Exception(String msg) {
        super(msg);
    }

    public ExtendOAuth2Exception(String msg, Throwable t) {
        super(msg, t);
    }

    public ExtendOAuth2Exception(String msg, String dataMsg) {
        super(msg);
        this.dataMsg = dataMsg;

    }

}

ForbiddenException

/**
* @description: 拒绝访问Exception
* @author TAO
* @date 2021/9/29 22:10
*/
public class ForbiddenException extends ExtendOAuth2Exception{

    public ForbiddenException(String msg, Throwable t) {
        super(msg, t);
    }

    @Override
    public String getOAuth2ErrorCode() {
        return "access_denied---拒绝访问";
    }

    @Override
    public int getHttpErrorCode() {
        return HttpStatus.FORBIDDEN.value();
    }

}

GrantTypeException

/**
* @description: 授权模式Exception
* @author TAO
* @date 2021/10/1 16:21
*/
public class GrantTypeException extends ExtendOAuth2Exception{

    public GrantTypeException(String msg, Throwable t) {
        super(msg, t);
    }

    @Override
    public String getOAuth2ErrorCode() {
        return "不支持当前授权类型!!!";
    }

    @Override
    public int getHttpErrorCode() {
        return 400;
    }

}

MethodNotAllowedException

/**
* @description: 方法不允许访问Exception
* @author TAO
* @date 2021/9/29 22:18
*/
public class MethodNotAllowedException extends ExtendOAuth2Exception{

    public MethodNotAllowedException(String msg, Throwable t) {
        super(msg, t);
    }

    @Override
    public String getOAuth2ErrorCode() {
        return "method_not_allowed---方法不允许访问";
    }

    @Override
    public int getHttpErrorCode() {
        return HttpStatus.METHOD_NOT_ALLOWED.value();
    }

}

ServerErrorException

/**
* @description: 服务器内部Exception
* @author TAO
* @date 2021/9/29 22:20
*/
public class ServerErrorException extends ExtendOAuth2Exception{

    public ServerErrorException(String msg, Throwable t) {
        super(msg, t);
    }

    @Override
    public String getOAuth2ErrorCode() {
        return "server_error---服务器内部异常";
    }

    @Override
    public int getHttpErrorCode() {
        return HttpStatus.INTERNAL_SERVER_ERROR.value();
    }

}

UnauthorizedException

/**
* @description: 未授权Exception
* @author TAO
* @date 2021/9/29 22:21
*/
public class UnauthorizedException extends ExtendOAuth2Exception {

    public UnauthorizedException(String msg, Throwable t) {
        super(msg, t);
    }

    @Override
    public String getOAuth2ErrorCode() {
        return "unauthorized---未授权";
    }

    @Override
    public int getHttpErrorCode() {
        return HttpStatus.UNAUTHORIZED.value();
    }

}

异常序列化器

/**
* @description: ExtendOAuth2Exception返回格式序列化
* @author TAO
* @date 2021/9/29 22:42
*/
public class ExtendOAuth2ExceptionSerializer extends StdSerializer<ExtendOAuth2Exception> {

    public ExtendOAuth2ExceptionSerializer() {
        super(ExtendOAuth2Exception.class);
    }

    @Override
    public void serialize(ExtendOAuth2Exception e, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
        gen.writeStartObject();
        gen.writeObjectField("code", String.valueOf(e.getHttpErrorCode()));
        gen.writeStringField("msg", e.getMessage());
        gen.writeStringField("data", e.getDataMsg());
        gen.writeEndObject();

    }
}

这玩意就是将我们的异常信息从新按照我们的格式返回!

配置异常翻译器
前面写了一大堆,当然只有配置给SpringSecurity OAuth2才能派上用场!找到授权服配置类

@Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {//配置令牌的访问端点和令牌服务
        endpoints
                .authorizationCodeServices(authorizationCodeServices)
                .authenticationManager(authenticationManager)//认证管理器
                .exceptionTranslator(new Auth2ResponseExceptionTranslator()) // 设置自定义的异常解析器
                .tokenServices(tokenService)
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
        ;
    }

.exceptionTranslator(new Auth2ResponseExceptionTranslator()) // 设置自定义的异常解析器
启动项目测试
SpringSecurity OAuth2异常处理OAuth2Exception_java_04
这是我故意将授权类型写错,返回的异常信息,既有我们的自定义异常,格式有满足我们的要求!我们在将账号密码写错试试
SpringSecurity OAuth2异常处理OAuth2Exception_ide_05
这里返回则是英文的,改不改都无所谓了,当然咯我们也可以修改,例如我们在校验账号密码的时候,代码如下!
SpringSecurity OAuth2异常处理OAuth2Exception_restful_06

SpringSecurity OAuth2异常处理OAuth2Exception_ide_07

那么到这里基本上就差不多了,如果在细的话就要根据自己项目进行定制一些异常,大致上流程就是这些。下面进入资源服异常处理!

资源服异常

简介说明
这个就比较简单了,文章开头有基本介绍资源服异常场景,这里在重复说一下,主要场景也就三个,未携带token,权限不足,token解析失败(过期或者无效token),通常我们处理两个场景就差不多了,上面三个场景我们可以分为token异常(未携带token,oken解析失败(过期或者无效token)),下面我们开始撸代码!

token异常–未携带token,oken解析失败(过期或者无效token)

/**
 * @author TAO
 * @description: 解决匿名用户访问无权限资源时的异常处理器
 * 实际上也就是用户没有认证过,请求头中没有携带了Token,或者是resource_ids范围不够
 * @date 2021/4/8 22:43
 */
@Slf4j
@Component("customAuthenticationEntryPointHandler")
public class CustomAuthenticationEntryPointHandler extends OAuth2AuthenticationEntryPoint {

    @Override
    @SneakyThrows
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {
        response.setCharacterEncoding(CommonConstants.UTF8);
        response.setContentType(CommonConstants.CONTENT_TYPE);
        R<String> result = new R<>();
        result.setCode(HttpStatus.HTTP_UNAUTHORIZED);
        log.info("匿名用户访问无权限资源时的异常");
        if (authException != null) {
            log.info("errorMsg===>"+authException.getMessage());
            result.setMsg("匿名用户访问无权限资源时的异常");
        }
        Throwable cause = authException.getCause();
        if (cause instanceof OAuth2AccessDeniedException) {
            log.info("resource_ids范围不够");
        }  else if (cause instanceof InvalidTokenException) {
            log.info("Token解析失败");
        }else if (authException instanceof InsufficientAuthenticationException) {
            log.info("未携带token");
        }

        response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);
        PrintWriter printWriter = response.getWriter();
        printWriter.append(new ObjectMapper().writeValueAsString(result));
    }

}

权限不足

/**
 * @author TAO
 * @description: 解决认证过的用户访问无权限资源时的异常处理器,实际上也就是用户认证过了,请求头中携带了Token,只是权限不够
 * @date 2021/4/8 22:42
 */
@Slf4j
@Component("customAccessDeniedHandler")
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        log.info("认证过的用户访问无权限资源时的异常");
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        response.setStatus(HttpStatus.FORBIDDEN.value());
        R<String> result = new R<>();
        result.setCode(cn.hutool.http.HttpStatus.HTTP_FORBIDDEN);//权限不足403
        result.setMsg("认证过的用户访问无权限资源时的异常");
        response.getWriter().write(new ObjectMapper().writeValueAsString(result));
    }

}

资源服配置处理器
这里也和授权服那样配置翻译器一样,在资源服核心配置中,代码如下!

@Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(RESOURCE_ID)//资源 id
                .authenticationEntryPoint(customAuthenticationEntryPointHandler)//匿名用户访问无权限资源时的异常处理器
                .accessDeniedHandler(customAccessDeniedHandler)//认证过的用户访问无权限资源时的异常处理器
                .stateless(true);
    }

重启服务器测试
不携带token捕获
SpringSecurity OAuth2异常处理OAuth2Exception_java_08
错误token
SpringSecurity OAuth2异常处理OAuth2Exception_analyzer_09
错误resource_ids范围
SpringSecurity OAuth2异常处理OAuth2Exception_spring_10
这个范围是指授权服颁发的令牌中携带的资源服id不符合!如下
SpringSecurity OAuth2异常处理OAuth2Exception_spring_11
此时我们的资源服为res2,所以会被异常捕获!

权限不足
SpringSecurity OAuth2异常处理OAuth2Exception_ide_12
那么至此怎个异常捕获就告一段落了!

写在最后

在写这篇文章的资源服异常处理时看了一下其他的资料,有的是直接将未携带token和token解析失败分开处理的,我这里是使用的如下代码

		}  else if (cause instanceof InvalidTokenException) {
            log.info("Token解析失败");
        }else if (authException instanceof InsufficientAuthenticationException) {
            log.info("未携带token");
        }

InvalidTokenException和InsufficientAuthenticationException,这可能和个人编码风格有关吧,我是属于那种不想把原有流程打乱的,也不喜欢写一些特殊处理代码,看过一篇帖子是直接通过加过滤器检验请求头中有没有携带token,来抛出这个异常的,个人觉得不是很好,这样莫名会让请求都经过一个过滤器,这样多浪费呀!代码如下!

定义过滤器

@Component
public class MyTokenFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String uri=request.getRequestURI();
        if(uri.equals("/oauth/token")||uri.equals("/oauth/login")){
            filterChain.doFilter(request,response);
        }else{
            boolean access_token=false;
            boolean authorization=false;
            if(request.getParameter("access_token")==null){
                access_token=true;
            }
            if(request.getHeader("Authorization")==null){
                authorization=true;
            }else{
                if(!request.getHeader("Authorization").startsWith("Bearer")){
                    authorization=true;
                }
            }
            if(access_token&&authorization){
                response.setContentType("application/json;charset=UTF-8");
                response.getWriter().write(JSONObject.toJSONString(new ResponseResult(401,"未获得凭证!")));
            }else{
                filterChain.doFilter(request,response);
            }
        }
    }
}

在WebSecurity核心配置中配置当前过滤器

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/oauth/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .httpBasic()
                .and()
                .csrf().disable();
        http.addFilterBefore(mytokenfilter, BasicAuthenticationFilter.class);
    }

这个方案我没试,感兴趣的可以看看!最后说一点,为什么这个方案我不愿意试,是因为这里资源服认证时还加入的SpringSecurity的一套东西,在配置之过滤器这里就能体现出,因为是配置在WebSecurity的配置中,这个看过我的往期文章的就知道为什么我这里不试试,这个个人感觉就有问题,一套资源同时需要被两套东西控制容易出问题,详细请看往期文章!SpringSecurityOAuth2登录后无法跳转获取授权码地址,直接跳转根路径原因详解,这就是同时配置了SpringSecurity和资源服接管产生的问题

标签:OAuth2,OAuth2Exception,class,SpringSecurity,Override,token,msg,异常,public
From: https://blog.51cto.com/u_15899048/5903404

相关文章

  • SpringSecurity OAuth2 关于 UserAuthenticationConverter
    前言之前写了一大堆关于SpringSecurityOAuth2的相关文章,本以为可以告一段落了,但是有了解到一个新东西,之前没注意到的,就是UserAuthenticationConverter,本章就来看看这是......
  • SpringSecurityOAuth2授权流程源码分析(自定义验证码模式)
    前言周末闲来无事,谢谢自己的项目,然后想把老的授权模式改造一下,老的是基于SpringSecurity的实现,想升级为SpringSecurityOAuth2模式,于是看了下之前搭建的SpringSecurityO......
  • SpringSecurity之授权
    回顾之前:Jwt解决的是认证的问题(我是谁),但是在SpringSecurity中最受欢迎的是授权(我能做哪些事情?)1.AccessDecisionManager2.安全表达式,越广泛适用的规则需......
  • SpringSecurity多表验证
    在开始之前我想感叹一句,技术久了不回头看看真的会忘记的,这次公司让我重新开发一个程序,项目架构为单体多模块开发,其中有个需求就是需要不同用户表进行登录,且不同表的用户名......
  • SpringSecurity从入门到精通
    0.简介SpringSecurity和Shiro比较。中大型的项目都是使用SpringSecurity做安全框架,小项目使用Shiro比较多,因为它比SpringSecurity上手更加简单认证与授权:认证:验证......
  • Spring Cloud 最新版发布,Spring Security + OAuth2 终于安排上了!
    大家好,我是栈长。今天给大家通报一则框架更新消息,时隔两个月,SpringCloud2021.0.5最新版发布了,来看下最新的SpringCloud版本情况:SpringCloud无疑是现在Java微服务......
  • 5.OAuth2详解
     理论OAuth是一个关于授权(authorization)的开放网络标准,用来授权第三方应用获取用户数据,是目前最流行的授权机制,它当前的版本是2.0。应用场景假如你正在“网站A”上冲......
  • SpringSecurity之JwtFilter-实现独立认证过滤器
    前言:之前做的项目都是框架封装好的认证过程,空余时间复习一下。1.访问令牌2.刷新令牌3.令牌如何存放?  3.1:store(放内存中),刷新浏览器会有重新登录的问题。  3.2:......
  • SpringSecurity-从入门到精通-三更草堂
    SpringSecurity从入门到精通课程介绍0.简介​ SpringSecurity是Spring家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比......
  • SpringSecurity登录时报错栈溢出,StackOverflow,SpringSecurity多端登录实现方案
    最近在用springsecurity做一套医疗项目,要求一套后端对应两套前端界面,用户(患者)和医生。先写的用户登录界面,没有问题,再用同样方法写医生登录的时候报错栈溢出stackoverfl......