同一套代码,采用的是 springCloud security.oauth2 密码模式认证登录注册,在本地跑,一切正常。
换成 Linux 的IP,通过 IDEA 打包成 docker 镜像在本地搭建的 centOS 中运行,测试登录接口,报空指针异常。
检查 jdk 版本,都是 1.8 :
CentOS 的:
[root]# java -version
java version "1.8.0_271"
Java(TM) SE Runtime Environment (build 1.8.0_271-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)
windows 的:
$ java -version
java version "1.8.0_311"
Java(TM) SE Runtime Environment (build 1.8.0_311-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.311-b11, mixed mode)
访问部署的非认证微服务的接口文档,访问成功。
通过网关微服务访问其它资源微服务,访问成功。
通过 postman 访问登录接口,返回结果为 null
查看 认证微服务 docker容器日志,显示登录成功,认证成功处理器异常=null,无法获取到 token ,抛出异常 NullPointerException , 无法登录成功。
docker 认证微服务容器关键报错日志:
05:33:04.035 INFO 1 --- [io-7001-exec-10] c.j.o.CustomAuthenticationSuccessHandler : 登录成功JwtUser(uid=1,
......
05:33:04.102 INFO 1 --- [io-7001-exec-10] c.j.o.CustomAuthenticationSuccessHandler : 认证成功处理器异常=null
java.lang.NullPointerException: null
at org.springframework.security.oauth2.provider.token.TokenEnhancerChain.enhance(TokenEnhancerChain.java:47)
at org.springframework.security.oauth2.provider.token.DefaultTokenServices.createAccessToken(DefaultTokenServices.java:301)
at
.......
at ( 这是自己写的成功处理器 ) com.jili20.oauth2.CustomAuthenticationSuccessHandler.onAuthenticationSuccess(CustomAuthenticationSuccessHandler.java:92)
报错 java 代码位置:
package com.jili20.oauth2;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jili20.util.enums.ResultEnum;
import com.jili20.util.result.Result;
import com.jili20.util.tool.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 成功处理器
* 校验客户端令牌,生成 jwt 令牌,响应 Result 数据
* localhost:7001/auth/login
*/
@Slf4j
@Component("customAuthenticationSuccessHandler")
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
// 请求头 前缀常量 //注意这里有一个空格
private static final String HEADER_TYPE = "Basic ";
// 客户端使用 jdbc 管理
@Resource
private ClientDetailsService clientDetailsService;
@Resource
private PasswordEncoder passwordEncoder;
@Resource // 创建令牌用
private AuthorizationServerTokenServices authorizationServerTokenServices;
@Resource // 转换为 json 对象
private ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// 获取当前的认证信息,只认证了用户和密码是否正确
log.info("登录成功{}", authentication.getPrincipal());
// 获取请求头中的客户端信息
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
log.info("header {}", header);
// 响应结果对象
Result result = null;
try {
// 下面这些判断,在 com.jili20.web.service.AuthService 刷新令牌中已经实现,复制过来就行
if (header == null || !header.startsWith(HEADER_TYPE)) {
throw new UnsupportedOperationException("请求头中无 client 信息");
}
// 解析请求头的客户端信息
String[] tokens = RequestUtil.extractAndDecodeHeader(header);
assert tokens.length == 2;
String clientId = tokens[0];
String clientSecret = tokens[1];
// 查询客户端信息,核对是否有效
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
if (clientDetails == null) {
throw new UnsupportedOperationException("clientId 对应的配置信息不存在:" + clientId);
}
// 校验客户端密码是否有效
if (!passwordEncoder.matches(clientSecret, clientDetails.getClientSecret())) {
throw new UnsupportedOperationException("无效 clientSecret");
}
// 组合请求对象,去获取令牌
// clientDetails.getScope() 可访问的范围 ; 取一个名字 custom 认证类型.
TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP,
clientId, clientDetails.getScope(), "custom");
// 创建 OAuth2Request 对象; OAuth2 必须要通过 tokenRequest 拿到 oAuth2Request 对象
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
// 有了上面两个对象,构建新的对象
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
// 通过 oAuth2Authentication 这个对象 作为参数,获取 访问令牌对象
OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
result = Result.ok(accessToken);
} catch (Exception e) {
log.info("认证成功处理器异常={}", e.getMessage(), e);
// 认证失败
result = Result.build(ResultEnum.AUTH_FAIL.getCode(), e.getMessage());
}
// 调字符集
response.setContentType("application/json;charset=UTF-8");
// 将对象转换成 json 字符串 ,这样客户端就会接收到 这个 json 数据
// 这样客户端才
response.getWriter().write(objectMapper.writeValueAsString(result));
}
}
由于是同一套代码,在本地运行没问题,环境也一样,虚拟机里除认证微服务,其它微服务访问也正常,我不知道如果解决这个 bug 了。