技术标签: spring fastjson oauth2 springboot
起因: 在工作中,授权框架从 Spring Security
更换为 Oauth2
,授权获取 token
的时候,发现登录后,使用 FastJson
和采用默认的Jackson2
颁发 token
返回结果格式完全不同,按照 Oauth2
官网提供的示例中的返回结果,对比使用 FastJson
返回结果完全不同,于是在这个情况下,决定先探一下 Jackson2
为什么会返回正确格式,而 FastJson
却存在问题。
FastJson
错误格式
{
"additionalInformation": {
},
"expiration": "2021-04-15 16:22:09",
"expired": false,
"expiresIn": 7199,
"refreshToken": null,
"scope": [
"all"
],
"tokenType": "bearer",
"value": "852fd42a-1765-402d-bd27-3c6dfbf37eef"
}
Jackson2
正确格式
{
"access_token": "852fd42a-1765-402d-bd27-3c6dfbf37eef",
"token_type": "bearer",
"expires_in": 7199,
"scope": "all"
}
最开始发现这个问题的时候,我也是一头雾水,反正就很懵。很显然我只是更换了一个消息序列化框架,并没有更换其他内容,所以问题就出在消息转换器上。既然问题定位到了后,那么久先从正确的一个消息序列化框架入手,先看看他是怎么完成对 Oauth2
定制化解析的。
根据以前看 Mybatis
和 Spring
源码打下的基础,决定从 Jackson2
源码入手,通过对源码 DEBUG
看看能不能找到一些端倪。
- 进入
MappingJackson2HttpMessageConverter
类中,查看每个方法,凭借直觉判断哪个方法是往外写结果的方法,最终在org.springframework.http.converter.AbstractGenericHttpMessageConverter
类中找到了org.springframework.http.converter.AbstractGenericHttpMessageConverter#write
方法,代码如下:
public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
final HttpHeaders headers = outputMessage.getHeaders();
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {
@Override
public OutputStream getBody() {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}));
}
else {
writeInternal(t, type, outputMessage);
outputMessage.getBody().flush();
}
}
通过 DEBUG 模式,将断点打到
if
条件上,最终发现并没有进入if
内,而是走的else
, 并且我能够确定到当前参数t
的类型是DefaultOAuth2AccessToken
,并且这个对象和FastJson
序列化后的json
格式十分相似,那么我就确定了我之前的判断是对的,那么继续进入writeInternal(t, type, outputMessage)
方法内;
writeInternal(t, type, httpOutputMessage)
实现类啊,这~, 不要紧,咱们只是为了解决问题,而不是学源码,三个实现类都来一个断点,走哪个类,就看哪个类嘛,但是根据类名,我猜测是在
AbstractJackson2HttpMessageConverter
中的。
@Override
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// ********************************断点打在此处***************************************
MediaType contentType = outputMessage.getHeaders().getContentType();
JsonEncoding encoding = getJsonEncoding(contentType);
OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputStream, encoding);
try {
writePrefix(generator, object);
Object value = object;
Class<?> serializationView = null;
FilterProvider filters = null;
JavaType javaType = null;
if (object instanceof MappingJacksonValue) {
MappingJacksonValue container = (MappingJacksonValue) object;
value = container.getValue();
serializationView = container.getSerializationView();
filters = container.getFilters();
}
if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
// 通过 DEBUG ,看到这句话被执行了,很显然在 1.1 步中,我就知道这一步执行的答案了
javaType = getJavaType(type, null);
}
ObjectWriter objectWriter = (serializationView != null ?
this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());
if (filters != null) {
objectWriter = objectWriter.with(filters);
}
if (javaType != null && javaType.isContainerType()) {
objectWriter = objectWriter.forType(javaType);
}
SerializationConfig config = objectWriter.getConfig();
if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
objectWriter = objectWriter.with(this.ssePrettyPrinter);
}
// 最终进入了这个方法
objectWriter.writeValue(generator, value);
writeSuffix(generator, object);
generator.flush();
generator.close();
}
catch (InvalidDefinitionException ex) {
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
}
catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
}
}
ObjectWrite
在
1.3
中,通过DEBUG
发现方法进入了objectWriter.writeValue(generator, value);
内,那么这一步就DEBUG
这个方法内部
public void writeValue(JsonGenerator g, Object value) throws IOException {
_assertNotNull("g", g);
_configureGenerator(g);
if (_config.isEnabled(SerializationFeature.CLOSE_CLOSEABLE)
&& (value instanceof Closeable)) {
Closeable toClose = (Closeable) value;
try {
_prefetch.serialize(g, value, _serializerProvider());
if (_config.isEnabled(SerializationFeature.FLUSH_AFTER_WRITE_VALUE)) {
g.flush();
}
} catch (Exception e) {
ClassUtil.closeOnFailAndThrowAsIOE(null, toClose, e);
return;
}
toClose.close();
} else {
_prefetch.serialize(g, value, _serializerProvider());
if (_config.isEnabled(SerializationFeature.FLUSH_AFTER_WRITE_VALUE)) {
g.flush();
}
}
}
通过
DEBUG
此方法,最终进入了else
的逻辑,执行了_prefetch.serialize(g, value, _serializerProvider())
这句代码,那么继续进入该方法
com.fasterxml.jackson.databind.ObjectWriter.Prefetch#serialize
方法内public void serialize(JsonGenerator gen, Object value, DefaultSerializerProvider prov) throws IOException {
if (typeSerializer != null) {
prov.serializePolymorphic(gen, value, rootType, valueSerializer, typeSerializer);
} else if (valueSerializer != null) {
prov.serializeValue(gen, value, rootType, valueSerializer);
} else if (rootType != null) {
prov.serializeValue(gen, value, rootType);
} else {
prov.serializeValue(gen, value);
}
}
通过
DEBUG
,不难发现最终走到了else
逻辑,进入prov.serializeValue(gen, value)
DefaultSerializerProvider
类中通过该类名就能知道,这个类主要是提供一个消息序列化器,那么这个类是一个重头戏,最终今天要分析的源头也就在这个类中
public void serializeValue(JsonGenerator gen, Object value) throws IOException {
_generator = gen;
if (value == null) {
_serializeNull(gen);
return;
}
final Class<?> cls = value.getClass();
// ******************************************************************
final JsonSerializer<Object> ser = findTypedValueSerializer(cls, true, null);
// ******************************************************************
PropertyName rootName = _config.getFullRootName();
if (rootName == null) {
// not explicitly specified
if (_config.isEnabled(SerializationFeature.WRAP_ROOT_VALUE)) {
_serialize(gen, value, ser, _config.findRootName(cls));
return;
}
} else if (!rootName.isEmpty()) {
_serialize(gen, value, ser, rootName);
return;
}
_serialize(gen, value, ser);
}
在代码中,我标注了一句代码,该代码是产生今天这个问题的核心原因,那么这句代码到底都做了什么内容?
通过方法名,findTypedValSerializer
,翻译过来就是通过被序列化的对象的Class对象,获取到该类的序列化器
通过以上的代码,你发现了什么了没,是不是Oauth2
针对了Jackson2
开发了一个单独的消息序列化器,却并没有对FastJson
开发,
既然你不给我开发,我自己开发一个就完事儿了,说干就干。
Oauth2
开发 FastJson
序列化器1
点 的分析,我们知道 Oauth2
对 Jackson2
开发了一个消息序列化器,那么我们需要自己写一个,就先看看别人的处理逻辑是怎么样的!进入
OAuth2AccessTokenJackson2Serializer
类中
public final class OAuth2AccessTokenJackson2Serializer extends StdSerializer<OAuth2AccessToken> {
public OAuth2AccessTokenJackson2Serializer() {
super(OAuth2AccessToken.class);
}
@Override
public void serialize(OAuth2AccessToken token, JsonGenerator jgen, SerializerProvider provider) throws IOException,
JsonGenerationException {
jgen.writeStartObject();
// OAuth2AccessToken.ACCESS_TOKEN = access_token
jgen.writeStringField(OAuth2AccessToken.ACCESS_TOKEN, token.getValue());
// OAuth2AccessToken.TOKEN_TYPE = token_type
jgen.writeStringField(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType());
OAuth2RefreshToken refreshToken = token.getRefreshToken();
if (refreshToken != null) {
// OAuth2AccessToken.REFRESH_TOKEN = refresh_token
jgen.writeStringField(OAuth2AccessToken.REFRESH_TOKEN, refreshToken.getValue());
}
Date expiration = token.getExpiration();
if (expiration != null) {
long now = System.currentTimeMillis();
// OAuth2AccessToken.EXPIRES_IN = expires_in
jgen.writeNumberField(OAuth2AccessToken.EXPIRES_IN, (expiration.getTime() - now) / 1000);
}
Set<String> scope = token.getScope();
if (scope != null && !scope.isEmpty()) {
StringBuffer scopes = new StringBuffer();
for (String s : scope) {
Assert.hasLength(s, "Scopes cannot be null or empty. Got " + scope + "");
scopes.append(s);
scopes.append(" ");
}
// OAuth2AccessToken.SCOPE = scope
jgen.writeStringField(OAuth2AccessToken.SCOPE, scopes.substring(0, scopes.length() - 1));
}
Map<String, Object> additionalInformation = token.getAdditionalInformation();
for (String key : additionalInformation.keySet()) {
jgen.writeObjectField(key, additionalInformation.get(key));
}
jgen.writeEndObject();
}
}
通过以上的类,发现好像这并不难啊,我自己也能写一个,那么开始动手写一个
FastJson
的消息序列化器
Oauth2AccessTokenFastJsonSerializer
OAuth2AccessTokenJackson2Serializer
都是取值赋值,我搞一个Map
,然后序列化成String
,然后写出去,效果是一样的,那么说干就干
public class Oauth2AccessTokenFastJsonSerializer implements ObjectSerializer {
@Override
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
SerializeWriter out = serializer.out;
Map<String, Object> token = this.fastJsonSerializerOAuth2Token((OAuth2AccessToken)object);
String strToken = JSONObject.toJSONString(token);
out.write(strToken);
}
private Map<String, Object> fastJsonSerializerOAuth2Token(OAuth2AccessToken token) {
// 创建结果集包装容器
Map<String, Object> tokenMap = new HashMap<>();
// access_token 赋值
tokenMap.put(OAuth2AccessToken.ACCESS_TOKEN, token.getValue());
// token_type 赋值
tokenMap.put(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType());
// 是否存在 refresh_token
OAuth2RefreshToken refreshToken = token.getRefreshToken();
if (refreshToken != null) {
tokenMap.put(OAuth2AccessToken.REFRESH_TOKEN, refreshToken.getValue());
}
// 是否存在超时时间
Date expiration = token.getExpiration();
if (expiration != null) {
long now = System.currentTimeMillis();
tokenMap.put(OAuth2AccessToken.EXPIRES_IN, (expiration.getTime() - now) / 1000);
}
// 多个token作用范围需要处理,单不能为空
Set<String> scope = token.getScope();
if (scope != null && !scope.isEmpty()) {
StringBuilder scopes = new StringBuilder();
for (String s : scope) {
Assert.hasLength(s, "Scopes cannot be null or empty. Got " + scope + "");
scopes.append(s);
scopes.append(" ");
}
tokenMap.put(OAuth2AccessToken.SCOPE, scopes.substring(0, scopes.length() - 1));
}
// 其余参数的处理
Map<String, Object> additionalInformation = token.getAdditionalInformation();
for (String key : additionalInformation.keySet()) {
tokenMap.put(key, additionalInformation.get(key));
}
return tokenMap;
}
}
注意:
Jackson2 是实现的com.fasterxml.jackson.databind.ser.std.StdSerializer
这个类,然后重写serialize(OAuth2AccessToken token, JsonGenerator jgen, SerializerProvider provider)
方法,而这个类确是 Jackson2 提供的,FastJson 重写序列化器需要实现com.alibaba.fastjson.serializer.ObjectSerializer
类,重写write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features)
方法
@Configuration
public class GlobalFastJsonHttpMessageConvertConfiguration implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.clear();
// converters.removeIf(convert -> convert instanceof FastJsonHttpMessageConverter);
// converters.removeIf(convert -> convert instanceof MappingJackson2HttpMessageConverter);
converters.add(this.fastJsonHttpMessageConvert());
}
private HttpMessageConverter<?> fastJsonHttpMessageConvert() {
FastJsonHttpMessageConverter fc = new FastJsonHttpMessageConverter();
FastJsonConfig fjc = new FastJsonConfig();
fjc.setSerializerFeatures(
// 引用字段名称
SerializerFeature.QuoteFieldNames,
// 写入映射空值
SerializerFeature.WriteMapNullValue,
// 禁用循环参考检测
SerializerFeature.DisableCircularReferenceDetect,
// 写入日期使用日期格式
SerializerFeature.WriteDateUseDateFormat,
// 将空字符串写入为空
SerializerFeature.WriteNullStringAsEmpty
);
// **************************** 注册序列化器到容器中 ************************************
fjc.getSerializeConfig().put(DefaultOAuth2AccessToken.class, new Oauth2AccessTokenFastJsonSerializer());
// **************************** 注册序列化器到容器中 ************************************
List<MediaType> mediaTypeList = new ArrayList<>();
mediaTypeList.add(MediaType.APPLICATION_JSON);
fc.setSupportedMediaTypes(mediaTypeList);
fc.setFastJsonConfig(fjc);
return fc;
}
}
fjc.getSerializeConfig().put(DefaultOAuth2AccessToken.class, new Oauth2AccessTokenFastJsonSerializer())
这句代码,就是将自定义的Oauth2
定制化序列化器注册到配置容器中,值得注意得是,SerializeConfig
中,key
是DefaultOAuth2AccessToken.class
并不是OAuth2AccessToken.class
Oauth2AccessTokenFastJsonSerializer
完全符合需求
解决问题其实并不难,只是需要一双慧眼,去发现问题所在的点,然后一点点的推进,就能解决问题,阅读源码需要坚持,有了基础的条件下,很多代码配合注释是嫩看懂的,千万不要遇到问题就打退堂鼓。
文章浏览阅读757次,点赞19次,收藏9次。文字动态特效_html canvas 效果
针对组件业务上的需求,需要给 el-table-column 加上限制,需保证表头在一行展示,部分列的内容要一行展示,自适应单项列的宽度;
Ali-Sentinel-链路控制
message handler
文章浏览阅读4.2k次。环境:vs2019 gmssl 32位编译1、首先新建项目2、在VS的工程设置工程属性(参考连接https://blog.csdn.net/zhonghua_csdn/article/details/99011892)右击工程名 ——> 选择“属性” 在“VC++目录”——> “包含目录”中添加openSSL的include文件(在您安装openssl的文件下) 在“VC++目录”——> “库目录”中添加openSSL的lib文件(在您安装openssl的文件下) 在“._使用c语言调用openssl实现sm4代码
文章浏览阅读73次。来源:http://www.bysjhf.com.cn目前,U盘病毒的情况非常严重,几乎所有带病毒的U盘,根目录里都有一个autorun.inf。右键菜单多了“自动播放”、“Open”、“Browser”等项目。由于我们习惯用双击来打开磁盘,但现在我们双击,通常不是打开U盘,而是让autorun.inf里所设的程序自动播放。所以对于很多人来说相当麻烦。其实Autorun...._linux怎么为windows做autorun免疫
文章浏览阅读1.5k次。Qt Creator 报错:Error while building/deploying project helloworld (kit: Desktop Qt 5.6.2 MinGW 32bit) When executing step "qmake" - zhangjunwu - 博客园 (cnblogs.com)https://www.cnblogs.com/zhangjunwu/p/7417566.html注意:Qt文件路径不要出现中文名字和空格!!!......_error while building/deploying project xianzhazhi (kit: desktop qt 5.12.9 ms
文章浏览阅读1.3k次。Installing packages. This might take a couple of minutes.Installing react, react-dom, and react-scripts with cra-template-typescript...npm ERR! code 1npm ERR! path C:\Users\MHX\Desktop\react-demo\node_modules\canvasnpm ERR! command failednpm ERR! comm_installing packages. this might take a couple of minutes. installing react,
文章浏览阅读1.9w次,点赞43次,收藏214次。关于西电计科本科学习的一些经验分享与资料汇总_西电毕设拿良容易吗
文章浏览阅读279次,点赞9次,收藏3次。项目根目录打开终端,执行以下命令,安装依赖。执行以下命令后,在浏览器中打开。就可以打开这个项目了。
文章浏览阅读8.5k次,点赞4次,收藏24次。关于C++中二维vector使用vector本来就是可以用来代替一维数组的,vector提供了operator[]函数,可以像数组一样的操作,而且还有边界检查,动态改变大小。这里只介绍用它来代替二维的数组,二维以上的可以依此类推。1、定义二维vectorvector<vector<int>> A;//错误的定义方式vector<vector<int> > A;//正缺的定义方式vector<vector<int> > v;/_c++ 二维vector
文章浏览阅读187次。广告关闭腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元!导言:记录下学习的算法题,写练多,脑子才能转的快! 今日算法题:二分法查找说下我对于二分法查找的理解:【和猜数字游戏差不多】 要在一个有序数列中找到一个与对应给定数字。 1、找到有序数列中最中间的数字2、若中间值大于给定值,则在左边数列重新二分查找3、若中间值小于给定值,则在右边数列..._python服务端算法题