Search code examples
angularspring-boothttpsangular-cli-v8

Angular 8 application accessing proxied Spring Boot application on localhost over https


I have written a Spring Boot backend and an Angular 8 frontend that I am packaging together. So when deployed, both use the same port and this works nicely.

During develpoment, I want both applications to run separately and have set this up as follows:

application.yml

server:
  port: 10000
  servlet:
    context-path: /

DataRestController.java

@RestController
@RequestMapping("/api/data")
public class DataRestController {

  @GetMapping()
  public Collection<Data> findAll() {
    // populate result
    return result;
  }
}

data.service.ts

export class DataService {
    private url = '/api/data';

    constructor(private http: HttpClient) {
    }

    getData(): Observable<Data[]> {
        return this.http.get<Data[]>(this.url);
    }
}

In order for the frontend to be able to connect to the backend I have the following command defined in my package.json file:

"start": "ng serve --proxy-config proxy.conf.json --hmr --port 4201"

proxy.conf.json

{
  "/api": {
    "target": "http://localhost:10000",
    "secure": "false"
  }
}

This all works perfectly. I can point my browser to http://localhost:10000/api/data and will receive the data in JSON format. Request by the frontend to http://localhost:4201/api/data are transparently forwarded to the backend URL and my angular application receives its data.

However, now I wanted to switch over to secure communication over https. I took the following steps:

I generated a PKCS12 file with a key pair and then extracted both the private key and certificate from this:

keytool -genkeypair -alias data -keyalg RSA -keysize 2048 -storetype PKCS12 -validity 3650 -keystore data.p12 -storepass <password>
openssl pkcs12 -info -in data.p12 -nodes -nocerts > data.key
openssl pkcs12 -info -in data.p12 -nokeys > data.cert

I copied the PKCS12 file to /src/main/resources/keystore/data.p12 in my backend and the two other files to /ssl/data.* in my frontend.

I added the following lines to my *application.yml

server:
  ssl:
    key-store-type: pkcs12
    key-store: classpath:keystore/data.p12
    key-alias: data
    key-store-password: ENC(<jasypt encrypted password>)
    key-password: ENC(<jasypt encrypted password>)

I also added a SecurityConfig.java file

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
        .requiresChannel()
        .anyRequest()
        .requiresSecure()
    ;
  }
}

If I now start my backend application, it no longer responds to the http URL, but it does answer on https://localhost:10000/api/data (I had to tell firefox to accept the self-signed certificate the first time I tried this, but afterwards it works flawlessly).

Now is where my troubles started, when I tried my frontend to talk to the backend over https :-(

I added the following script to package.json

"start-ssl": "ng serve --proxy-config proxy-secure.conf.json --hmr --port 4201 --ssl true --ssl-cert ssl/data.cert --ssl-key ssl/data.key",

and created a new file proxy-secure.conf.json

{
  "/api": {
    "target": "https://localhost:10000",
    "secure": "true"
  },
}

When I run start-ssl my application is now served on https://localhost:4201 and it loads fine in the browser. However, when the angular application wants to access https://localhost:4201/api/data I get the following error in the console and the browser shows that a HTTP Status 500 is returned for the request.

[HPM] Error occurred while trying to proxy request /api/data from localhost:4201 to localhost (DEPTH_ZERO_SELF_SIGNED_CERT) (https://nodejs.org/api/errors.html#errors_common_system_errors)

After some googling I stumbled across this conversation on github and following the various advice given there I tried the following:

{
  "/api": {
    "target": "https://localhost:10000",
    "secure": "false"
  },
}

as well as

{
  "/api": {
    "target": "https://localhost:10000",
    "secure": "false"
    "changeOrigin": true,
  },
}

and I also changed the file extension so the file is now proxy-secure.conf.js

var fs = require('fs');
var PROXY_CONFIG = {
  "/api": {
    "target": {
      "host": "localhost",
      "port": "10000",
      "protocol": "https:",
      "key": fs.readFileSync('ssl/data.key'),
      "cert": fs.readFileSync('ssl/data.cert')
    },
    "secure": "false",
    "rejectUnauthorized": "false",
    "changeOrigin": true,
    "logLevel": "debug"
  }
};
module.exports = PROXY_CONFIG;

This produces the following output when the application starts up:

[HPM] Proxy created: /api  ->  {
  host: 'localhost',
  port: '10000',
  protocol: 'https:',
  key: <Buffer 42 61 ... <many> more bytes>,
  cert: <Buffer 42 61 ... <many> more bytes>
}
[HPM] Subscribed to http-proxy events:  [ 'error', 'close' ]

However, as soon as the front tries to access the backend, I still get the same error:

[HPM] Error occurred while trying to proxy request /api/data from localhost:4201 to localhost (DEPTH_ZERO_SELF_SIGNED_CERT) (https://nodejs.org/api/errors.html#errors_common_system_errors)

I am running out of ideas and google has been no help. I can't imagine that I am the first person to try this setup. Is there anyway to tell the angular proxy to either not care about my self signed certificate or to tell it to trust that certificate?

If I do a ng build --prod and a mvn clean install (which thanks to maven-resources-plugin copies the frontend's dist folder into classes/static of the char and then run the Spring Boot Application using java -jar data-0.0.1-SNAPSHOT.jar everything works:

I can access data using my browser by going to https://localhost:10000/api/data and get my JSON raw data. I can also go to https://localhost:10000/ to launch my angular app and it has no difficulty directly accessing above JSON data over https.

So my setup works in production but I cannot get it to run in my development configuration with the angular proxy.


Edit: to report about trying out the suggestions from the SO article Berk Kurkcuoglu linked to

As far as I can tell, that article describes how to enable SSL (something which I had no problem with), but still, it was worth a shot:

I have added the following to my angular.json

"serve": {
    "builder": "@angular-devkit/build-angular:dev-server",
    "options": {
        "browserTarget": "data:build",
        "hmrWarning": false,
        "sslKey": "ssl/data.key",
        "sslCert": "ssl/data.cert",
        "ssl": true
    }
},

This has the effect that I can leave away the --ssl --ssl-cert <cert> --ssl-key <key> parameters from my ng serve entry in package.json, but the proxy still throws the same error.


Solution

  • Well, this is embarrassing.

    It turns out that the correct answers have been out there (on SO or Github) all the time and I was just to dumb to see it.

    My proxy.conf.json file looked as follows:

    {
      "/api": {
        "target": "https://localhost:10000",
        "secure": "false"
      },
    }
    

    when it should have been:

    {
      "/api": {
        "target": "https://localhost:10000",
        "secure": false
      },
    }
    

    (Note the boolean value instead of the string. Of course both "true" and "false" are truthy, so the both amounted to having true in my config.

    Now that I've changed that to false, everything works nicely.