两种方式实现Spring Cloud OAuth2 自定义token返回格式
问题描述
Spring Security OAuth的token返回格式都是默认的,但是往往这个格式是不适配系统,/oauth/token
返回的格式如下:
{
"access_token": token
"token_type": "bearer",
"refresh_token": xxxx
"expires_in": xxx,
"scope": "xxx",
"jti": xxxx
....................
}
然而此时系统中的统一返回格式为:
{
"code":xxx
"data":xxx
"msg":xxx
}
那么如何去对默认的格式进行修改呢?
解决方案
其实解决方案还是很多的,据陈某了解有如下两种解决方案:
- 使用AOP的方式对
/oauth/token
这个接口的结果拦截修改 - 重定义接口覆盖默认的
第一种方案呢可以实现,但是对于陈某来说不够优雅,实现比较简单,不显逼格
于是陈某今天介绍第二种方案,一种比较优雅的方式;想要理解第二种方式必须对Spring Security的底层源码有一些了解。
/oauth/token
这个接口定义在哪里呢?通过源码我们知道定义在org.springframework.security.oauth2.provider.endpoint.TokenEndpoint
中,如下:
@RequestMapping(value = "/oauth/token", method=RequestMethod.GET)
public ResponseEntity<OAuth2AccessToken> getAccessToken(Principal principal, @RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {}
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {}
可以看到针对这个接口定义了两个,一个是GET请求、一个是POST请求
TokenEndpoint
其实就是一个接口,使用注解@FrameworkEndpoint
标注,这个注解和@Controller
的作用一样,如下:
@FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint {}
那么知道在哪里定义的就好办了,模仿着它这个接口自己重新定义一个覆盖掉不就好了,如下:
@Api(value = "OAuth接口")
@RestController
@RequestMapping("/oauth")
@Slf4j
public class AuthController implements InitializingBean {
//令牌请求的端点
@Autowired
private TokenEndpoint tokenEndpoint;
//自定义异常翻译器,针对用户名、密码异常,授权类型不支持的异常进行处理
private OAuthServerWebResponseExceptionTranslator translate;
/**
* 重写/oauth/token这个默认接口,返回的数据格式统一
*/
@PostMapping(value = "/token")
public ResultMsg<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
OAuth2AccessToken accessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
return ResultMsg.resultSuccess(accessToken);
}
}
可以看到接口内部不需要自己重写逻辑,只需要调用TokenEndpoint
中的方法
注意:由于对TokenEndpoint中的端点重写了,因此前面定义的对用户名、密码之类的异常捕获的翻译类(OAuthServerWebResponseExceptionTranslator)将会失效,需要在全局异常中进行捕获
上面是/oauth/token
的接口,/oauth/check_token
这个校验token的接口如需自定义也是可以的,对应的类是org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint
重写后代码如下:
@Api(value = "OAuth接口")
@RestController
@RequestMapping("/oauth")
@Slf4j
public class AuthController implements InitializingBean {
@Autowired
private CheckTokenEndpoint checkTokenEndpoint;
//自定义异常翻译器,针对用户名、密码异常,授权类型不支持的异常进行处理
private OAuthServerWebResponseExceptionTranslator translate;
/**
* 重写/oauth/check_token这个默认接口,用于校验令牌,返回的数据格式统一
*/
@PostMapping(value = "/check_token")
public ResultMsg<Map<String,?>> checkToken(@RequestParam("token") String value) {
Map<String, ?> map = checkTokenEndpoint.checkToken(value);
return ResultMsg.resultSuccess(map);
}
这种方式是不是很优雅?也很符合Spring Security的设计思想,AOP的方式还要对参数解析,重新包装
关键切面拦截器
在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";
}
好了,关于测试的话自己搞一搞
总结
本篇文章介绍了认证服务中对token的返回格式自定义,总的来说还是比较简单的,有兴趣的也可以去网上找找关于AOP的方式。
文章分别转自一下地址,如有侵权,请联系删除。:
1.https://www.jb51.net/article/218529.htm
2. https://blog.csdn.net/m0_67698950/article/details/125681135