自定义Spring Cloud OAuth2中/oauth/token的返回内容格式 本是古典 何须时尚 2023-01-23 05:51 1阅读 0赞 ### 自定义Spring Cloud OAuth2中/oauth/token的返回内容格式 ### * 背景 * 实现原理 * 代码实现 * * 相关类 * 关键切面拦截器 # 背景 # 在前后端分离的项目中,一般后端返回给前端的格式是一个固定的json格式。在这个前提下,Spring Cloud OAuth2 生成access token的请求/oauth/token的返回内容就需要自定义。 访问/oauth/token示例如下: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3psYmRtbQ_size_16_color_FFFFFF_t_70_pic_center] 原始返回值的格式如下: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3psYmRtbQ_size_16_color_FFFFFF_t_70_pic_center 1] 我们希望使用我们自己固定的json格式,如下: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3psYmRtbQ_size_16_color_FFFFFF_t_70_pic_center 2] # 实现原理 # 原理就是通过切面编程实现对/oauth/token端点请求的结果进行拦截封装处理,由于/oauth/token是Spring Cloud OAuth2的内部端点,因此需要对相关的Spring源码进行分析。最终定位到 org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken() 方法上。 # 代码实现 # ## 相关类 ## CodeEnum.java package com.wongoing.common.model; /** * @description: 代码枚举 * @author: zheng * @date: Created in 2021/1/26 11:18 * @version: 0.0.1 * @modified By: */ public enum CodeEnum { SUCCESS(0), ERROR(1); private Integer code; CodeEnum(Integer code) { this.code = code; } public Integer getCode() { return this.code; } } Result.java package com.wongoing.common.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * @description: Rest API 接口方法返回类型定义 * @author: zheng * @date: Created in 2021/1/26 13:25 * @version: 0.0.1 * @modified By: */ @Data @NoArgsConstructor @AllArgsConstructor public class Result<T> implements Serializable { private T data; private Integer code; private String msg; public static <T> Result<T> of(T data, Integer code, String msg) { return new Result<>(data, code, msg); } public static <T> Result<T> succeed(String msg) { return of(null, CodeEnum.SUCCESS.getCode(), msg); } public static <T> Result<T> succeed(T model, String msg) { return of(model, CodeEnum.SUCCESS.getCode(), msg); } public static <T> Result<T> succeed(T model) { return of(model, CodeEnum.SUCCESS.getCode(), ""); } public static <T> Result<T> failed(String msg) { return of(null, CodeEnum.ERROR.getCode(), msg); } public static <T> Result<T> failed(T model, String msg) { return of(model, CodeEnum.ERROR.getCode(), msg); } } ## 关键切面拦截器 ## 在uaa项目中定义OauthTokenAspect.java package com.wongoing.oauth2.filter; import com.wongoing.common.constant.SecurityConstants; import com.wongoing.common.context.TenantContextHolder; import com.wongoing.common.model.Result; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.stereotype.Component; import java.security.Principal; import java.util.Map; /** * @description: oauth-token拦截器 * 1. 赋值租户 * 2. 统一返回token格式 * * @author: zheng * @date: Created in 2021/7/12 16:25 * @version: 0.0.1 * @modified By: */ @Slf4j @Component @Aspect public class OauthTokenAspect { @Around("execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(..))") public Object handleControllerMethod(ProceedingJoinPoint joinPoint) throws Throwable { try { Object[] args = joinPoint.getArgs(); Principal principal = (Principal) args[0]; if (!(principal instanceof Authentication)) { throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter."); } String clientId = this.getClientId(principal); Map<String, String> parameters = (Map<String, String>) args[1]; String grantType = parameters.get(OAuth2Utils.GRANT_TYPE); //保存租户id TenantContextHolder.setTenant(clientId); Object proceed = joinPoint.proceed(); if (SecurityConstants.AUTHORIZATION_CODE.equals(grantType)) { /** * 如果使用 @EnableOAuth2Sso 注解不能修改返回格式,否则授权码模式可以统一改 * 因为本项目的 sso-demo/ss-sso 里面使用了 @EnableOAuth2Sso 注解,所以这里就不修改授权码模式的token返回值了 */ return proceed; } else { ResponseEntity<OAuth2AccessToken> responseEntity = (ResponseEntity<OAuth2AccessToken>) proceed; OAuth2AccessToken body = responseEntity.getBody(); return ResponseEntity .status(HttpStatus.OK) .body(Result.succeed(body)); } } finally { TenantContextHolder.clear(); } } private String getClientId(Principal principal) { Authentication client = (Authentication) principal; if (!client.isAuthenticated()) { throw new InsufficientAuthenticationException("The client is not authenticated."); } String clientId = client.getName(); if (client instanceof OAuth2Authentication) { clientId = ((OAuth2Authentication) client).getOAuth2Request().getClientId(); } return clientId; } } 其中的常量值: public abstract class OAuth2Utils { public static final String GRANT_TYPE = "grant_type"; } public interface SecurityConstants { /** * 授权码模式 */ String AUTHORIZATION_CODE = "authorization_code"; } [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3psYmRtbQ_size_16_color_FFFFFF_t_70_pic_center]: /images/20221004/c4e5dfb370684e8b909cde3a70de4771.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3psYmRtbQ_size_16_color_FFFFFF_t_70_pic_center 1]: /images/20221004/e3bf3b777c144225bb8c08d81d681263.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3psYmRtbQ_size_16_color_FFFFFF_t_70_pic_center 2]: /images/20221004/db0efb060de24c6bb82a7b21fca6509b.png
还没有评论,来说两句吧...