当前位置: 首页 > 科技观察

SpringCloudAlibaba实战中Oauth2认证服务器自定义异常

时间:2023-03-20 22:44:28 科技观察

前言今天的内容主要是解决一个粉丝提出的问题:使用SpringSecurityOAuth2时如何自定义认证服务器返回异常。那么首先我们以Password模式为例,看看在认证过程中会出现哪些异常情况。授权模式错误这里我们故意将授权模式密码修改为password1,认证服务器返回异常如下图{"error":"unsupported_grant_type","error_description":"Unsupportedgranttype:password1"}密码错误在认证或密码时会出现如下异常错误:"Badclientcredentials"}上面的返回结果很不友好,前端代码很难判断是什么错误,所以我们需要对返回的错误进行统一的异常处理,让它返回统一的异常格式。问题分析如果只关注解决方案,可以直接跳转到解决方案模块!OAuth2Exception异常处理在Oauth2认证服务器中,认证逻辑最终会调用TokenEndpoint#postAccessToken()方法,一旦认证出现OAuth2Exception异常,就会在handledException()中捕获异常。下图为用户密码异常时的debug截图:认证服务器捕获到OAuth2Exception异常后,会调用WebResponseExceptionTranslator#translate()方法进行异常翻译。默认的翻译处理实现类是DefaultWebResponseExceptionTranslator。处理完成后会调用handleOAuth2Exception()方法将处理后的异常返回给前端。这是我们之前看到的异常效果。处理方式熟悉Oauth2套路的同学应该知道如何处理此类异常,即“自定义一个异常翻译类,返回我们需要的自定义格式,然后注入到认证服务器中”。但是这个处理逻辑只能解决OAuth2Exception异常,也就是前言中的“授权模式异常”和“账号密码异常”,并不能解决我们客户端的异常。客户端异常处理客户端认证的异常发生在过滤器ClientCredentialsTokenEndpointFilter上,它有一个post-add失败处理方法,最后将异常传递给OAuth2AuthenticationEntryPoint,即所谓的认证入口。执行顺序如下:然后跳转到父类的AbstractOAuth2SecurityExceptionHandler#doHandle()进行处理:最后DefaultOAuth2ExceptionRenderer#handleHttpEntityResponse()方法将异常输出给客户端的处理方法。通过上面的分析,我们知道客户端的认证失败异常是将过滤器ClientCredentialsTokenEndpointFilter转给OAuth2AuthenticationEntryPoint得到响应结果。这种情况下,我们可以重写ClientCredentialsTokenEndpointFilter,将原来的OAuth2AuthenticationEntryPoint替换成自定义的AuthenticationEntryPoint,在自定义的AuthenticationEntryPoint中处理我们想要的异常数据。解决方案为了解决以上异常,我们首先需要针对不同的异常编写错误码:ReturnCode.javaCLIENT_AUTHENTICATION_FAILED(1001,"客户端认证失败"),USERNAME_OR_PASSWORD_ERROR(1002,"用户名或密码错误"),UNSUPPORTED_GRANT_TYPE(1003,"不支持的身份验证模式");OAuth2Exception异常@Slf4jpublicclassCustomWebResponseExceptionTranslatorimplementsWebResponseExceptionTranslator{@OverridepublicResponseEntity>translate(Exceptione)throwsException{log.error("Authenticationserverexception",e);ResultDataresponseEx=resolve(e);returnnewResponseEntity<>(response,HttpStatus.valueOf(response.getHttpStatus()));}/***构建返回异常*@parameexception*@return*/privateResultDataresolveException(Exceptione){//初始值500ReturnCodereturnCode=ReturnCode.RC500;inthttpStatus=HttpStatus。UNAUTHORIZED.value();//认证方式不支持E_OR_PASSWORD_ERROR;}ResultDatafailResponse=R??esultData.fail(returnCode.getCode(),returnCode.getMessage());failResponse.setHttpStatus(httpStatus);returnfailResponse;}}然后在认证服务器配置类@Overridepublic中注入自定义异常转换类voidconfigure(AuthorizationServerEndpointsConfigurerendpoints)throwsException{//如果需要使用refresh_token方式,需要注入userDetailServiceendpoints.authenticationManager(this.authenticationManager).userDetailsS??ervice(userDetailService)//注入tokenGranter.tokenGranter(tokenGranter/injectcustomservice);/使用自定义的tokenService,需要把tokenServce中的配置移到这里//.tokenServices(tokenServices());//自定义异常转换类endpoints.exceptionTranslator(newCustomWebResponseExceptionTranslator());}客户端异常重写客户端Authentication过滤器,不要使用默认的OAuth2AuthenticationEntryPoint处理异常publicclassCustomClientCredentialsTokenEndpointFilterextendsClientCredentialsTokenEndpointFilter{privatefinalAuthorizationServerSecurityConfigurerconfigurer;privateAuthenticationEntryPointauthenticationEntryPoint;publicCustomClientCredentialtialsTokenEndpointFilter(AuthorizationServerSecurityConfigurerconfigurer){this.configurer=configurer;}@OverridepublicvoidsetAuthenticationEntryPoint(AuthenticationEntryPointauthenticationEntryPoint){super.setAuthenticationEntryPoint(null);this.authenticationEntryPoint=authenticationEntryPoint;}@OverrideprotectedAuthenticationManagergetAuthenticationManager(){returnconfigurer.and().getShareManager();}@OverridepublicvoidafterPropertiesSet(){setAuthenticationFailureHandler((request,response,e)->authenticationEntryPoint.commence(request,response,e));setAuthenticationSuccessHandler((request,response,authentication)->{});}}在认证服务器注册异常处理总编辑,自定义异常返回结果(代码位于AuthorizationServerConfig)@BeanpublicAuthenticationEntryPointauthenticationEntryPoint(){return(request,response,e)->{response.setStatus(HttpStatus.UNAUTHORIZED.value());ResultDataresultData=ResultData.fail(ReturnCode.CLIENT_AUTHENTICATION_Code(FAILED),ReturnCode.CLIENT_AUTHENTICATION_FAILED.getMessage());WebUtils.writeJson(response,resultData);};}修改认证服务器配置,注入自定义过滤器@Overridepublicvoidconfigure(AuthorizationServerSecurityConfigurersecurity)throwsException{CustomClientCredentialsTokenEndpointFilterendpointFilter=newCustomClientCredentialsTokenEndpointFilter(security);endpointFilter.afterPropertiesSet();endpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint());security.addTokenEndpointAuthenticationFilter(endpointFilter);security.authenticationEntryPoint(authenticationEntryPoint())/*.allowFormAuthenticationForClients()*///如果使用表单认证,需要添加.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticateed()");}这个时候需要把allowFormAuthenticationForClients()配置去掉,不然自定义的filter不会生效,至于为什么不生效,可以看源码测试看看是否授权方式不对账号密码不对客户端不对有救了!