Spring Security OAuth2 自定义异常处理 中文乱码

994 阅读1分钟

报错信息:

java.lang.IllegalArgumentException: a header value contains a prohibited character '\f': Bearer error="unauthorized", error_description="&÷áo 8÷Tû¡X" at io.netty.handler.codec.http.DefaultHttpHeadersHeaderValueConverterAndValidator.validateValueChar(DefaultHttpHeaders.java:477)atio.netty.handler.codec.http.DefaultHttpHeadersHeaderValueConverterAndValidator.validateValueChar(DefaultHttpHeaders.java:477) at io.netty.handler.codec.http.DefaultHttpHeadersHeaderValueConverterAndValidator.convertObject(DefaultHttpHeaders.java:453) at io.netty.handler.codec.http.DefaultHttpHeadersHeaderValueConverterAndValidator.convertObject(DefaultHttpHeaders.java:444)atio.netty.handler.codec.DefaultHeaders.addObject(DefaultHeaders.java:327)atio.netty.handler.codec.http.DefaultHttpHeaders.add(DefaultHttpHeaders.java:135)atio.netty.handler.codec.http.HttpObjectDecoder.readHeaders(HttpObjectDecoder.java:610)atio.netty.handler.codec.http.HttpObjectDecoder.decode(HttpObjectDecoder.java:257)atio.netty.handler.codec.http.HttpClientCodecHeaderValueConverterAndValidator.convertObject(DefaultHttpHeaders.java:444) at io.netty.handler.codec.DefaultHeaders.addObject(DefaultHeaders.java:327) at io.netty.handler.codec.http.DefaultHttpHeaders.add(DefaultHttpHeaders.java:135) at io.netty.handler.codec.http.HttpObjectDecoder.readHeaders(HttpObjectDecoder.java:610) at io.netty.handler.codec.http.HttpObjectDecoder.decode(HttpObjectDecoder.java:257) at io.netty.handler.codec.http.HttpClientCodecDecoder.decode(HttpClientCodec.java:225) at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:508) at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:447) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276) at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) at io.netty.channel.DefaultChannelPipelineHeadContext.channelRead(DefaultChannelPipeline.java:1410)atio.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)atio.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)atio.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)atio.netty.channel.nio.AbstractNioByteChannelHeadContext.channelRead(DefaultChannelPipeline.java:1410) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) at io.netty.channel.nio.AbstractNioByteChannelNioByteUnsafe.read(AbstractNioByteChannel.java:166) at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719) at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655) at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581) at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) at io.netty.util.concurrent.SingleThreadEventExecutor4.run(SingleThreadEventExecutor.java:989)atio.netty.util.internal.ThreadExecutorMap4.run(SingleThreadEventExecutor.java:989) at io.netty.util.internal.ThreadExecutorMap2.run(ThreadExecutorMap.java:74) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.base/java.lang.Thread.run(Thread.java:834)

排查现场:

在loadUserByUsername方法中编写以下代码 throw new UserNotFoundException("用户信息出错,请联系");

1、Postman访问gateway网关路由到该服务, 直接报以上的异常信息 a header value contains a prohibited character '\f': Bearer error="unauthorized", error_description="&÷áo 8÷Tû¡X"

2、Postman单独访问该服务, 无法返回响应体

奇怪的是,异常报文是“用户名或密码错误”是正常的,并没有中文乱码

查看了很多源码,发现

其实两个现场都是一个错误导致,在OAuth2中自定义的异常的处理逻辑

其中有段代码写到:

if (status == HttpStatus.UNAUTHORIZED.value() || (e instanceof InsufficientScopeException)) {
   headers.set("WWW-Authenticate", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));
}

其中e.getSummary()内的错误信息 如果是 【中文】,则响应体出现问题

由于我自定义了异常继承了AuthenticationException异常,导致会执行上面的代码逻辑

在 WWW-Authenticate 添加 【error="unauthorized", error_description="用户信息出错,请联系"】 会出现了中文乱码导致第1和第2的错误

初步解决方案:

将代码修改为以下,核心是 使用 base64加密传输,避免中文:

if (status == HttpStatus.UNAUTHORIZED.value() || (e instanceof InsufficientScopeException)) {
    String encoded = Base64.getEncoder().encodeToString(e.getSummary().getBytes(StandardCharsets.UTF_8));
    headers.set("WWW-Authenticate", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, encoded));
}

总结:

这个问题很隐蔽,查了很久 才找到 WWW-Authenticate 的中文值的问题