RESTful客户端/服务器的HTTPS通信问题

时间:2019-08-09 16:03:39

标签: java spring-boot ssl https

我试图在我编写的使用Spring Boot提供RESTful功能的应用程序上通过HTTPS设置基于Web的安全性。该应用程序仅使用HTTP即可正常工作。

我已经做了大量的研究,以研究如何在应用程序中“启用” HTTPS(至少我是这样认为的),并将提供代码段和配置片段来说明我的功能。

我认为我已经接近了,但是还不能正常工作,我尝试过的各种方法都没有成功。

当前实现不要求服务(服务器)验证客户端的凭据。另外,我不需要任何形式的“用户”身份验证。

以下是当前设置的简要说明:

  • “任务计划者”服务,该服务将对其他两个对象进行REST调用 服务来执行一些工作。
  • 一种“路线生成器”服务,当任务计划者调用该服务时,该服务将返回一些响应数据。
  • 一种“路线评估器”服务,当任务计划者调用该服务时,它将返回一些数据以做出响应。
  • “客户” 这将向任务计划者发出REST调用以“计划任务”。任务计划者一无所获。

还有一个“虚拟”服务,可以简单地将当前时间返回给客户端的GET请求。简单的测试器。

所有这五个元素都被实现为@Service,并且任务计划程序,“路线”服务和虚拟对象具有对应的控制器(@RestController),在这些控制器上映射了REST端点。

我有为这三种服务(任务计划程序和两个“路线”服务-假人仅使用“路线”证书之一)生成的证书,并且这些文件位于“密钥库”位置。我还有一个“信任库”位置,其中包含生成CA的公钥。所有这五个服务都具有信任库。

我无法让客户端与任何服务进行对话(为简单起见,使用“虚拟”)。我还尝试通过Web浏览器访问虚拟端点,结果似乎表明通信管道的某些部分正在发生,但失败了。

下面是代码片段,希望可以按代码显示图片。

服务器(以“虚拟”为例)

Dummy.java:

@Service
@Profile("dummy")
public class Dummy {
  public String doIt() {
    return Long.toString(System.currentTimeMillis());
  }
}

DummyController.java:

@RestController
@RequestMapping("/rst")
@Profile("dummy")
public class DummyController {
  @Autowired
  private Dummy service;

  @GetMapping(value = "/dummy", produces = "text/plain")
  public String dummy() {
    return service.doIt();
  }
}

注意:下面的类和application.yml中的属性是我从网上的一个示例(https://github.com/indrabasak/spring-tls-example)中改编而来的。我不太理解已定义的“角色”的概念。这里有很多我还是不明白。

SecurityConfiguration.java:

@Configuration
@EnableWebSecurity
@EnableConfigurationProperties(SecurityAuthProperties.class)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
  private final Logger                 logger = LogManager.getLogger();
  private final SecurityAuthProperties properties;

  @Autowired
  public SecurityConfiguration(SecurityAuthProperties properties) {
    this.properties = properties;
  }

  @Override
  public void configure(AuthenticationManagerBuilder auth) {
    // properties.getUsers().forEach((key, value) -> {
    // try {
    // auth.inMemoryAuthentication()
    // .passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder())
    // .withUser(value.getId()).password(value.getPassword()).roles(value.getRoles());
    // logger.info("Added user " + value.getId() + " with password " + value.getPassword());
    // } catch (Exception e) {
    // throw new SecurityConfigurationException(
    // "Problem encountered while setting up authentication mananger", e);
    // }
    // });
  }

  @Override
  public void configure(HttpSecurity http) throws Exception {
    properties.getEndpoints().forEach((key, value) -> {
      try {
        for (HttpMethod method : value.getMethods()) {
          // http.authorizeRequests().antMatchers(method, value.getPath())
          // .hasAnyAuthority(value.getRoles()).and().httpBasic().and().csrf().disable();
          http.authorizeRequests().antMatchers(method, value.getPath()).permitAll().and()
              .httpBasic().and().csrf().disable();
          logger.info("Added security for path " + value.getPath() + " and method " + method);
        }
      } catch (Exception e) {
        throw new SecurityConfigurationException(
            "Problem encountered while setting up endpoint restrictions", e);
      }
    });

    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
  }

  @Override
  public void configure(WebSecurity web) {
    // TODO - what (if anything) do we do here?
  }
}

SecurityAuthProperties.java: (“用户”部分当前已取消,因为我们没有使用它。)

@ConfigurationProperties("security.auth")
public class SecurityAuthProperties {

  private static final String   ROLE_PREFIX    = "ROLE_";
  public static final String    ROLE_ANONYMOUS = "ROLE_ANONYMOUS";
  private Map<String, Endpoint> endpoints      = new HashMap<>();
  // private Map<String, User> users = new HashMap<>();

