热门标签 | 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实现统一认证和鉴权方案终于完善了!


推荐阅读
  • 本文探讨了如何在PHP与MySQL环境中实现高效的分页查询,包括基本的分页实现、性能优化技巧以及高级的分页策略。 ... [详细]
  • 本文详细介绍了如何正确设置Shadowsocks公共代理,包括调整超时设置、检查系统限制、防止滥用及遵守DMCA法规等关键步骤。 ... [详细]
  • 汇总了2023年7月7日最新的网络安全新闻和技术更新,包括最新的漏洞披露、工具发布及安全事件。 ... [详细]
  • 本文详细介绍如何在SSM(Spring + Spring MVC + MyBatis)框架中实现分页功能。包括分页的基本概念、数据准备、前端分页栏的设计与实现、后端分页逻辑的编写以及最终的测试步骤。 ... [详细]
  • SSE图像算法优化系列三:超高速导向滤波实现过程纪要(欢迎挑战)
    自从何凯明提出导向滤波后,因为其算法的简单性和有效性,该算法得到了广泛的应用,以至于新版的matlab都将其作为标准自带的函数之一了&#x ... [详细]
  • Spring Security基础配置详解
    本文详细介绍了Spring Security的基础配置方法,包括如何搭建Maven多模块工程以及具体的安全配置步骤,帮助开发者更好地理解和应用这一强大的安全框架。 ... [详细]
  • 本文介绍了SIP(Session Initiation Protocol,会话发起协议)的基本概念、功能、消息格式及其实现机制。SIP是一种在IP网络上用于建立、管理和终止多媒体通信会话的应用层协议。 ... [详细]
  • 在尝试通过自定义端口部署Spring Cloud Eureka时遇到了连接失败的问题。本文详细描述了问题的现象,并提供了有效的解决方案,以帮助遇到类似情况的开发者。 ... [详细]
  • 本文详细介绍了 Java 中 org.w3c.dom.Node 类的 isEqualNode() 方法的功能、参数及返回值,并通过多个实际代码示例来展示其具体应用。此方法用于检测两个节点是否相等,而不仅仅是判断它们是否为同一个对象。 ... [详细]
  • 在现代Web开发中,HTML5 Canvas常用于图像处理和绘图任务。本文将详细介绍如何将Canvas中的图像导出并上传至服务器,适用于拼图、图片编辑等场景。 ... [详细]
  • CentOS7通过RealVNC实现多人使用服务器桌面
    背景:公司研发团队通过VNC登录到CentOS服务器的桌面实现软件开发工作为防止数据外泄,需要在RealVNC设置禁止传输文件、访问粘贴板等策略过程&# ... [详细]
  • 本文探讨了Linux环境下线程私有数据(Thread-Specific Data, TSD)的概念及其重要性,介绍了如何通过TSD技术避免多线程间全局变量冲突的问题,并提供了具体的实现方法和示例代码。 ... [详细]
  • C/C++ 应用程序的安装与卸载解决方案
    本文介绍了如何使用Inno Setup来创建C/C++应用程序的安装程序,包括自动检测并安装所需的运行库,确保应用能够顺利安装和卸载。 ... [详细]
  • Asynchronous JavaScript and XML (AJAX) 的流行很大程度上得益于 Google 在其产品如 Google Suggest 和 Google Maps 中的应用。本文将深入探讨 AJAX 在 .NET 环境下的工作原理及其实现方法。 ... [详细]
  • 本文详细介绍如何在 Apache 中设置虚拟主机,包括基本配置和高级设置,帮助用户更好地理解和使用虚拟主机功能。 ... [详细]
author-avatar
mobiledu2502916313
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有