热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

我扒了半天源码,终于找到了Oauth2自定义处理结果的最佳方案!

本文将详细介绍Oauth2中自定义处理结果的方案,希望对大家有所帮助!解决什么问题自定义Oauth2处理结果,主要是为了统一接口返回信息的格式,从下面几个方面着手。自定义Oauth

本文将详细介绍Oauth2中自定义处理结果的方案,希望对大家有所帮助!



解决什么问题


自定义Oauth2处理结果,主要是为了统一接口返回信息的格式,从下面几个方面着手。




  • 自定义Oauth2登录认证成功和失败的返回结果;

  • JWT令牌过期或者签名不正确,网关认证失败的返回结果;

  • 携带过期或者签名不正确的JWT令牌访问白名单接口,网关直接认证失败。


自定义登录认证结果


认证成功返回结果



  • 我们先来看看默认的返回结果,访问Oauth2登录认证接口:http://localhost:9201/auth/oauth/token



  • 我们之前使用的都是统一的通用返回结果CommonResult,Oauth2的这个结果显然不符合,需要统一下,通用返回结果格式如下;

/*** 通用返回对象* Created by macro on 2019/4/19.*/
public class CommonResult {private long code;private String message;private T data;
}


  • 其实我们只要找到一个关键类就可以自定义Oauth2的登录认证接口了,它就是org.springframework.security.oauth2.provider.endpoint.TokenEndpoint,其中定义了我们非常熟悉的登录认证接口,我们只要自己重写登录认证接口,直接调用默认的实现逻辑,然后把默认返回的结果处理下即可,下面是默认的实现逻辑;

@FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint {@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)public ResponseEntity postAccessToken(Principal principal, @RequestParamMap parameters) throws HttpRequestMethodNotSupportedException {if (!(principal instanceof Authentication)) {throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");}String clientId = getClientId(principal);ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);if (clientId != null && !clientId.equals("")) {// Only validate the client details if a client authenticated during this// request.if (!clientId.equals(tokenRequest.getClientId())) {// double check to make sure that the client ID in the token request is the same as that in the// authenticated clientthrow new InvalidClientException("Given client ID does not match authenticated client");}}if (authenticatedClient != null) {oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);}if (!StringUtils.hasText(tokenRequest.getGrantType())) {throw new InvalidRequestException("Missing grant type");}if (tokenRequest.getGrantType().equals("implicit")) {throw new InvalidGrantException("Implicit grant type not supported from token endpoint");}if (isAuthCodeRequest(parameters)) {// The scope was requested or determined during the authorization stepif (!tokenRequest.getScope().isEmpty()) {logger.debug("Clearing scope of incoming token request");tokenRequest.setScope(Collections. emptySet());}}if (isRefreshTokenRequest(parameters)) {// A refresh token has its own default scopes, so we should ignore any added by the factory here.tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));}OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);if (token == null) {throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());}return getResponse(token);}
}


  • 我们将需要的JWT信息封装成对象,然后放入到我们的通用返回结果的data属性中去;

/*** Oauth2获取Token返回信息封装* Created by macro on 2020/7/17.*/
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
public class Oauth2TokenDto {/*** 访问令牌*/private String token;/*** 刷新令牌*/private String refreshToken;/*** 访问令牌头前缀*/private String tokenHead;/*** 有效时间(秒)*/private int expiresIn;
}


  • 创建一个AuthController,自定义实现Oauth2默认的登录认证接口;

/*** 自定义Oauth2获取令牌接口* Created by macro on 2020/7/17.*/
@RestController
@RequestMapping("/oauth")
public class AuthController {@Autowiredprivate TokenEndpoint tokenEndpoint;/*** Oauth2登录认证*/@RequestMapping(value = "/token", method = RequestMethod.POST)public CommonResult postAccessToken(Principal principal, @RequestParam Map parameters) throws HttpRequestMethodNotSupportedException {OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();Oauth2TokenDto oauth2TokenDto = Oauth2TokenDto.builder().token(oAuth2AccessToken.getValue()).refreshToken(oAuth2AccessToken.getRefreshToken().getValue()).expiresIn(oAuth2AccessToken.getExpiresIn()).tokenHead("Bearer ").build();return CommonResult.success(oauth2TokenDto);}
}


  • 再次调用登录认证接口,我们可以发现返回结果已经变成了符合我们通用返回结果的格式了!


认证失败返回结果



  • 认证成功的结果统一了,认证失败的结果我们也得统一下吧,先来看下原来认证失败的结果;



  • 我们仔细查看下登录认证的默认实现可以发现,很多认证失败的操作都会直接抛出OAuth2Exception异常,对于在Controller中抛出的异常,我们可以使用@ControllerAdvice注解来进行全局处理;

/*** 全局处理Oauth2抛出的异常* Created by macro on 2020/7/17.*/
@ControllerAdvice
public class Oauth2ExceptionHandler {@ResponseBody@ExceptionHandler(value = OAuth2Exception.class)public CommonResult handleOauth2(OAuth2Exception e) {return CommonResult.failed(e.getMessage());}
}


  • 当我们输错密码,再次调用登录认证接口时,发现认证失败的结果也统一了。


自定义网关鉴权失败结果



  • 当我们使用过期或签名不正确的JWT令牌访问需要权限的接口时,会直接返回状态码401

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V2w8CAHD-1596462003622)(https://upload-images.jianshu.io/upload_images/22459064-b8bf72f75c310112?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]



  • 这个返回结果不符合我们的通用结果格式,其实我们想要的是返回状态码为200,然后返回如下格式信息;

{"code": 401,"data": "Jwt expired at 2020-07-10T08:38:40Z","message": "暂未登录或token已经过期"
}


  • 这里有个非常简单的改法,只需添加一行代码,修改网关的安全配置ResourceServerConfig,设置好资源服务器的ServerAuthenticationEntryPoint即可;

/*** 资源服务器配置* Created by macro on 2020/6/19.*/
@AllArgsConstructor
@Configuration
@EnableWebFluxSecurity
public class ResourceServerConfig {private final AuthorizationManager authorizationManager;private final IgnoreUrlsConfig ignoreUrlsConfig;private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;@Beanpublic SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());//自定义处理JWT请求头过期或签名错误的结果(新添加的)http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);http.authorizeExchange().pathMatchers(ArrayUtil.toArray(ignoreUrlsConfig.getUrls(),String.class)).permitAll()//白名单配置.anyExchange().access(authorizationManager)//鉴权管理器配置.and().exceptionHandling().accessDeniedHandler(restfulAccessDeniedHandler)//处理未授权.authenticationEntryPoint(restAuthenticationEntryPoint)//处理未认证.and().csrf().disable();return http.build();}
}


  • 添加完成后,再次访问需要权限的接口,就会返回我们想要的结果了。


