To run frontend locally, use ./gradlew :save-frontend:browserDevelopmentRun --continuous
or ./gradlew :save-frontend:browserProductionRun --continuous
.
To pack distribution, use ./gradlew :save-frontend:browserDevelopmentWebpack
and ./gradlew :save-frontend:browserProductionWebpack
.
save-backend consumes frontend distribution as a dependency. Frontend distribution is copied and included in spring boot resources.
The most interesting part of nginx.conf
is here:
location / {
index index.html;
try_files $uri $uri/ /index.html;
add_header Cache-Control "private; no-store";
etag off;
add_header Last-Modified "";
if_modified_since off;
}
Here we define a configuration for the location /
block, which is the default location block in nginx
and matches any
request that doesn't match other specific location blocks.
Let's break down each directive and explain what happens:
index index.html
; This directive sets the default file to serve when a directory is requested. In this case, if a request is made to a directory (e.g., http://example.com/), Nginx will try to serve theindex.html
file from that directory. If the file doesn't exist, Nginx will move to the next directive.try_files $uri $uri/ /index.html;
This directive specifies a series of files and locations that Nginx should try to serve. It acts as a fallback mechanism when the requested file is not found in the specified locations.
$uri
represents the requested URI.$uri/
represents the URI followed by a trailing slash, typically used for directories./index.html
is the last fallback option.
add_header Cache-Control "private; no-store";
This directive adds an HTTP response header namedCache-Control
to the server's responses."private; no-store"
sets theCache-Control
header value, which tells the client and intermediate caching servers not to store any cached copies of the response. It ensures that the content is not stored in any cache, making every request hit the server directly.etag off;
ETag
is an identifier that helps with caching, but disabling it ensures that the server doesn't useETag
for caching validation. This can be useful for certain scenarios whereETags
are not necessary or could cause caching issues.add_header Last-Modified "";
TheLast-Modified
header is used for caching validation, but setting it to an empty value indicates that the server does not want to participate in any caching validation based on the last modification date of the resource.if_modified_since off;
Theif_modified_since
directive controls the behavior of theIf-Modified-Since
request header. Disabling it ensures that clients won't use conditional requests, which can be helpful in certain caching scenarios or when caching behavior needs to be controlled explicitly.
Here is a webpack-dev-server
configuration for running without api-gateway
on:
config.devServer = Object.assign(
{},
config.devServer || {},
{
setupMiddlewares: (middlewares, devServer) => {
devServer.app.get("/sec/oauth-providers", (req, res) => { return res.send([]); });
return middlewares;
},
proxy: [
{
context: ["/api/demo/**"],
target: 'http://localhost:5421',
logLevel: 'debug',
onProxyReq: function (proxyReq, req, res) {
proxyReq.setHeader("X-Authorization-Id", "1");
proxyReq.setHeader("X-Authorization-Name", "admin");
proxyReq.setHeader("X-Authorization-Roles", "ROLE_SUPER_ADMIN");
proxyReq.setHeader("X-Authorization-Status", "ACTIVE");
}
},
{
context: ["/api/cpg/**"],
target: 'http://localhost:5500',
logLevel: 'debug',
onProxyReq: function (proxyReq, req, res) {
proxyReq.setHeader("X-Authorization-Id", "1");
proxyReq.setHeader("X-Authorization-Name", "admin");
proxyReq.setHeader("X-Authorization-Roles", "ROLE_SUPER_ADMIN");
proxyReq.setHeader("X-Authorization-Status", "ACTIVE");
}
},
{
context: ["/api/**"],
target: 'http://localhost:5800',
logLevel: 'debug',
onProxyReq: function (proxyReq, req, res) {
proxyReq.setHeader("X-Authorization-Id", "1");
proxyReq.setHeader("X-Authorization-Name", "admin");
proxyReq.setHeader("X-Authorization-Roles", "ROLE_SUPER_ADMIN");
proxyReq.setHeader("X-Authorization-Status", "ACTIVE");
}
}
],
historyApiFallback: true
}
);
setupMiddlewares
sets a stub for /sec/oauth-providers
endpoint.
historyApiFallback
makes webpack-dev-server
return index.html
in case of 404 Not Found
.
Sometimes it is useful to add authorization headers when proxying to some Spring services e.g. save-backend
.
It can be done with setting onProxyReq
:
onProxyReq: (proxyReq, req, res) => {
proxyReq.setHeader("X-Authorization-Id", "1");
proxyReq.setHeader("X-Authorization-Name", "admin");
proxyReq.setHeader("X-Authorization-Roles", "ROLE_SUPER_ADMIN");
proxyReq.setHeader("X-Authorization-Status", "ACTIVE");
}
Thus, we add Authorization
and X-Authorization-Source
headers that correspond with admin
user headers.
-
When the default
dev-server.js
is used, the front-end is expected to communicate directly with the back-end, omitting any gateway. When enabling OAuth, make sure the gateway is contacted instead:context
: add/sec/**, /oauth2/**, /#/oauth2/**
to the list;target
: change tohttp://localhost:5300
(the default gateway URL);onProxyReq
: drop the entire callback, since all auth headers (X-Authorization-Id
,X-Authorization-Name
,X_Authorization-Status
andX-Authorization-Roles
) will be set by the gateway now (the gateway acts as a reverse proxy);bypass
: drop the entire callback.
The resulting
dev-server.js
should look like this:config.devServer = Object.assign( {}, config.devServer || {}, { proxy: [ { context: ["/api/**", "/sec/**", "/oauth2/**", "/logout/**", "/#/oauth2/**"], target: 'http://localhost:5300', logLevel: 'debug', } ], historyApiFallback: true } )
Notice that
historyApiFallback
is required forBrowserRouter
work fine. -
Avoid potential name conflicts between local users (those authenticated using HTTP Basic Auth) and users created via an external OAuth provider. For example, if you have a local user named
torvalds
, don't try to authenticate as a GitHub user with the same name.