赞
踩
针对用户名、密码、授权类型错误的异常解决方式比较复杂,需要定制的比较多。
这部分根据自己业务需要定制,举个例子,代码如下:
public enum ResultCode {
CLIENT_AUTHENTICATION_FAILED(1001,"客户端认证失败"),
USERNAME_OR_PASSWORD_ERROR(1002,"用户名或密码错误"),
UNSUPPORTED_GRANT_TYPE(1003, "不支持的认证模式"),
NO_PERMISSION(1005,"无权限访问!"),
UNAUTHORIZED(401, "系统错误"),
INVALID_TOKEN(1004,"无效的token");
需要自定义一个异常翻译器,默认的是DefaultWebResponseExceptionTranslator,此处必须重写,其中有一个需要实现的方法,如下:
ResponseEntity<T> translate(Exception e) throws Exception;
这个方法就是根据传递过来的Exception判断不同的异常返回特定的信息,这里需要判断的异常的如下:
创建一个OAuthServerWebResponseExceptionTranslator实现WebResponseExceptionTranslator,代码如下:
public class OAuthServerWebResponseExceptionTranslator implements WebResponseExceptionTranslator{ /** * 业务处理方法,重写这个方法返回客户端信息 */ @Override public ResponseEntity<ResultMsg> translate(Exception e){ ResultMsg resultMsg = doTranslateHandler(e); return new ResponseEntity<>(resultMsg, HttpStatus.UNAUTHORIZED); } /** * 根据异常定制返回信息 * TODO 自己根据业务封装 */ private ResultMsg doTranslateHandler(Exception e) { //初始值,系统错误, ResultCode resultCode = ResultCode.UNAUTHORIZED; //判断异常,不支持的认证方式 if(e instanceof UnsupportedGrantTypeException){ resultCode = ResultCode.UNSUPPORTED_GRANT_TYPE; //用户名或密码异常 }else if(e instanceof InvalidGrantException){ resultCode = ResultCode.USERNAME_OR_PASSWORD_ERROR; } return new ResultMsg(resultCode.getCode(),resultCode.getMsg(),null); } }
需要将自定义的异常翻译器OAuthServerWebResponseExceptionTranslator在配置文件中配置,很简单,一行代码的事。
在AuthorizationServerConfig配置文件指定,代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x0dh3IeZ-1662780344834)(C:\Users\CSEN\AppData\Roaming\Typora\typora-user-images\image-20220906203102659.png)]
这部分比较复杂,想要理解还是需要些基础的,解决这个异常的方案很多,陈某只是介绍其中一种,下面详细介绍。
这部分根据自己业务需要定制,和第一步一样。
这个AuthenticationEntryPoint是不是很熟悉,前面的文章已经介绍过了,此处需要自定义来返回定制的提示信息。
创建OAuthServerAuthenticationEntryPoint,实现AuthenticationEntryPoint,重写其中的方法,代码如下:
public class OAuthServerAuthenticationEntryPoint implements AuthenticationEntryPoint {
/**
* 认证失败处理器会调用这个方法返回提示信息
* TODO 实际开发中可以自己定义,此处直接返回JSON数据:客户端认证失败错误提示
*/
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
ResponseUtils.result(response,new ResultMsg(ResultCode.CLIENT_AUTHENTICATION_FAILED.getCode(),ResultCode.CLIENT_AUTHENTICATION_FAILED.getMsg(),null));
}
}
ClientCredentialsTokenEndpointFilter这个过滤器的主要作用就是校验客户端的ID、秘钥,代码如下:
public class OAuthServerClientCredentialsTokenEndpointFilter extends ClientCredentialsTokenEndpointFilter { private final AuthorizationServerSecurityConfigurer configurer; private AuthenticationEntryPoint authenticationEntryPoint; /** * 构造方法 * @param configurer AuthorizationServerSecurityConfigurer对昂 * @param authenticationEntryPoint 自定义的AuthenticationEntryPoint */ public OAuthServerClientCredentialsTokenEndpointFilter(AuthorizationServerSecurityConfigurer configurer, AuthenticationEntryPoint authenticationEntryPoint) { System.out.println("自定义的客户端认证的过滤器的构造方法"); this.configurer = configurer; this.authenticationEntryPoint=authenticationEntryPoint; } @Override public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) { System.out.println("setAuthenticationEntryPoint"); this.authenticationEntryPoint = authenticationEntryPoint; } /** * 需要重写这个方法,返回AuthenticationManager */ @Override protected AuthenticationManager getAuthenticationManager() { System.out.println("getAuthenticationManager"); return configurer.and().getSharedObject(AuthenticationManager.class); } /** * 设置AuthenticationEntryPoint主要逻辑 */ @Override public void afterPropertiesSet() { System.out.println("设置AuthenticationEntryPoint主要逻辑"); //TODO 定制认证失败处理器,开发中可以自己修改 setAuthenticationFailureHandler((request, response, exception) -> { if (exception instanceof BadCredentialsException) { exception = new BadCredentialsException(exception.getMessage(), new BadClientCredentialsException()); } authenticationEntryPoint.commence(request, response, exception); }); //成功处理器,和父类相同,为空即可。 setAuthenticationSuccessHandler((request, response, authentication) -> { }); } }
有几个重要的部分需要讲一下,如下:
只需要将自定义的过滤器添加到AuthorizationServerSecurityConfigurer中,代码如下:
@Override public void configure(AuthorizationServerSecurityConfigurer security) { System.out.println("配置令牌访问的安全约束"); //自定义ClientCredentialsTokenEndpointFilter,用于处理客户端id,密码错误的异常 ①OAuthServerClientCredentialsTokenEndpointFilter endpointFilter = new OAuthServerClientCredentialsTokenEndpointFilter(security,authenticationEntryPoint); ① endpointFilter.afterPropertiesSet(); ①security.addTokenEndpointAuthenticationFilter(endpointFilter); security .authenticationEntryPoint(authenticationEntryPoint) //开启/oauth/token_key验证端口权限访问 .tokenKeyAccess("permitAll()") //开启/oauth/check_token验证端口认证权限访问 .checkTokenAccess("permitAll()"); ② //一定不要添加allowFormAuthenticationForClients,否则自定义的OAuthServerClientCredentialsTokenEndpointFilter不生效 // .allowFormAuthenticationForClients(); }
第①部分是添加过滤器,其中authenticationEntryPoint使用的是第2步自定义的OAuthServerAuthenticationEntryPoint
第②部分一定要注意:一定要去掉这行代码,具体原因源码解释。
OAuthServerAuthenticationEntryPoint这个过滤器继承了 AbstractAuthenticationProcessingFilter 这个抽象类,一切的逻辑都在 doFilter() 中,陈某简化了其中的关键代码如下:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { try { //调用子类的attemptAuthentication方法,获取参数并且认证 authResult = attemptAuthentication(request, response); } catch (InternalAuthenticationServiceException failed) { //一旦认证异常,则调用unsuccessfulAuthentication方法,通过failureHandler处理 unsuccessfulAuthentication(request, response, failed); return; } catch (AuthenticationException failed) { //一旦认证异常,则调用unsuccessfulAuthentication方法,通过failureHandler处理 unsuccessfulAuthentication(request, response, failed); return; } //认证成功,则调用successHandler处理 successfulAuthentication(request, response, chain, authResult); }
关键代码在 unsuccessfulAuthentication() 这个方法中,代码如下
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Authentication request failed: " + failed.toString(), failed);
this.logger.debug("Updated SecurityContextHolder to contain null Authentication");
this.logger.debug("Delegating to authentication failure handler " + this.failureHandler);
}
this.rememberMeServices.loginFail(request, response);
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
这个就要看 AuthorizationServerSecurityConfigurer#configure() 这个方法了,其中有一段代码如下:
while(var2.hasNext()) {
Filter filter = (Filter)var2.next();
http.addFilterBefore(filter, BasicAuthenticationFilter.class);
}
也就是说,我们自定义的过滤链被加到了BasicAuthenticationFilter里面
还是在 AuthorizationServerSecurityConfigurer#configure() 这个方法中,一旦设置了 allowFormAuthenticationForClients 为true,则会创建 ClientCredentialsTokenEndpointFilter,此时自定义的自然失效了。
下面针对上述两种异常分别定制异常提示信息,这个比认证服务定制简单。
@Component public class RequestAccessDeniedHandler implements ServerAccessDeniedHandler { @Override public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) { System.out.println("RequestAccessDeniedHandler"); ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.OK); response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); System.out.println("wuquan2"); String body= JSONUtil.toJsonStr(new ResultMsg(ResultCode.NO_PERMISSION.getCode(),ResultCode.NO_PERMISSION.getMsg(),null)); DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8"))); return response.writeWith(Mono.just(buffer)); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。