兼容白名单接口



  • 其实对于白名单接口一直有个问题,当携带过期或签名不正确的JWT令牌访问时,会直接返回token过期的结果,我们可以访问下登录认证接口试试;



  • 明明就是个白名单接口,只不过携带的token不对就不让访问了,显然有点不合理。如何解决呢,我们先看看不带token访问怎么样;



  • 其实我们只要在Oauth2默认的认证过滤器前面再加个过滤器,如果是白名单接口,直接移除认证头即可,首先定义好我们的过滤器;

/*** 白名单路径访问时需要移除JWT请求头* Created by macro on 2020/7/24.*/
@Component
public class IgnoreUrlsRemoveJwtFilter implements WebFilter {@Autowiredprivate IgnoreUrlsConfig ignoreUrlsConfig;@Overridepublic Mono filter(ServerWebExchange exchange, WebFilterChain chain) {ServerHttpRequest request = exchange.getRequest();URI uri = request.getURI();PathMatcher pathMatcher = new AntPathMatcher();//白名单路径移除JWT请求头List ignoreUrls = ignoreUrlsConfig.getUrls();for (String ignoreUrl : ignoreUrls) {if (pathMatcher.match(ignoreUrl, uri.getPath())) {request = exchange.getRequest().mutate().header("Authorization", "").build();exchange = exchange.mutate().request(request).build();return chain.filter(exchange);}}return chain.filter(exchange);}
}


  • 然后把这个过滤器配置到默认的认证过滤器之前即可,在ResourceServerConfig中进行配置;

/*** 资源服务器配置* Created by macro on 2020/6/19.*/
@AllArgsConstructor
@Configuration
@EnableWebFluxSecurity
public class ResourceServerConfig {private final AuthorizationManager authorizationManager;private final IgnoreUrlsConfig ignoreUrlsConfig;private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;private final IgnoreUrlsRemoveJwtFilter ignoreUrlsRemoveJwtFilter;@Beanpublic SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());//自定义处理JWT请求头过期或签名错误的结果http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);//对白名单路径,直接移除JWT请求头(新添加的)http.addFilterBefore(ignoreUrlsRemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION);http.authorizeExchange().pathMatchers(ArrayUtil.toArray(ignoreUrlsConfig.getUrls(),String.class)).permitAll()//白名单配置.anyExchange().access(authorizationManager)//鉴权管理器配置.and().exceptionHandling().accessDeniedHandler(restfulAccessDeniedHandler)//处理未授权.authenticationEntryPoint(restAuthenticationEntryPoint)//处理未认证.and().csrf().disable();return http.build();}}


  • 携带过期请求头再次访问,发现已经可以正常访问了。


总结

至此,微服务中使用Oauth2实现统一认证和鉴权方案终于完善了!


推荐阅读
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 本文讨论了在shiro java配置中加入Shiro listener后启动失败的问题。作者引入了一系列jar包,并在web.xml中配置了相关内容,但启动后却无法正常运行。文章提供了具体引入的jar包和web.xml的配置内容,并指出可能的错误原因。该问题可能与jar包版本不兼容、web.xml配置错误等有关。 ... [详细]
  • 本文介绍了关于Java异常的八大常见问题,包括异常管理的最佳做法、在try块中定义的变量不能用于catch或finally的原因以及为什么Double.parseDouble(null)和Integer.parseInt(null)会抛出不同的异常。同时指出这些问题是由于不同的开发人员开发所导致的,不值得过多思考。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 第四章高阶函数(参数传递、高阶函数、lambda表达式)(python进阶)的讲解和应用
    本文主要讲解了第四章高阶函数(参数传递、高阶函数、lambda表达式)的相关知识,包括函数参数传递机制和赋值机制、引用传递的概念和应用、默认参数的定义和使用等内容。同时介绍了高阶函数和lambda表达式的概念,并给出了一些实例代码进行演示。对于想要进一步提升python编程能力的读者来说,本文将是一个不错的学习资料。 ... [详细]
  • 本文讨论了如何在codeigniter中识别来自angularjs的请求,并提供了两种方法的代码示例。作者尝试了$this->input->is_ajax_request()和自定义函数is_ajax(),但都没有成功。最后,作者展示了一个ajax请求的示例代码。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • 大数据Hadoop生态(20)MapReduce框架原理OutputFormat的开发笔记
    本文介绍了大数据Hadoop生态(20)MapReduce框架原理OutputFormat的开发笔记,包括outputFormat接口实现类、自定义outputFormat步骤和案例。案例中将包含nty的日志输出到nty.log文件,其他日志输出到other.log文件。同时提供了一些相关网址供参考。 ... [详细]
author-avatar
mobiledu2502916313
这个家伙很懒,什么也没留下!
RankList | 热门文章