  @PostConstruct
  public void init() {
    endpoints.forEach((key, value) -> {
      List<String> roles = new ArrayList<>();
      for (String role : value.getRoles()) {
        roles.add(ROLE_PREFIX + role);
      }
      value.setRoles(roles.toArray(new String[0]));
    });

    // users.forEach((key, value) -> {
    // if (value.getId() == null) {
    // value.setId(key);
    // }
    //
    // if (value.getEncoding() != null) {
    // value.setPassword("{" + value.getEncoding().trim() + "}" + value.getPassword());
    // } else {
    // value.setPassword("{noop}" + value.getPassword());
    // }
    // });
  }

  public Map<String, Endpoint> getEndpoints() {
    return endpoints;
  }

  public void setEndpoints(Map<String, Endpoint> endpoints) {
    this.endpoints = endpoints;
  }

  // public Map<String, User> getUsers() {
  // return users;
  // }
  //
  // public void setUsers(Map<String, User> users) {
  // this.users = users;
  // }

  public static class Endpoint {
    private String       path;
    private HttpMethod[] methods;
    private String[]     roles;

    // trivial getters/setters removed for brevity

    public String[] getRoles() {
      if (roles == null || roles.length == 0) {
        roles = new String[1];
        roles[0] = ROLE_ANONYMOUS;
      }
      return roles;
    }
  }

  public static class User {
    private String   id;
    private String   encoding;
    private String   password;
    private String[] roles;

    // trivial getters/setters removed for brevity

    public String[] getRoles() {
      if (roles == null || roles.length == 0) {
        roles = new String[1];
        roles[0] = ROLE_ANONYMOUS;
      }
      return roles;
    }
  }
}

application.yml:

...
server:
  port: 8443
  ssl:
    enabled: true
    protocol: TLS
    trust-store-type: JKS
    trust-store: classpath:truststore/server.truststore
    trust-store-password: <password>
    key-store-type: JKS

security:
  auth:
    endpoints:
      endpoint1:
        path: /rst/dummy
        methods: GET
        roles: 

客户

ClientService.java:

@Service
public class ClientService {
  private final Logger        logger            = LogManager.getLogger();

  private static final String REST_DUMMY        = "rst/dummy";

  // @Autowired
  // private RestTemplate template;

  @Value("${web.protocol:http}")
  private String              protocol;
  @Value("${mission-planner.host:localhost}")
  private String              missionPlannerHost;
  @Value("${mission-planner.port:8443}")
  private int                 missionPlannerPort;

  @Scheduled(fixedRate = 10000)
  public void planMission() {
    logger.info("ClientService.planMission()");
    RestTemplate template = new RestTemplate();
    String url = new URLBuilder.Builder().usingProtocol(protocol).onHost(missionPlannerHost)
        .atPort(missionPlannerPort).atEndPoint(REST_DUMMY).build();
    String response = template.getForObject(url, String.class);
  }
}

我有一个大问题,如果服务器不需要验证客户端,则需要在客户端进行什么( )“安全”配置?我确实有一堆试图在客户端上执行此操作的类/配置,但是当前已将其禁用。

使用如下所示的代码,当我尝试与虚拟服务进行通信时,我在客户端上遇到异常:

org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://localhost:8443/rst/dummy": sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

更新

我决定尝试将server.ssl.key-alias(我在运行配置中通过-D设置)更改为大写(这似乎是证书所具有的),现在又得到了一个有趣的新异常。注意:我还为客户端和虚拟服务都设置了javax.net.debug=ssl

scheduling-1, WRITE: TLSv1.2 Handshake, length = 196
scheduling-1, READ: TLSv1.2 Alert, length = 2
scheduling-1, RECV TLSv1.2 ALERT:  fatal, handshake_failure
scheduling-1, called closeSocket()
scheduling-1, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
[2019-08-09 13:28:45.648] scheduling-1 ERROR: support.TaskUtils$LoggingErrorHandler:96 - Unexpected error occurred in scheduled task.
org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://localhost:8443/rst/dummy": Received fatal alert: handshake_failure; nested exception is javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

这是我在服务中得到的:

matching alias: route_assessor_1
matching alias: route_assessor_1
qtp1060563153-39, fatal error: 40: no cipher suites in common
javax.net.ssl.SSLHandshakeException: no cipher suites in common
%% Invalidated:  [Session-1, SSL_NULL_WITH_NULL_NULL]
qtp1060563153-39, SEND TLSv1.2 ALERT:  fatal, description = handshake_failure
qtp1060563153-39, WRITE: TLSv1.2 Alert, length = 2
qtp1060563153-39, fatal: engine already closed.  Rethrowing javax.net.ssl.SSLHandshakeException: no cipher suites in common
qtp1060563153-39, called closeOutbound()
qtp1060563153-39, closeOutboundInternal()

1 个答案:

答案 0 :(得分:0)

这似乎是由于运动部件过多而忘记重新打开东西的情况。

经过大量的整理并返回到原始源(https://github.com/indrabasak/spring-tls-example)并进行了一段时间的试验,我看不出作者的工作代码与非工作代码之间有什么显着差异

然后突然出现了其中一种情况,我意识到我不是在客户端中使用安全配置的REST模板(由于现在我不记得的原因而被注释掉了),。我只使用了一个普通的未配置模板。

我没有注释代码,瞧瞧,客户端现在验证服务器的证书。

接下来的问题。