bing_yu2001
2021-11-22 16:07
采纳率: 66.7%
浏览 49

同一套代码,在本地运行正常,打包成镜像到本地虚拟机里运行报空指针异常

同一套代码,采用的是 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 了。

  • 写回答
  • 好问题 提建议
  • 追加酬金
  • 关注问题
  • 邀请回答

4条回答 默认 最新

相关推荐 更多相似问题