BAEL-1996 (#5507)
* [BAEL-1996] WIP - Initial import * [BAEL-1996] Apply formatting rules * [BAEL-1996] Import UAA modules * [BAEL-1996] New directory structure
This commit is contained in:
parent
ab821cb4e5
commit
df8158ad81
|
@ -0,0 +1,24 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
|
||||
# Change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# We recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -0,0 +1,149 @@
|
|||
# This file is inspired by https://github.com/alexkaratarakis/gitattributes
|
||||
#
|
||||
# Auto detect text files and perform LF normalization
|
||||
# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/
|
||||
* text=auto
|
||||
|
||||
# The above will handle all files NOT found below
|
||||
# These files are text and should be normalized (Convert crlf => lf)
|
||||
|
||||
*.bat text eol=crlf
|
||||
*.coffee text
|
||||
*.css text
|
||||
*.cql text
|
||||
*.df text
|
||||
*.ejs text
|
||||
*.html text
|
||||
*.java text
|
||||
*.js text
|
||||
*.json text
|
||||
*.less text
|
||||
*.properties text
|
||||
*.sass text
|
||||
*.scss text
|
||||
*.sh text eol=lf
|
||||
*.sql text
|
||||
*.txt text
|
||||
*.ts text
|
||||
*.xml text
|
||||
*.yaml text
|
||||
*.yml text
|
||||
|
||||
# Documents
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
*.markdown text
|
||||
*.md text
|
||||
*.adoc text
|
||||
*.textile text
|
||||
*.mustache text
|
||||
*.csv text
|
||||
*.tab text
|
||||
*.tsv text
|
||||
*.txt text
|
||||
AUTHORS text
|
||||
CHANGELOG text
|
||||
CHANGES text
|
||||
CONTRIBUTING text
|
||||
COPYING text
|
||||
copyright text
|
||||
*COPYRIGHT* text
|
||||
INSTALL text
|
||||
license text
|
||||
LICENSE text
|
||||
NEWS text
|
||||
readme text
|
||||
*README* text
|
||||
TODO text
|
||||
|
||||
# Graphics
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.tif binary
|
||||
*.tiff binary
|
||||
*.ico binary
|
||||
# SVG treated as an asset (binary) by default. If you want to treat it as text,
|
||||
# comment-out the following line and uncomment the line after.
|
||||
*.svg binary
|
||||
#*.svg text
|
||||
*.eps binary
|
||||
|
||||
# These files are binary and should be left untouched
|
||||
# (binary is a macro for -text -diff)
|
||||
*.class binary
|
||||
*.jar binary
|
||||
*.war binary
|
||||
|
||||
## LINTERS
|
||||
.csslintrc text
|
||||
.eslintrc text
|
||||
.jscsrc text
|
||||
.jshintrc text
|
||||
.jshintignore text
|
||||
.stylelintrc text
|
||||
|
||||
## CONFIGS
|
||||
*.bowerrc text
|
||||
*.conf text
|
||||
*.config text
|
||||
.editorconfig text
|
||||
.gitattributes text
|
||||
.gitconfig text
|
||||
.gitignore text
|
||||
.htaccess text
|
||||
*.npmignore text
|
||||
|
||||
## HEROKU
|
||||
Procfile text
|
||||
.slugignore text
|
||||
|
||||
## AUDIO
|
||||
*.kar binary
|
||||
*.m4a binary
|
||||
*.mid binary
|
||||
*.midi binary
|
||||
*.mp3 binary
|
||||
*.ogg binary
|
||||
*.ra binary
|
||||
|
||||
## VIDEO
|
||||
*.3gpp binary
|
||||
*.3gp binary
|
||||
*.as binary
|
||||
*.asf binary
|
||||
*.asx binary
|
||||
*.fla binary
|
||||
*.flv binary
|
||||
*.m4v binary
|
||||
*.mng binary
|
||||
*.mov binary
|
||||
*.mp4 binary
|
||||
*.mpeg binary
|
||||
*.mpg binary
|
||||
*.swc binary
|
||||
*.swf binary
|
||||
*.webm binary
|
||||
|
||||
## ARCHIVES
|
||||
*.7z binary
|
||||
*.gz binary
|
||||
*.rar binary
|
||||
*.tar binary
|
||||
*.zip binary
|
||||
|
||||
## FONTS
|
||||
*.ttf binary
|
||||
*.eot binary
|
||||
*.otf binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
|
@ -0,0 +1,145 @@
|
|||
######################
|
||||
# Project Specific
|
||||
######################
|
||||
/src/main/webapp/content/css/main.css
|
||||
/target/www/**
|
||||
/src/test/javascript/coverage/
|
||||
|
||||
######################
|
||||
# Node
|
||||
######################
|
||||
/node/
|
||||
node_tmp/
|
||||
node_modules/
|
||||
npm-debug.log.*
|
||||
/.awcache/*
|
||||
/.cache-loader/*
|
||||
|
||||
######################
|
||||
# SASS
|
||||
######################
|
||||
.sass-cache/
|
||||
|
||||
######################
|
||||
# Eclipse
|
||||
######################
|
||||
*.pydevproject
|
||||
.project
|
||||
.metadata
|
||||
tmp/
|
||||
tmp/**/*
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.classpath
|
||||
.settings/
|
||||
.loadpath
|
||||
.factorypath
|
||||
/src/main/resources/rebel.xml
|
||||
|
||||
# External tool builders
|
||||
.externalToolBuilders/**
|
||||
|
||||
# Locally stored "Eclipse launch configurations"
|
||||
*.launch
|
||||
|
||||
# CDT-specific
|
||||
.cproject
|
||||
|
||||
# PDT-specific
|
||||
.buildpath
|
||||
|
||||
######################
|
||||
# Intellij
|
||||
######################
|
||||
.idea/
|
||||
*.iml
|
||||
*.iws
|
||||
*.ipr
|
||||
*.ids
|
||||
*.orig
|
||||
classes/
|
||||
out/
|
||||
|
||||
######################
|
||||
# Visual Studio Code
|
||||
######################
|
||||
.vscode/
|
||||
|
||||
######################
|
||||
# Maven
|
||||
######################
|
||||
/log/
|
||||
/target/
|
||||
|
||||
######################
|
||||
# Gradle
|
||||
######################
|
||||
.gradle/
|
||||
/build/
|
||||
|
||||
######################
|
||||
# Package Files
|
||||
######################
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
*.db
|
||||
|
||||
######################
|
||||
# Windows
|
||||
######################
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
######################
|
||||
# Mac OSX
|
||||
######################
|
||||
.DS_Store
|
||||
.svn
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear on external disk
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
|
||||
######################
|
||||
# Directories
|
||||
######################
|
||||
/bin/
|
||||
/deploy/
|
||||
|
||||
######################
|
||||
# Logs
|
||||
######################
|
||||
*.log*
|
||||
|
||||
######################
|
||||
# Others
|
||||
######################
|
||||
*.class
|
||||
*.*~
|
||||
*~
|
||||
.merge_file*
|
||||
|
||||
######################
|
||||
# Gradle Wrapper
|
||||
######################
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
######################
|
||||
# Maven Wrapper
|
||||
######################
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
######################
|
||||
# ESLint
|
||||
######################
|
||||
.eslintcache
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"name": "Quote",
|
||||
"fields": [
|
||||
{
|
||||
"fieldName": "symbol",
|
||||
"fieldType": "String",
|
||||
"fieldValidateRules": [
|
||||
"required",
|
||||
"unique"
|
||||
]
|
||||
},
|
||||
{
|
||||
"fieldName": "price",
|
||||
"fieldType": "BigDecimal",
|
||||
"fieldValidateRules": [
|
||||
"required"
|
||||
]
|
||||
},
|
||||
{
|
||||
"fieldName": "lastTrade",
|
||||
"fieldType": "ZonedDateTime",
|
||||
"fieldValidateRules": [
|
||||
"required"
|
||||
]
|
||||
}
|
||||
],
|
||||
"relationships": [],
|
||||
"changelogDate": "20181020145525",
|
||||
"entityTableName": "quote",
|
||||
"dto": "mapstruct",
|
||||
"pagination": "pagination",
|
||||
"service": "serviceImpl",
|
||||
"jpaMetamodelFiltering": true,
|
||||
"fluentMethods": true,
|
||||
"clientRootFolder": "quotes",
|
||||
"applications": "*",
|
||||
"microserviceName": "quotes"
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
import java.nio.channels.*;
|
||||
import java.util.Properties;
|
||||
|
||||
public class MavenWrapperDownloader {
|
||||
|
||||
/**
|
||||
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
|
||||
*/
|
||||
private static final String DEFAULT_DOWNLOAD_URL =
|
||||
"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
|
||||
|
||||
/**
|
||||
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
|
||||
* use instead of the default one.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
|
||||
".mvn/wrapper/maven-wrapper.properties";
|
||||
|
||||
/**
|
||||
* Path where the maven-wrapper.jar will be saved to.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_JAR_PATH =
|
||||
".mvn/wrapper/maven-wrapper.jar";
|
||||
|
||||
/**
|
||||
* Name of the property which should be used to override the default download url for the wrapper.
|
||||
*/
|
||||
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("- Downloader started");
|
||||
File baseDirectory = new File(args[0]);
|
||||
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
|
||||
|
||||
// If the maven-wrapper.properties exists, read it and check if it contains a custom
|
||||
// wrapperUrl parameter.
|
||||
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
|
||||
String url = DEFAULT_DOWNLOAD_URL;
|
||||
if(mavenWrapperPropertyFile.exists()) {
|
||||
FileInputStream mavenWrapperPropertyFileInputStream = null;
|
||||
try {
|
||||
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
|
||||
Properties mavenWrapperProperties = new Properties();
|
||||
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
|
||||
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
|
||||
} catch (IOException e) {
|
||||
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
|
||||
} finally {
|
||||
try {
|
||||
if(mavenWrapperPropertyFileInputStream != null) {
|
||||
mavenWrapperPropertyFileInputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore ...
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading from: : " + url);
|
||||
|
||||
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
|
||||
if(!outputFile.getParentFile().exists()) {
|
||||
if(!outputFile.getParentFile().mkdirs()) {
|
||||
System.out.println(
|
||||
"- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
|
||||
try {
|
||||
downloadFileFromURL(url, outputFile);
|
||||
System.out.println("Done");
|
||||
System.exit(0);
|
||||
} catch (Throwable e) {
|
||||
System.out.println("- Error downloading");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
|
||||
URL website = new URL(urlString);
|
||||
ReadableByteChannel rbc;
|
||||
rbc = Channels.newChannel(website.openStream());
|
||||
FileOutputStream fos = new FileOutputStream(destination);
|
||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||
fos.close();
|
||||
rbc.close();
|
||||
}
|
||||
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip
|
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
target
|
|
@ -0,0 +1,12 @@
|
|||
# Prettier configuration
|
||||
|
||||
printWidth: 140
|
||||
singleQuote: true
|
||||
tabWidth: 4
|
||||
useTabs: false
|
||||
|
||||
# js and ts rules:
|
||||
arrowParens: avoid
|
||||
|
||||
# jsx and tsx rules:
|
||||
jsxBracketSameLine: false
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"generator-jhipster": {
|
||||
"promptValues": {
|
||||
"packageName": "com.baeldung.jhipster.gateway",
|
||||
"nativeLanguage": "en"
|
||||
},
|
||||
"jhipsterVersion": "5.4.2",
|
||||
"applicationType": "gateway",
|
||||
"baseName": "gateway",
|
||||
"packageName": "com.baeldung.jhipster.gateway",
|
||||
"packageFolder": "com/baeldung/jhipster/gateway",
|
||||
"serverPort": "8080",
|
||||
"authenticationType": "uaa",
|
||||
"uaaBaseName": "uaa",
|
||||
"cacheProvider": "hazelcast",
|
||||
"enableHibernateCache": true,
|
||||
"websocket": false,
|
||||
"databaseType": "sql",
|
||||
"devDatabaseType": "h2Disk",
|
||||
"prodDatabaseType": "mysql",
|
||||
"searchEngine": false,
|
||||
"messageBroker": false,
|
||||
"serviceDiscoveryType": "eureka",
|
||||
"buildTool": "maven",
|
||||
"enableSwaggerCodegen": false,
|
||||
"clientFramework": "angularX",
|
||||
"useSass": true,
|
||||
"clientPackageManager": "npm",
|
||||
"testFrameworks": [],
|
||||
"jhiPrefix": "jhi",
|
||||
"enableTranslation": true,
|
||||
"nativeLanguage": "en",
|
||||
"languages": [
|
||||
"en",
|
||||
"fr",
|
||||
"pt-br"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
# gateway
|
||||
This application was generated using JHipster 5.4.2, you can find documentation and help at [https://www.jhipster.tech/documentation-archive/v5.4.2](https://www.jhipster.tech/documentation-archive/v5.4.2).
|
||||
|
||||
This is a "gateway" application intended to be part of a microservice architecture, please refer to the [Doing microservices with JHipster][] page of the documentation for more information.
|
||||
|
||||
This application is configured for Service Discovery and Configuration with the JHipster-Registry. On launch, it will refuse to start if it is not able to connect to the JHipster-Registry at [http://localhost:8761](http://localhost:8761). For more information, read our documentation on [Service Discovery and Configuration with the JHipster-Registry][].
|
||||
|
||||
## Development
|
||||
|
||||
Before you can build this project, you must install and configure the following dependencies on your machine:
|
||||
|
||||
1. [Node.js][]: We use Node to run a development web server and build the project.
|
||||
Depending on your system, you can install Node either from source or as a pre-packaged bundle.
|
||||
|
||||
After installing Node, you should be able to run the following command to install development tools.
|
||||
You will only need to run this command when dependencies change in [package.json](package.json).
|
||||
|
||||
npm install
|
||||
|
||||
We use npm scripts and [Webpack][] as our build system.
|
||||
|
||||
Run the following commands in two separate terminals to create a blissful development experience where your browser
|
||||
auto-refreshes when files change on your hard drive.
|
||||
|
||||
./mvnw
|
||||
npm start
|
||||
|
||||
Npm is also used to manage CSS and JavaScript dependencies used in this application. You can upgrade dependencies by
|
||||
specifying a newer version in [package.json](package.json). You can also run `npm update` and `npm install` to manage dependencies.
|
||||
Add the `help` flag on any command to see how you can use it. For example, `npm help update`.
|
||||
|
||||
The `npm run` command will list all of the scripts available to run for this project.
|
||||
|
||||
### Service workers
|
||||
|
||||
Service workers are commented by default, to enable them please uncomment the following code.
|
||||
|
||||
* The service worker registering script in index.html
|
||||
|
||||
```html
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker
|
||||
.register('./service-worker.js')
|
||||
.then(function() { console.log('Service Worker Registered'); });
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
Note: workbox creates the respective service worker and dynamically generate the `service-worker.js`
|
||||
|
||||
### Managing dependencies
|
||||
|
||||
For example, to add [Leaflet][] library as a runtime dependency of your application, you would run following command:
|
||||
|
||||
npm install --save --save-exact leaflet
|
||||
|
||||
To benefit from TypeScript type definitions from [DefinitelyTyped][] repository in development, you would run following command:
|
||||
|
||||
npm install --save-dev --save-exact @types/leaflet
|
||||
|
||||
Then you would import the JS and CSS files specified in library's installation instructions so that [Webpack][] knows about them:
|
||||
Edit [src/main/webapp/app/vendor.ts](src/main/webapp/app/vendor.ts) file:
|
||||
~~~
|
||||
import 'leaflet/dist/leaflet.js';
|
||||
~~~
|
||||
|
||||
Edit [src/main/webapp/content/css/vendor.css](src/main/webapp/content/css/vendor.css) file:
|
||||
~~~
|
||||
@import '~leaflet/dist/leaflet.css';
|
||||
~~~
|
||||
Note: there are still few other things remaining to do for Leaflet that we won't detail here.
|
||||
|
||||
For further instructions on how to develop with JHipster, have a look at [Using JHipster in development][].
|
||||
|
||||
### Using angular-cli
|
||||
|
||||
You can also use [Angular CLI][] to generate some custom client code.
|
||||
|
||||
For example, the following command:
|
||||
|
||||
ng generate component my-component
|
||||
|
||||
will generate few files:
|
||||
|
||||
create src/main/webapp/app/my-component/my-component.component.html
|
||||
create src/main/webapp/app/my-component/my-component.component.ts
|
||||
update src/main/webapp/app/app.module.ts
|
||||
|
||||
|
||||
## Building for production
|
||||
|
||||
To optimize the gateway application for production, run:
|
||||
|
||||
./mvnw -Pprod clean package
|
||||
|
||||
This will concatenate and minify the client CSS and JavaScript files. It will also modify `index.html` so it references these new files.
|
||||
To ensure everything worked, run:
|
||||
|
||||
java -jar target/*.war
|
||||
|
||||
Then navigate to [http://localhost:8080](http://localhost:8080) in your browser.
|
||||
|
||||
Refer to [Using JHipster in production][] for more details.
|
||||
|
||||
## Testing
|
||||
|
||||
To launch your application's tests, run:
|
||||
|
||||
./mvnw clean test
|
||||
|
||||
### Client tests
|
||||
|
||||
Unit tests are run by [Jest][] and written with [Jasmine][]. They're located in [src/test/javascript/](src/test/javascript/) and can be run with:
|
||||
|
||||
npm test
|
||||
|
||||
|
||||
|
||||
For more information, refer to the [Running tests page][].
|
||||
|
||||
### Code quality
|
||||
|
||||
Sonar is used to analyse code quality. You can start a local Sonar server (accessible on http://localhost:9001) with:
|
||||
|
||||
```
|
||||
docker-compose -f src/main/docker/sonar.yml up -d
|
||||
```
|
||||
|
||||
Then, run a Sonar analysis:
|
||||
|
||||
```
|
||||
./mvnw -Pprod clean test sonar:sonar
|
||||
```
|
||||
|
||||
For more information, refer to the [Code quality page][].
|
||||
|
||||
## Using Docker to simplify development (optional)
|
||||
|
||||
You can use Docker to improve your JHipster development experience. A number of docker-compose configuration are available in the [src/main/docker](src/main/docker) folder to launch required third party services.
|
||||
|
||||
For example, to start a mysql database in a docker container, run:
|
||||
|
||||
docker-compose -f src/main/docker/mysql.yml up -d
|
||||
|
||||
To stop it and remove the container, run:
|
||||
|
||||
docker-compose -f src/main/docker/mysql.yml down
|
||||
|
||||
You can also fully dockerize your application and all the services that it depends on.
|
||||
To achieve this, first build a docker image of your app by running:
|
||||
|
||||
./mvnw package -Pprod jib:dockerBuild
|
||||
|
||||
Then run:
|
||||
|
||||
docker-compose -f src/main/docker/app.yml up -d
|
||||
|
||||
For more information refer to [Using Docker and Docker-Compose][], this page also contains information on the docker-compose sub-generator (`jhipster docker-compose`), which is able to generate docker configurations for one or several JHipster applications.
|
||||
|
||||
## Continuous Integration (optional)
|
||||
|
||||
To configure CI for your project, run the ci-cd sub-generator (`jhipster ci-cd`), this will let you generate configuration files for a number of Continuous Integration systems. Consult the [Setting up Continuous Integration][] page for more information.
|
||||
|
||||
[JHipster Homepage and latest documentation]: https://www.jhipster.tech
|
||||
[JHipster 5.4.2 archive]: https://www.jhipster.tech/documentation-archive/v5.4.2
|
||||
[Doing microservices with JHipster]: https://www.jhipster.tech/documentation-archive/v5.4.2/microservices-architecture/
|
||||
[Using JHipster in development]: https://www.jhipster.tech/documentation-archive/v5.4.2/development/
|
||||
[Service Discovery and Configuration with the JHipster-Registry]: https://www.jhipster.tech/documentation-archive/v5.4.2/microservices-architecture/#jhipster-registry
|
||||
[Using Docker and Docker-Compose]: https://www.jhipster.tech/documentation-archive/v5.4.2/docker-compose
|
||||
[Using JHipster in production]: https://www.jhipster.tech/documentation-archive/v5.4.2/production/
|
||||
[Running tests page]: https://www.jhipster.tech/documentation-archive/v5.4.2/running-tests/
|
||||
[Code quality page]: https://www.jhipster.tech/documentation-archive/v5.4.2/code-quality/
|
||||
[Setting up Continuous Integration]: https://www.jhipster.tech/documentation-archive/v5.4.2/setting-up-ci/
|
||||
|
||||
|
||||
[Node.js]: https://nodejs.org/
|
||||
[Yarn]: https://yarnpkg.org/
|
||||
[Webpack]: https://webpack.github.io/
|
||||
[Angular CLI]: https://cli.angular.io/
|
||||
[BrowserSync]: http://www.browsersync.io/
|
||||
[Jest]: https://facebook.github.io/jest/
|
||||
[Jasmine]: http://jasmine.github.io/2.0/introduction.html
|
||||
[Protractor]: https://angular.github.io/protractor/
|
||||
[Leaflet]: http://leafletjs.com/
|
||||
[DefinitelyTyped]: http://definitelytyped.org/
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"gateway": {
|
||||
"root": "",
|
||||
"sourceRoot": "src/main/webapp",
|
||||
"projectType": "application",
|
||||
"architect": {}
|
||||
}
|
||||
},
|
||||
"defaultProject": "gateway",
|
||||
"cli": {
|
||||
"packageManager": "yarn"
|
||||
},
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"inlineStyle": true,
|
||||
"inlineTemplate": false,
|
||||
"spec": false,
|
||||
"prefix": "jhi",
|
||||
"styleExt": "scss"
|
||||
},
|
||||
"@schematics/angular:directive": {
|
||||
"spec": false,
|
||||
"prefix": "jhi"
|
||||
},
|
||||
"@schematics/angular:guard": {
|
||||
"spec": false
|
||||
},
|
||||
"@schematics/angular:pipe": {
|
||||
"spec": false
|
||||
},
|
||||
"@schematics/angular:service": {
|
||||
"spec": false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,286 @@
|
|||
#!/bin/sh
|
||||
# ----------------------------------------------------------------------------
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Maven2 Start Up Batch script
|
||||
#
|
||||
# Required ENV vars:
|
||||
# ------------------
|
||||
# JAVA_HOME - location of a JDK home dir
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# M2_HOME - location of maven2's installed home dir
|
||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
# e.g. to debug Maven itself, use
|
||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||
|
||||
if [ -f /etc/mavenrc ] ; then
|
||||
. /etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.mavenrc" ] ; then
|
||||
. "$HOME/.mavenrc"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# OS specific support. $var _must_ be set to either true or false.
|
||||
cygwin=false;
|
||||
darwin=false;
|
||||
mingw=false
|
||||
case "`uname`" in
|
||||
CYGWIN*) cygwin=true ;;
|
||||
MINGW*) mingw=true;;
|
||||
Darwin*) darwin=true
|
||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
if [ -x "/usr/libexec/java_home" ]; then
|
||||
export JAVA_HOME="`/usr/libexec/java_home`"
|
||||
else
|
||||
export JAVA_HOME="/Library/Java/Home"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
if [ -r /etc/gentoo-release ] ; then
|
||||
JAVA_HOME=`java-config --jre-home`
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$M2_HOME" ] ; then
|
||||
## resolve links - $0 may be a link to maven's home
|
||||
PRG="$0"
|
||||
|
||||
# need this for relative symlinks
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG="`dirname "$PRG"`/$link"
|
||||
fi
|
||||
done
|
||||
|
||||
saveddir=`pwd`
|
||||
|
||||
M2_HOME=`dirname "$PRG"`/..
|
||||
|
||||
# make it fully qualified
|
||||
M2_HOME=`cd "$M2_HOME" && pwd`
|
||||
|
||||
cd "$saveddir"
|
||||
# echo Using m2 at $M2_HOME
|
||||
fi
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
||||
if $cygwin ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --unix "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
|
||||
fi
|
||||
|
||||
# For Mingw, ensure paths are in UNIX format before anything is touched
|
||||
if $mingw ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME="`(cd "$M2_HOME"; pwd)`"
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
|
||||
# TODO classpath?
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
javaExecutable="`which javac`"
|
||||
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
|
||||
# readlink(1) is not available as standard on Solaris 10.
|
||||
readLink=`which readlink`
|
||||
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
|
||||
if $darwin ; then
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
|
||||
else
|
||||
javaExecutable="`readlink -f \"$javaExecutable\"`"
|
||||
fi
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
|
||||
JAVA_HOME="$javaHome"
|
||||
export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$JAVACMD" ] ; then
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
else
|
||||
JAVACMD="`which java`"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
echo "Error: JAVA_HOME is not defined correctly." >&2
|
||||
echo " We cannot execute $JAVACMD" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
echo "Warning: JAVA_HOME environment variable is not set."
|
||||
fi
|
||||
|
||||
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
|
||||
|
||||
# traverses directory structure from process work directory to filesystem root
|
||||
# first directory with .mvn subdirectory is considered project base directory
|
||||
find_maven_basedir() {
|
||||
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
echo "Path not specified to find_maven_basedir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
basedir="$1"
|
||||
wdir="$1"
|
||||
while [ "$wdir" != '/' ] ; do
|
||||
if [ -d "$wdir"/.mvn ] ; then
|
||||
basedir=$wdir
|
||||
break
|
||||
fi
|
||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||
if [ -d "${wdir}" ]; then
|
||||
wdir=`cd "$wdir/.."; pwd`
|
||||
fi
|
||||
# end of workaround
|
||||
done
|
||||
echo "${basedir}"
|
||||
}
|
||||
|
||||
# concatenates all lines of a file
|
||||
concat_lines() {
|
||||
if [ -f "$1" ]; then
|
||||
echo "$(tr -s '\n' ' ' < "$1")"
|
||||
fi
|
||||
}
|
||||
|
||||
BASE_DIR=`find_maven_basedir "$(pwd)"`
|
||||
if [ -z "$BASE_DIR" ]; then
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
##########################################################################################
|
||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
##########################################################################################
|
||||
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found .mvn/wrapper/maven-wrapper.jar"
|
||||
fi
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
||||
fi
|
||||
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
|
||||
while IFS="=" read key value; do
|
||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
|
||||
esac
|
||||
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Downloading from: $jarUrl"
|
||||
fi
|
||||
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
|
||||
|
||||
if command -v wget > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found wget ... using wget"
|
||||
fi
|
||||
wget "$jarUrl" -O "$wrapperJarPath"
|
||||
elif command -v curl > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found curl ... using curl"
|
||||
fi
|
||||
curl -o "$wrapperJarPath" "$jarUrl"
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Falling back to using Java to download"
|
||||
fi
|
||||
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||
if [ -e "$javaClass" ]; then
|
||||
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Compiling MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
# Compiling the Java class
|
||||
("$JAVA_HOME/bin/javac" "$javaClass")
|
||||
fi
|
||||
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
# Running the downloader
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Running MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
##########################################################################################
|
||||
# End of extension
|
||||
##########################################################################################
|
||||
|
||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo $MAVEN_PROJECTBASEDIR
|
||||
fi
|
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --path --windows "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
|
||||
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
|
||||
fi
|
||||
|
||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
exec "$JAVACMD" \
|
||||
$MAVEN_OPTS \
|
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
|
@ -0,0 +1,161 @@
|
|||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Maven2 Start Up Batch script
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM M2_HOME - location of maven2's installed home dir
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@REM e.g. to debug Maven itself, use
|
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||
@echo off
|
||||
@REM set title of command window
|
||||
title %0
|
||||
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
|
||||
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||
@setlocal
|
||||
|
||||
@REM ==== START VALIDATION ====
|
||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME not found in your environment. >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:OkJHome
|
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
@REM ==== END VALIDATION ====
|
||||
|
||||
:init
|
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||
@REM Fallback to current working directory if not found.
|
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||
|
||||
set EXEC_DIR=%CD%
|
||||
set WDIR=%EXEC_DIR%
|
||||
:findBaseDir
|
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||
cd ..
|
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||
set WDIR=%CD%
|
||||
goto findBaseDir
|
||||
|
||||
:baseDirFound
|
||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||
cd "%EXEC_DIR%"
|
||||
goto endDetectBaseDir
|
||||
|
||||
:baseDirNotFound
|
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||
cd "%EXEC_DIR%"
|
||||
|
||||
:endDetectBaseDir
|
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion
|
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||
|
||||
:endReadAdditionalConfig
|
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
|
||||
FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
|
||||
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
|
||||
)
|
||||
|
||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
if exist %WRAPPER_JAR% (
|
||||
echo Found %WRAPPER_JAR%
|
||||
) else (
|
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||
echo Downloading from: %DOWNLOAD_URL%
|
||||
powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
|
||||
echo Finished downloading %WRAPPER_JAR%
|
||||
)
|
||||
@REM End of extension
|
||||
|
||||
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
:error
|
||||
set ERROR_CODE=1
|
||||
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
|
||||
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%" == "on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
|
||||
|
||||
exit /B %ERROR_CODE%
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,131 @@
|
|||
{
|
||||
"name": "gateway",
|
||||
"version": "0.0.0",
|
||||
"description": "Description for gateway",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"cacheDirectories": [
|
||||
"node_modules"
|
||||
],
|
||||
"dependencies": {
|
||||
"@angular/common": "6.1.0",
|
||||
"@angular/compiler": "6.1.0",
|
||||
"@angular/core": "6.1.0",
|
||||
"@angular/forms": "6.1.0",
|
||||
"@angular/platform-browser": "6.1.0",
|
||||
"@angular/platform-browser-dynamic": "6.1.0",
|
||||
"@angular/router": "6.1.0",
|
||||
"@fortawesome/angular-fontawesome": "0.2.0",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.4",
|
||||
"@fortawesome/free-solid-svg-icons": "5.3.1",
|
||||
"@ng-bootstrap/ng-bootstrap": "3.0.0",
|
||||
"bootstrap": "4.1.3",
|
||||
"core-js": "2.5.7",
|
||||
"moment": "2.22.2",
|
||||
"ng-jhipster": "0.5.4",
|
||||
"ngx-cookie": "2.0.1",
|
||||
"ngx-infinite-scroll": "0.5.1",
|
||||
"ngx-webstorage": "2.0.1",
|
||||
"reflect-metadata": "0.1.12",
|
||||
"rxjs": "6.1.0",
|
||||
"rxjs-compat": "6.1.0",
|
||||
"swagger-ui": "2.2.10",
|
||||
"tslib": "1.9.3",
|
||||
"zone.js": "0.8.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/cli": "6.1.2",
|
||||
"@angular/compiler-cli": "6.1.0",
|
||||
"@ngtools/webpack": "6.0.0",
|
||||
"@types/jest": "22.2.3",
|
||||
"@types/node": "9.4.7",
|
||||
"angular-router-loader": "0.8.5",
|
||||
"angular2-template-loader": "0.6.2",
|
||||
"browser-sync": "2.24.6",
|
||||
"browser-sync-webpack-plugin": "2.2.2",
|
||||
"cache-loader": "1.2.2",
|
||||
"codelyzer": "4.2.1",
|
||||
"copy-webpack-plugin": "4.5.1",
|
||||
"css-loader": "0.28.10",
|
||||
"exports-loader": "0.7.0",
|
||||
"file-loader": "1.1.11",
|
||||
"fork-ts-checker-webpack-plugin": "0.4.1",
|
||||
"friendly-errors-webpack-plugin": "1.7.0",
|
||||
"generator-jhipster": "5.4.2",
|
||||
"html-loader": "0.5.5",
|
||||
"html-webpack-plugin": "3.2.0",
|
||||
"husky": "1.1.0",
|
||||
"jest": "22.4.3",
|
||||
"jest-junit": "5.1.0",
|
||||
"jest-preset-angular": "5.2.2",
|
||||
"jest-sonar-reporter": "2.0.0",
|
||||
"lint-staged": "7.3.0",
|
||||
"merge-jsons-webpack-plugin": "1.0.14",
|
||||
"mini-css-extract-plugin": "0.4.2",
|
||||
"moment-locales-webpack-plugin": "1.0.5",
|
||||
"optimize-css-assets-webpack-plugin": "5.0.1",
|
||||
"prettier": "1.11.1",
|
||||
"proxy-middleware": "0.15.0",
|
||||
"raw-loader": "0.5.1",
|
||||
"rimraf": "2.6.1",
|
||||
"simple-progress-webpack-plugin": "1.1.2",
|
||||
"style-loader": "0.20.3",
|
||||
"tapable": "1.0.0",
|
||||
"terser-webpack-plugin": "1.0.0",
|
||||
"thread-loader": "1.1.5",
|
||||
"to-string-loader": "1.1.5",
|
||||
"ts-loader": "4.0.1",
|
||||
"tslint": "5.9.1",
|
||||
"tslint-config-prettier": "1.9.0",
|
||||
"tslint-loader": "3.6.0",
|
||||
"typescript": "2.7.2",
|
||||
"sass": "1.13.0",
|
||||
"sass-loader": "7.1.0",
|
||||
"postcss-loader": "2.1.1",
|
||||
"xml2js": "0.4.19",
|
||||
"webpack": "4.8.0",
|
||||
"webpack-cli": "2.1.3",
|
||||
"webpack-dev-server": "3.1.5",
|
||||
"webpack-merge": "4.1.2",
|
||||
"webpack-notifier": "1.6.0",
|
||||
"webpack-visualizer-plugin": "0.1.11",
|
||||
"workbox-webpack-plugin": "3.2.0",
|
||||
"write-file-webpack-plugin": "4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"src/**/*.{ts,css,scss}": [
|
||||
"prettier --write",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"prettier:format": "prettier --write \"src/**/*.{ts,css,scss}\"",
|
||||
"lint": "tslint --project tsconfig.json -e 'node_modules/**'",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"ngc": "ngc -p tsconfig-aot.json",
|
||||
"cleanup": "rimraf target/{aot,www}",
|
||||
"clean-www": "rimraf target//www/app/{src,target/}",
|
||||
"start": "npm run webpack:dev",
|
||||
"start-tls": "npm run webpack:dev -- --env.tls",
|
||||
"serve": "npm run start",
|
||||
"build": "npm run webpack:prod",
|
||||
"test": "npm run lint && jest --coverage --logHeapUsage -w=2 --config src/test/javascript/jest.conf.js",
|
||||
"test:watch": "npm test -- --watch --clearCache",
|
||||
"webpack:dev": "npm run webpack-dev-server -- --config webpack/webpack.dev.js --inline --hot --port=9060 --watch-content-base --env.stats=minimal",
|
||||
"webpack:dev-verbose": "npm run webpack-dev-server -- --config webpack/webpack.dev.js --inline --hot --port=9060 --watch-content-base --profile --progress --env.stats=normal",
|
||||
"webpack:build:main": "npm run webpack -- --config webpack/webpack.dev.js --env.stats=normal",
|
||||
"webpack:build": "npm run cleanup && npm run webpack:build:main",
|
||||
"webpack:prod:main": "npm run webpack -- --config webpack/webpack.prod.js --profile",
|
||||
"webpack:prod": "npm run cleanup && npm run webpack:prod:main && npm run clean-www",
|
||||
"webpack:test": "npm run test",
|
||||
"webpack-dev-server": "node --max_old_space_size=4096 node_modules/webpack-dev-server/bin/webpack-dev-server.js",
|
||||
"webpack": "node --max_old_space_size=4096 node_modules/webpack/bin/webpack.js"
|
||||
},
|
||||
"jestSonar": {
|
||||
"reportPath": "target/test-results/jest",
|
||||
"reportFile": "TESTS-results-sonar.xml"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
plugins: []
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"*": {
|
||||
"target": "http://localhost:8080",
|
||||
"secure": false,
|
||||
"loglevel": "debug"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
# https://docs.docker.com/engine/reference/builder/#dockerignore-file
|
||||
classes/
|
||||
generated-sources/
|
||||
generated-test-sources/
|
||||
h2db/
|
||||
maven-archiver/
|
||||
maven-status/
|
||||
reports/
|
||||
surefire-reports/
|
||||
test-classes/
|
||||
test-results/
|
||||
www/
|
||||
!*.jar
|
||||
!*.war
|
|
@ -0,0 +1,20 @@
|
|||
FROM openjdk:8-jre-alpine
|
||||
|
||||
ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \
|
||||
JHIPSTER_SLEEP=0 \
|
||||
JAVA_OPTS=""
|
||||
|
||||
# Add a jhipster user to run our application so that it doesn't need to run as root
|
||||
RUN adduser -D -s /bin/sh jhipster
|
||||
WORKDIR /home/jhipster
|
||||
|
||||
ADD entrypoint.sh entrypoint.sh
|
||||
RUN chmod 755 entrypoint.sh && chown jhipster:jhipster entrypoint.sh
|
||||
USER jhipster
|
||||
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
|
||||
EXPOSE 8080 5701/udp
|
||||
|
||||
ADD *.war app.war
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
version: '2'
|
||||
services:
|
||||
gateway-app:
|
||||
image: gateway
|
||||
environment:
|
||||
# - _JAVA_OPTIONS=-Xmx512m -Xms256m
|
||||
- SPRING_PROFILES_ACTIVE=prod,swagger
|
||||
- EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://admin:$${jhipster.registry.password}@jhipster-registry:8761/eureka
|
||||
- SPRING_CLOUD_CONFIG_URI=http://admin:$${jhipster.registry.password}@jhipster-registry:8761/config
|
||||
- SPRING_DATASOURCE_URL=jdbc:mysql://gateway-mysql:3306/gateway?useUnicode=true&characterEncoding=utf8&useSSL=false
|
||||
- JHIPSTER_SLEEP=30 # gives time for the JHipster Registry to boot before the application
|
||||
ports:
|
||||
- 8080:8080
|
||||
gateway-mysql:
|
||||
extends:
|
||||
file: mysql.yml
|
||||
service: gateway-mysql
|
||||
jhipster-registry:
|
||||
extends:
|
||||
file: jhipster-registry.yml
|
||||
service: jhipster-registry
|
||||
environment:
|
||||
- SPRING_CLOUD_CONFIG_SERVER_COMPOSITE_0_TYPE=native
|
||||
- SPRING_CLOUD_CONFIG_SERVER_COMPOSITE_0_SEARCH_LOCATIONS=file:./central-config/docker-config/
|
|
@ -0,0 +1,7 @@
|
|||
# Central configuration sources details
|
||||
|
||||
The JHipster-Registry will use the following directories as its configuration source :
|
||||
- localhost-config : when running the registry in docker with the jhipster-registry.yml docker-compose file
|
||||
- docker-config : when running the registry and the app both in docker with the app.yml docker-compose file
|
||||
|
||||
For more info, refer to https://www.jhipster.tech/microservices-architecture/#registry_app_configuration
|
|
@ -0,0 +1,15 @@
|
|||
# Common configuration shared between all applications
|
||||
configserver:
|
||||
name: Docker JHipster Registry
|
||||
status: Connected to the JHipster Registry running in Docker
|
||||
|
||||
jhipster:
|
||||
security:
|
||||
authentication:
|
||||
jwt:
|
||||
secret: my-secret-key-which-should-be-changed-in-production-and-be-base64-encoded
|
||||
|
||||
eureka:
|
||||
client:
|
||||
service-url:
|
||||
defaultZone: http://admin:${jhipster.registry.password}@jhipster-registry:8761/eureka/
|
|
@ -0,0 +1,15 @@
|
|||
# Common configuration shared between all applications
|
||||
configserver:
|
||||
name: Docker JHipster Registry
|
||||
status: Connected to the JHipster Registry running in Docker
|
||||
|
||||
jhipster:
|
||||
security:
|
||||
authentication:
|
||||
jwt:
|
||||
secret: my-secret-key-which-should-be-changed-in-production-and-be-base64-encoded
|
||||
|
||||
eureka:
|
||||
client:
|
||||
service-url:
|
||||
defaultZone: http://admin:${jhipster.registry.password}@localhost:8761/eureka/
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "The application will start in ${JHIPSTER_SLEEP}s..." && sleep ${JHIPSTER_SLEEP}
|
||||
exec java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar "${HOME}/app.war" "$@"
|
|
@ -0,0 +1,6 @@
|
|||
version: '2'
|
||||
services:
|
||||
gateway-hazelcast-management-center:
|
||||
image: hazelcast/management-center:3.9.3
|
||||
ports:
|
||||
- 8180:8080
|
|
@ -0,0 +1,22 @@
|
|||
version: '2'
|
||||
services:
|
||||
jhipster-registry:
|
||||
image: jhipster/jhipster-registry:v4.0.4
|
||||
volumes:
|
||||
- ./central-server-config:/central-config
|
||||
# When run with the "dev" Spring profile, the JHipster Registry will
|
||||
# read the config from the local filesystem (central-server-config directory)
|
||||
# When run with the "prod" Spring profile, it will read the configuration from a Git repository
|
||||
# See https://www.jhipster.tech/microservices-architecture/#registry_app_configuration
|
||||
environment:
|
||||
# - _JAVA_OPTIONS=-Xmx512m -Xms256m
|
||||
- SPRING_PROFILES_ACTIVE=dev,swagger,uaa
|
||||
- SPRING_SECURITY_USER_PASSWORD=admin
|
||||
- JHIPSTER_REGISTRY_PASSWORD=admin
|
||||
- SPRING_CLOUD_CONFIG_SERVER_COMPOSITE_0_TYPE=native
|
||||
- SPRING_CLOUD_CONFIG_SERVER_COMPOSITE_0_SEARCH_LOCATIONS=file:./central-config/localhost-config/
|
||||
# - SPRING_CLOUD_CONFIG_SERVER_COMPOSITE_0_TYPE=git
|
||||
# - SPRING_CLOUD_CONFIG_SERVER_COMPOSITE_0_URI=https://github.com/jhipster/jhipster-registry/
|
||||
# - SPRING_CLOUD_CONFIG_SERVER_COMPOSITE_0_SEARCH_PATHS=central-config
|
||||
ports:
|
||||
- 8761:8761
|
|
@ -0,0 +1,13 @@
|
|||
version: '2'
|
||||
services:
|
||||
gateway-mysql:
|
||||
image: mysql:5.7.20
|
||||
# volumes:
|
||||
# - ~/volumes/jhipster/gateway/mysql/:/var/lib/mysql/
|
||||
environment:
|
||||
- MYSQL_USER=root
|
||||
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
|
||||
- MYSQL_DATABASE=gateway
|
||||
ports:
|
||||
- 3306:3306
|
||||
command: mysqld --lower_case_table_names=1 --skip-ssl --character_set_server=utf8mb4 --explicit_defaults_for_timestamp
|
|
@ -0,0 +1,7 @@
|
|||
version: '2'
|
||||
services:
|
||||
gateway-sonar:
|
||||
image: sonarqube:7.1-alpine
|
||||
ports:
|
||||
- 9001:9000
|
||||
- 9092:9092
|
|
@ -0,0 +1,21 @@
|
|||
package com.baeldung.jhipster.gateway;
|
||||
|
||||
import com.baeldung.jhipster.gateway.config.DefaultProfileUtil;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||
|
||||
/**
|
||||
* This is a helper Java class that provides an alternative to creating a web.xml.
|
||||
* This will be invoked only when the application is deployed to a Servlet container like Tomcat, JBoss etc.
|
||||
*/
|
||||
public class ApplicationWebXml extends SpringBootServletInitializer {
|
||||
|
||||
@Override
|
||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
|
||||
/**
|
||||
* set a default to use when no profile is configured.
|
||||
*/
|
||||
DefaultProfileUtil.addDefaultProfile(application.application());
|
||||
return application.sources(GatewayApp.class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
package com.baeldung.jhipster.gateway;
|
||||
|
||||
import com.baeldung.jhipster.gateway.config.ApplicationProperties;
|
||||
import com.baeldung.jhipster.gateway.config.DefaultProfileUtil;
|
||||
|
||||
import io.github.jhipster.config.JHipsterConstants;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableConfigurationProperties({LiquibaseProperties.class, ApplicationProperties.class})
|
||||
@EnableDiscoveryClient
|
||||
@EnableZuulProxy
|
||||
public class GatewayApp {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GatewayApp.class);
|
||||
|
||||
private final Environment env;
|
||||
|
||||
public GatewayApp(Environment env) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes gateway.
|
||||
* <p>
|
||||
* Spring profiles can be configured with a program argument --spring.profiles.active=your-active-profile
|
||||
* <p>
|
||||
* You can find more information on how profiles work with JHipster on <a href="https://www.jhipster.tech/profiles/">https://www.jhipster.tech/profiles/</a>.
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initApplication() {
|
||||
Collection<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
|
||||
if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) {
|
||||
log.error("You have misconfigured your application! It should not run " +
|
||||
"with both the 'dev' and 'prod' profiles at the same time.");
|
||||
}
|
||||
if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_CLOUD)) {
|
||||
log.error("You have misconfigured your application! It should not " +
|
||||
"run with both the 'dev' and 'cloud' profiles at the same time.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method, used to run the application.
|
||||
*
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
SpringApplication app = new SpringApplication(GatewayApp.class);
|
||||
DefaultProfileUtil.addDefaultProfile(app);
|
||||
Environment env = app.run(args).getEnvironment();
|
||||
logApplicationStartup(env);
|
||||
}
|
||||
|
||||
private static void logApplicationStartup(Environment env) {
|
||||
String protocol = "http";
|
||||
if (env.getProperty("server.ssl.key-store") != null) {
|
||||
protocol = "https";
|
||||
}
|
||||
String serverPort = env.getProperty("server.port");
|
||||
String contextPath = env.getProperty("server.servlet.context-path");
|
||||
if (StringUtils.isBlank(contextPath)) {
|
||||
contextPath = "/";
|
||||
}
|
||||
String hostAddress = "localhost";
|
||||
try {
|
||||
hostAddress = InetAddress.getLocalHost().getHostAddress();
|
||||
} catch (UnknownHostException e) {
|
||||
log.warn("The host name could not be determined, using `localhost` as fallback");
|
||||
}
|
||||
log.info("\n----------------------------------------------------------\n\t" +
|
||||
"Application '{}' is running! Access URLs:\n\t" +
|
||||
"Local: \t\t{}://localhost:{}{}\n\t" +
|
||||
"External: \t{}://{}:{}{}\n\t" +
|
||||
"Profile(s): \t{}\n----------------------------------------------------------",
|
||||
env.getProperty("spring.application.name"),
|
||||
protocol,
|
||||
serverPort,
|
||||
contextPath,
|
||||
protocol,
|
||||
hostAddress,
|
||||
serverPort,
|
||||
contextPath,
|
||||
env.getActiveProfiles());
|
||||
|
||||
String configServerStatus = env.getProperty("configserver.status");
|
||||
if (configServerStatus == null) {
|
||||
configServerStatus = "Not found or not setup for this application";
|
||||
}
|
||||
log.info("\n----------------------------------------------------------\n\t" +
|
||||
"Config Server: \t{}\n----------------------------------------------------------", configServerStatus);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package com.baeldung.jhipster.gateway.aop.logging;
|
||||
|
||||
import io.github.jhipster.config.JHipsterConstants;
|
||||
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Aspect for logging execution of service and repository Spring components.
|
||||
*
|
||||
* By default, it only runs with the "dev" profile.
|
||||
*/
|
||||
@Aspect
|
||||
public class LoggingAspect {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private final Environment env;
|
||||
|
||||
public LoggingAspect(Environment env) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pointcut that matches all repositories, services and Web REST endpoints.
|
||||
*/
|
||||
@Pointcut("within(@org.springframework.stereotype.Repository *)" +
|
||||
" || within(@org.springframework.stereotype.Service *)" +
|
||||
" || within(@org.springframework.web.bind.annotation.RestController *)")
|
||||
public void springBeanPointcut() {
|
||||
// Method is empty as this is just a Pointcut, the implementations are in the advices.
|
||||
}
|
||||
|
||||
/**
|
||||
* Pointcut that matches all Spring beans in the application's main packages.
|
||||
*/
|
||||
@Pointcut("within(com.baeldung.jhipster.gateway.repository..*)"+
|
||||
" || within(com.baeldung.jhipster.gateway.service..*)"+
|
||||
" || within(com.baeldung.jhipster.gateway.web.rest..*)")
|
||||
public void applicationPackagePointcut() {
|
||||
// Method is empty as this is just a Pointcut, the implementations are in the advices.
|
||||
}
|
||||
|
||||
/**
|
||||
* Advice that logs methods throwing exceptions.
|
||||
*
|
||||
* @param joinPoint join point for advice
|
||||
* @param e exception
|
||||
*/
|
||||
@AfterThrowing(pointcut = "applicationPackagePointcut() && springBeanPointcut()", throwing = "e")
|
||||
public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
|
||||
if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)) {
|
||||
log.error("Exception in {}.{}() with cause = \'{}\' and exception = \'{}\'", joinPoint.getSignature().getDeclaringTypeName(),
|
||||
joinPoint.getSignature().getName(), e.getCause() != null? e.getCause() : "NULL", e.getMessage(), e);
|
||||
|
||||
} else {
|
||||
log.error("Exception in {}.{}() with cause = {}", joinPoint.getSignature().getDeclaringTypeName(),
|
||||
joinPoint.getSignature().getName(), e.getCause() != null? e.getCause() : "NULL");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Advice that logs when a method is entered and exited.
|
||||
*
|
||||
* @param joinPoint join point for advice
|
||||
* @return result
|
||||
* @throws Throwable throws IllegalArgumentException
|
||||
*/
|
||||
@Around("applicationPackagePointcut() && springBeanPointcut()")
|
||||
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Enter: {}.{}() with argument[s] = {}", joinPoint.getSignature().getDeclaringTypeName(),
|
||||
joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()));
|
||||
}
|
||||
try {
|
||||
Object result = joinPoint.proceed();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Exit: {}.{}() with result = {}", joinPoint.getSignature().getDeclaringTypeName(),
|
||||
joinPoint.getSignature().getName(), result);
|
||||
}
|
||||
return result;
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error("Illegal argument: {} in {}.{}()", Arrays.toString(joinPoint.getArgs()),
|
||||
joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.baeldung.jhipster.gateway.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Properties specific to Gateway.
|
||||
* <p>
|
||||
* Properties are configured in the application.yml file.
|
||||
* See {@link io.github.jhipster.config.JHipsterProperties} for a good example.
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
|
||||
public class ApplicationProperties {
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package com.baeldung.jhipster.gateway.config;
|
||||
|
||||
import io.github.jhipster.async.ExceptionHandlingAsyncTaskExecutor;
|
||||
import io.github.jhipster.config.JHipsterProperties;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.*;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
public class AsyncConfiguration implements AsyncConfigurer, SchedulingConfigurer {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);
|
||||
|
||||
private final JHipsterProperties jHipsterProperties;
|
||||
|
||||
public AsyncConfiguration(JHipsterProperties jHipsterProperties) {
|
||||
this.jHipsterProperties = jHipsterProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Bean(name = "taskExecutor")
|
||||
public Executor getAsyncExecutor() {
|
||||
log.debug("Creating Async Task Executor");
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(jHipsterProperties.getAsync().getCorePoolSize());
|
||||
executor.setMaxPoolSize(jHipsterProperties.getAsync().getMaxPoolSize());
|
||||
executor.setQueueCapacity(jHipsterProperties.getAsync().getQueueCapacity());
|
||||
executor.setThreadNamePrefix("gateway-Executor-");
|
||||
return new ExceptionHandlingAsyncTaskExecutor(executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return new SimpleAsyncUncaughtExceptionHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
taskRegistrar.setScheduler(scheduledTaskExecutor());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Executor scheduledTaskExecutor() {
|
||||
return Executors.newScheduledThreadPool(jHipsterProperties.getAsync().getCorePoolSize());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package com.baeldung.jhipster.gateway.config;
|
||||
|
||||
import io.github.jhipster.config.JHipsterConstants;
|
||||
import io.github.jhipster.config.JHipsterProperties;
|
||||
|
||||
import com.hazelcast.config.*;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.client.serviceregistry.Registration;
|
||||
import org.springframework.context.annotation.*;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class CacheConfiguration {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(CacheConfiguration.class);
|
||||
|
||||
private final Environment env;
|
||||
|
||||
private final ServerProperties serverProperties;
|
||||
|
||||
private final DiscoveryClient discoveryClient;
|
||||
|
||||
private Registration registration;
|
||||
|
||||
public CacheConfiguration(Environment env, ServerProperties serverProperties, DiscoveryClient discoveryClient) {
|
||||
this.env = env;
|
||||
this.serverProperties = serverProperties;
|
||||
this.discoveryClient = discoveryClient;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setRegistration(Registration registration) {
|
||||
this.registration = registration;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
log.info("Closing Cache Manager");
|
||||
Hazelcast.shutdownAll();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CacheManager cacheManager(HazelcastInstance hazelcastInstance) {
|
||||
log.debug("Starting HazelcastCacheManager");
|
||||
CacheManager cacheManager = new com.hazelcast.spring.cache.HazelcastCacheManager(hazelcastInstance);
|
||||
return cacheManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HazelcastInstance hazelcastInstance(JHipsterProperties jHipsterProperties) {
|
||||
log.debug("Configuring Hazelcast");
|
||||
HazelcastInstance hazelCastInstance = Hazelcast.getHazelcastInstanceByName("gateway");
|
||||
if (hazelCastInstance != null) {
|
||||
log.debug("Hazelcast already initialized");
|
||||
return hazelCastInstance;
|
||||
}
|
||||
Config config = new Config();
|
||||
config.setInstanceName("gateway");
|
||||
config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);
|
||||
if (this.registration == null) {
|
||||
log.warn("No discovery service is set up, Hazelcast cannot create a cluster.");
|
||||
} else {
|
||||
// The serviceId is by default the application's name,
|
||||
// see the "spring.application.name" standard Spring property
|
||||
String serviceId = registration.getServiceId();
|
||||
log.debug("Configuring Hazelcast clustering for instanceId: {}", serviceId);
|
||||
// In development, everything goes through 127.0.0.1, with a different port
|
||||
if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)) {
|
||||
log.debug("Application is running with the \"dev\" profile, Hazelcast " +
|
||||
"cluster will only work with localhost instances");
|
||||
|
||||
System.setProperty("hazelcast.local.localAddress", "127.0.0.1");
|
||||
config.getNetworkConfig().setPort(serverProperties.getPort() + 5701);
|
||||
config.getNetworkConfig().getJoin().getTcpIpConfig().setEnabled(true);
|
||||
for (ServiceInstance instance : discoveryClient.getInstances(serviceId)) {
|
||||
String clusterMember = "127.0.0.1:" + (instance.getPort() + 5701);
|
||||
log.debug("Adding Hazelcast (dev) cluster member " + clusterMember);
|
||||
config.getNetworkConfig().getJoin().getTcpIpConfig().addMember(clusterMember);
|
||||
}
|
||||
} else { // Production configuration, one host per instance all using port 5701
|
||||
config.getNetworkConfig().setPort(5701);
|
||||
config.getNetworkConfig().getJoin().getTcpIpConfig().setEnabled(true);
|
||||
for (ServiceInstance instance : discoveryClient.getInstances(serviceId)) {
|
||||
String clusterMember = instance.getHost() + ":5701";
|
||||
log.debug("Adding Hazelcast (prod) cluster member " + clusterMember);
|
||||
config.getNetworkConfig().getJoin().getTcpIpConfig().addMember(clusterMember);
|
||||
}
|
||||
}
|
||||
}
|
||||
config.getMapConfigs().put("default", initializeDefaultMapConfig(jHipsterProperties));
|
||||
|
||||
// Full reference is available at: http://docs.hazelcast.org/docs/management-center/3.9/manual/html/Deploying_and_Starting.html
|
||||
config.setManagementCenterConfig(initializeDefaultManagementCenterConfig(jHipsterProperties));
|
||||
config.getMapConfigs().put("com.baeldung.jhipster.gateway.domain.*", initializeDomainMapConfig(jHipsterProperties));
|
||||
return Hazelcast.newHazelcastInstance(config);
|
||||
}
|
||||
|
||||
private ManagementCenterConfig initializeDefaultManagementCenterConfig(JHipsterProperties jHipsterProperties) {
|
||||
ManagementCenterConfig managementCenterConfig = new ManagementCenterConfig();
|
||||
managementCenterConfig.setEnabled(jHipsterProperties.getCache().getHazelcast().getManagementCenter().isEnabled());
|
||||
managementCenterConfig.setUrl(jHipsterProperties.getCache().getHazelcast().getManagementCenter().getUrl());
|
||||
managementCenterConfig.setUpdateInterval(jHipsterProperties.getCache().getHazelcast().getManagementCenter().getUpdateInterval());
|
||||
return managementCenterConfig;
|
||||
}
|
||||
|
||||
private MapConfig initializeDefaultMapConfig(JHipsterProperties jHipsterProperties) {
|
||||
MapConfig mapConfig = new MapConfig();
|
||||
|
||||
/*
|
||||
Number of backups. If 1 is set as the backup-count for example,
|
||||
then all entries of the map will be copied to another JVM for
|
||||
fail-safety. Valid numbers are 0 (no backup), 1, 2, 3.
|
||||
*/
|
||||
mapConfig.setBackupCount(jHipsterProperties.getCache().getHazelcast().getBackupCount());
|
||||
|
||||
/*
|
||||
Valid values are:
|
||||
NONE (no eviction),
|
||||
LRU (Least Recently Used),
|
||||
LFU (Least Frequently Used).
|
||||
NONE is the default.
|
||||
*/
|
||||
mapConfig.setEvictionPolicy(EvictionPolicy.LRU);
|
||||
|
||||
/*
|
||||
Maximum size of the map. When max size is reached,
|
||||
map is evicted based on the policy defined.
|
||||
Any integer between 0 and Integer.MAX_VALUE. 0 means
|
||||
Integer.MAX_VALUE. Default is 0.
|
||||
*/
|
||||
mapConfig.setMaxSizeConfig(new MaxSizeConfig(0, MaxSizeConfig.MaxSizePolicy.USED_HEAP_SIZE));
|
||||
|
||||
return mapConfig;
|
||||
}
|
||||
|
||||
private MapConfig initializeDomainMapConfig(JHipsterProperties jHipsterProperties) {
|
||||
MapConfig mapConfig = new MapConfig();
|
||||
mapConfig.setTimeToLiveSeconds(jHipsterProperties.getCache().getHazelcast().getTimeToLiveSeconds());
|
||||
return mapConfig;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package com.baeldung.jhipster.gateway.config;
|
||||
|
||||
import io.github.jhipster.config.JHipsterConstants;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cloud.config.java.AbstractCloudConfig;
|
||||
import org.springframework.context.annotation.*;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@Configuration
|
||||
@Profile(JHipsterConstants.SPRING_PROFILE_CLOUD)
|
||||
public class CloudDatabaseConfiguration extends AbstractCloudConfig {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(CloudDatabaseConfiguration.class);
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource(CacheManager cacheManager) {
|
||||
log.info("Configuring JDBC datasource from a cloud provider");
|
||||
return connectionFactory().dataSource();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.baeldung.jhipster.gateway.config;
|
||||
|
||||
/**
|
||||
* Application constants.
|
||||
*/
|
||||
public final class Constants {
|
||||
|
||||
// Regex for acceptable logins
|
||||
public static final String LOGIN_REGEX = "^[_.@A-Za-z0-9-]*$";
|
||||
|
||||
public static final String SYSTEM_ACCOUNT = "system";
|
||||
public static final String ANONYMOUS_USER = "anonymoususer";
|
||||
public static final String DEFAULT_LANGUAGE = "en";
|
||||
|
||||
private Constants() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package com.baeldung.jhipster.gateway.config;
|
||||
|
||||
import io.github.jhipster.config.JHipsterConstants;
|
||||
import io.github.jhipster.config.h2.H2ConfigurationHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
@Configuration
|
||||
@EnableJpaRepositories("com.baeldung.jhipster.gateway.repository")
|
||||
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
|
||||
@EnableTransactionManagement
|
||||
public class DatabaseConfiguration {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(DatabaseConfiguration.class);
|
||||
|
||||
|
||||
/**
|
||||
* Open the TCP port for the H2 database, so it is available remotely.
|
||||
*
|
||||
* @return the H2 database TCP server
|
||||
* @throws SQLException if the server failed to start
|
||||
*/
|
||||
@Bean(initMethod = "start", destroyMethod = "stop")
|
||||
@Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)
|
||||
public Object h2TCPServer() throws SQLException {
|
||||
log.debug("Starting H2 database");
|
||||
return H2ConfigurationHelper.createServer();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.baeldung.jhipster.gateway.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* Configure the converters to use the ISO format for dates by default.
|
||||
*/
|
||||
@Configuration
|
||||
public class DateTimeFormatConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
|
||||
registrar.setUseIsoFormat(true);
|
||||
registrar.registerFormatters(registry);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package com.baeldung.jhipster.gateway.config;
|
||||
|
||||
import io.github.jhipster.config.JHipsterConstants;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Utility class to load a Spring profile to be used as default
|
||||
* when there is no <code>spring.profiles.active</code> set in the environment or as command line argument.
|
||||
* If the value is not available in <code>application.yml</code> then <code>dev</code> profile will be used as default.
|
||||
*/
|
||||
public final class DefaultProfileUtil {
|
||||
|
||||
private static final String SPRING_PROFILE_DEFAULT = "spring.profiles.default";
|
||||
|
||||
private DefaultProfileUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a default to use when no profile is configured.
|
||||
*
|
||||
* @param app the Spring application
|
||||
*/
|
||||
public static void addDefaultProfile(SpringApplication app) {
|
||||
Map<String, Object> defProperties = new HashMap<>();
|
||||
/*
|
||||
* The default profile to use when no other profiles are defined
|
||||
* This cannot be set in the <code>application.yml</code> file.
|
||||
* See https://github.com/spring-projects/spring-boot/issues/1219
|
||||
*/
|
||||
defProperties.put(SPRING_PROFILE_DEFAULT, JHipsterConstants.SPRING_PROFILE_DEVELOPMENT);
|
||||
app.setDefaultProperties(defProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the profiles that are applied else get default profiles.
|
||||
*
|
||||
* @param env spring environment
|
||||
* @return profiles
|
||||
*/
|
||||
public static String[] getActiveProfiles(Environment env) {
|
||||
String[] profiles = env.getActiveProfiles();
|
||||
if (profiles.length == 0) {
|
||||
return env.getDefaultProfiles();
|
||||
}
|
||||
return profiles;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package com.baeldung.jhipster.gateway.config;
|
||||
|
||||
import io.github.jhipster.config.JHipsterProperties;
|
||||
|
||||
import com.baeldung.jhipster.gateway.gateway.ratelimiting.RateLimitingFilter;
|
||||
import com.baeldung.jhipster.gateway.gateway.accesscontrol.AccessControlFilter;
|
||||
import com.baeldung.jhipster.gateway.gateway.responserewriting.SwaggerBasePathRewritingFilter;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class GatewayConfiguration {
|
||||
|
||||
@Configuration
|
||||
public static class SwaggerBasePathRewritingConfiguration {
|
||||
|
||||
@Bean
|
||||
public SwaggerBasePathRewritingFilter swaggerBasePathRewritingFilter(){
|
||||
return new SwaggerBasePathRewritingFilter();
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class AccessControlFilterConfiguration {
|
||||
|
||||
@Bean
|
||||
public AccessControlFilter accessControlFilter(RouteLocator routeLocator, JHipsterProperties jHipsterProperties){
|
||||
return new AccessControlFilter(routeLocator, jHipsterProperties);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the Zuul filter that limits the number of API calls per user.
|
||||
* <p>
|
||||
* This uses Bucket4J to limit the API calls, see {@link com.baeldung.jhipster.gateway.gateway.ratelimiting.RateLimitingFilter}.
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty("jhipster.gateway.rate-limiting.enabled")
|
||||
public static class RateLimitingConfiguration {
|
||||
|
||||
private final JHipsterProperties jHipsterProperties;
|
||||
|
||||
public RateLimitingConfiguration(JHipsterProperties jHipsterProperties) {
|
||||
this.jHipsterProperties = jHipsterProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RateLimitingFilter rateLimitingFilter() {
|
||||
return new RateLimitingFilter(jHipsterProperties);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package com.baeldung.jhipster.gateway.config;
|
||||
|
||||
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
|
||||
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.zalando.problem.ProblemModule;
|
||||
import org.zalando.problem.violations.ConstraintViolationProblemModule;
|
||||
|
||||
@Configuration
|
||||
public class JacksonConfiguration {
|
||||
|
||||
/**
|
||||
* Support for Java date and time API.
|
||||
* @return the corresponding Jackson module.
|
||||
*/
|
||||
@Bean
|
||||
public JavaTimeModule javaTimeModule() {
|
||||
return new JavaTimeModule();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Jdk8Module jdk8TimeModule() {
|
||||
return new Jdk8Module();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Support for Hibernate types in Jackson.
|
||||
*/
|
||||
@Bean
|
||||
public Hibernate5Module hibernate5Module() {
|
||||
return new Hibernate5Module();
|
||||
}
|
||||
|
||||
/*
|
||||
* Jackson Afterburner module to speed up serialization/deserialization.
|
||||
*/
|
||||
@Bean
|
||||
public AfterburnerModule afterburnerModule() {
|
||||
return new AfterburnerModule();
|
||||
}
|
||||
|
||||
/*
|
||||
* Module for serialization/deserialization of RFC7807 Problem.
|
||||
*/
|
||||
@Bean
|
||||
ProblemModule problemModule() {
|
||||
return new ProblemModule();
|
||||
}
|
||||
|
||||
/*
|
||||
* Module for serialization/deserialization of ConstraintViolationProblem.
|
||||
*/
|
||||
@Bean
|
||||
ConstraintViolationProblemModule constraintViolationProblemModule() {
|
||||
return new ConstraintViolationProblemModule();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package com.baeldung.jhipster.gateway.config;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
|
||||
import io.github.jhipster.config.JHipsterConstants;
|
||||
import io.github.jhipster.config.liquibase.AsyncSpringLiquibase;
|
||||
import liquibase.integration.spring.SpringLiquibase;
|
||||
|
||||
@Configuration
|
||||
public class LiquibaseConfiguration {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(LiquibaseConfiguration.class);
|
||||
|
||||
private final Environment env;
|
||||
|
||||
private final CacheManager cacheManager;
|
||||
|
||||
public LiquibaseConfiguration(Environment env, CacheManager cacheManager) {
|
||||
this.env = env;
|
||||
this.cacheManager = cacheManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SpringLiquibase liquibase(@Qualifier("taskExecutor") TaskExecutor taskExecutor,
|
||||
DataSource dataSource, LiquibaseProperties liquibaseProperties) {
|
||||
|
||||
// Use liquibase.integration.spring.SpringLiquibase if you don't want Liquibase to start asynchronously
|
||||
SpringLiquibase liquibase = new AsyncSpringLiquibase(taskExecutor, env);
|
||||
liquibase.setDataSource(dataSource);
|
||||
liquibase.setChangeLog("classpath:config/liquibase/master.xml");
|
||||
liquibase.setContexts(liquibaseProperties.getContexts());
|
||||
liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema());
|
||||
liquibase.setDropFirst(liquibaseProperties.isDropFirst());
|
||||
liquibase.setChangeLogParameters(liquibaseProperties.getParameters());
|
||||
if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_NO_LIQUIBASE)) {
|
||||
liquibase.setShouldRun(false);
|
||||
} else {
|
||||
liquibase.setShouldRun(liquibaseProperties.isEnabled());
|
||||
log.debug("Configuring Liquibase");
|
||||
}
|
||||
return liquibase;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.baeldung.jhipster.gateway.config;
|
||||
|
||||
import io.github.jhipster.config.locale.AngularCookieLocaleResolver;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
import org.springframework.web.servlet.config.annotation.*;
|
||||
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
||||
|
||||
@Configuration
|
||||
public class LocaleConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Bean(name = "localeResolver")
|
||||
public LocaleResolver localeResolver() {
|
||||
AngularCookieLocaleResolver cookieLocaleResolver = new AngularCookieLocaleResolver();
|
||||
cookieLocaleResolver.setCookieName("NG_TRANSLATE_LANG_KEY");
|
||||
return cookieLocaleResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
|
||||
localeChangeInterceptor.setParamName("language");
|
||||
registry.addInterceptor(localeChangeInterceptor);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package com.baeldung.jhipster.gateway.config;
|
||||
|
||||
import com.baeldung.jhipster.gateway.aop.logging.LoggingAspect;
|
||||
|
||||
import io.github.jhipster.config.JHipsterConstants;
|
||||
|
||||
import org.springframework.context.annotation.*;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
public class LoggingAspectConfiguration {
|
||||
|
||||
@Bean
|
||||
@Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)
|
||||
public LoggingAspect loggingAspect(Environment env) {
|
||||
return new LoggingAspect(env);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
package com.baeldung.jhipster.gateway.config;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Iterator;
|
||||
|
||||
import io.github.jhipster.config.JHipsterProperties;
|
||||
|
||||
import ch.qos.logback.classic.AsyncAppender;
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.boolex.OnMarkerEvaluator;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.classic.spi.LoggerContextListener;
|
||||
import ch.qos.logback.core.Appender;
|
||||
import ch.qos.logback.core.filter.EvaluatorFilter;
|
||||
import ch.qos.logback.core.spi.ContextAwareBase;
|
||||
import ch.qos.logback.core.spi.FilterReply;
|
||||
import net.logstash.logback.appender.LogstashTcpSocketAppender;
|
||||
import net.logstash.logback.encoder.LogstashEncoder;
|
||||
import net.logstash.logback.stacktrace.ShortenedThrowableConverter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@RefreshScope
|
||||
public class LoggingConfiguration {
|
||||
|
||||
private static final String LOGSTASH_APPENDER_NAME = "LOGSTASH";
|
||||
|
||||
private static final String ASYNC_LOGSTASH_APPENDER_NAME = "ASYNC_LOGSTASH";
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(LoggingConfiguration.class);
|
||||
|
||||
private LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
|
||||
private final String appName;
|
||||
|
||||
private final String serverPort;
|
||||
|
||||
private final String version;
|
||||
|
||||
private final JHipsterProperties jHipsterProperties;
|
||||
|
||||
public LoggingConfiguration(@Value("${spring.application.name}") String appName, @Value("${server.port}") String serverPort,
|
||||
@Value("${info.project.version:}") String version, JHipsterProperties jHipsterProperties) {
|
||||
this.appName = appName;
|
||||
this.serverPort = serverPort;
|
||||
this.version = version;
|
||||
this.jHipsterProperties = jHipsterProperties;
|
||||
if (jHipsterProperties.getLogging().getLogstash().isEnabled()) {
|
||||
addLogstashAppender(context);
|
||||
addContextListener(context);
|
||||
}
|
||||
if (jHipsterProperties.getMetrics().getLogs().isEnabled()) {
|
||||
setMetricsMarkerLogbackFilter(context);
|
||||
}
|
||||
}
|
||||
|
||||
private void addContextListener(LoggerContext context) {
|
||||
LogbackLoggerContextListener loggerContextListener = new LogbackLoggerContextListener();
|
||||
loggerContextListener.setContext(context);
|
||||
context.addListener(loggerContextListener);
|
||||
}
|
||||
|
||||
private void addLogstashAppender(LoggerContext context) {
|
||||
log.info("Initializing Logstash logging");
|
||||
|
||||
LogstashTcpSocketAppender logstashAppender = new LogstashTcpSocketAppender();
|
||||
logstashAppender.setName(LOGSTASH_APPENDER_NAME);
|
||||
logstashAppender.setContext(context);
|
||||
String optionalFields = "";
|
||||
String customFields = "{\"app_name\":\"" + appName + "\",\"app_port\":\"" + serverPort + "\"," +
|
||||
optionalFields + "\"version\":\"" + version + "\"}";
|
||||
|
||||
// More documentation is available at: https://github.com/logstash/logstash-logback-encoder
|
||||
LogstashEncoder logstashEncoder = new LogstashEncoder();
|
||||
// Set the Logstash appender config from JHipster properties
|
||||
logstashEncoder.setCustomFields(customFields);
|
||||
// Set the Logstash appender config from JHipster properties
|
||||
logstashAppender.addDestinations(new InetSocketAddress(jHipsterProperties.getLogging().getLogstash().getHost(), jHipsterProperties.getLogging().getLogstash().getPort()));
|
||||
|
||||
ShortenedThrowableConverter throwableConverter = new ShortenedThrowableConverter();
|
||||
throwableConverter.setRootCauseFirst(true);
|
||||
logstashEncoder.setThrowableConverter(throwableConverter);
|
||||
logstashEncoder.setCustomFields(customFields);
|
||||
|
||||
logstashAppender.setEncoder(logstashEncoder);
|
||||
logstashAppender.start();
|
||||
|
||||
// Wrap the appender in an Async appender for performance
|
||||
AsyncAppender asyncLogstashAppender = new AsyncAppender();
|
||||
asyncLogstashAppender.setContext(context);
|
||||
asyncLogstashAppender.setName(ASYNC_LOGSTASH_APPENDER_NAME);
|
||||
asyncLogstashAppender.setQueueSize(jHipsterProperties.getLogging().getLogstash().getQueueSize());
|
||||
asyncLogstashAppender.addAppender(logstashAppender);
|
||||
asyncLogstashAppender.start();
|
||||
|
||||
context.getLogger("ROOT").addAppender(asyncLogstashAppender);
|
||||
}
|
||||
|
||||
// Configure a log filter to remove "metrics" logs from all appenders except the "LOGSTASH" appender
|
||||
private void setMetricsMarkerLogbackFilter(LoggerContext context) {
|
||||
log.info("Filtering metrics logs from all appenders except the {} appender", LOGSTASH_APPENDER_NAME);
|
||||
OnMarkerEvaluator onMarkerMetricsEvaluator = new OnMarkerEvaluator();
|
||||
onMarkerMetricsEvaluator.setContext(context);
|
||||
onMarkerMetricsEvaluator.addMarker("metrics");
|
||||
onMarkerMetricsEvaluator.start();
|
||||
EvaluatorFilter<ILoggingEvent> metricsFilter = new EvaluatorFilter<>();
|
||||
metricsFilter.setContext(context);
|
||||
metricsFilter.setEvaluator(onMarkerMetricsEvaluator);
|
||||
metricsFilter.setOnMatch(FilterReply.DENY);
|
||||
metricsFilter.start();
|
||||
|
||||
for (ch.qos.logback.classic.Logger logger : context.getLoggerList()) {
|
||||
for (Iterator<Appender<ILoggingEvent>> it = logger.iteratorForAppenders(); it.hasNext();) {
|
||||
Appender<ILoggingEvent> appender = it.next();
|
||||
if (!appender.getName().equals(ASYNC_LOGSTASH_APPENDER_NAME)) {
|
||||
log.debug("Filter metrics logs from the {} appender", appender.getName());
|
||||
appender.setContext(context);
|
||||
appender.addFilter(metricsFilter);
|
||||
appender.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logback configuration is achieved by configuration file and API.
|
||||
* When configuration file change is detected, the configuration is reset.
|
||||
* This listener ensures that the programmatic configuration is also re-applied after reset.
|
||||
*/
|
||||
class LogbackLoggerContextListener extends ContextAwareBase implements LoggerContextListener {
|
||||
|
||||
@Override
|
||||
public boolean isResetResistant() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(LoggerContext context) {
|
||||
addLogstashAppender(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReset(LoggerContext context) {
|
||||
addLogstashAppender(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(LoggerContext context) {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLevelChange(ch.qos.logback.classic.Logger logger, Level level) {
|
||||
// Nothing to do.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package com.baeldung.jhipster.gateway.config;
|
||||
|
||||
import io.github.jhipster.config.JHipsterProperties;
|
||||
|
||||
import com.codahale.metrics.JmxReporter;
|
||||
import com.codahale.metrics.JvmAttributeGaugeSet;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.Slf4jReporter;
|
||||
import com.codahale.metrics.health.HealthCheckRegistry;
|
||||
import com.codahale.metrics.jvm.*;
|
||||
import com.ryantenney.metrics.spring.config.annotation.EnableMetrics;
|
||||
import com.ryantenney.metrics.spring.config.annotation.MetricsConfigurerAdapter;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.*;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Configuration
|
||||
@EnableMetrics(proxyTargetClass = true)
|
||||
public class MetricsConfiguration extends MetricsConfigurerAdapter {
|
||||
|
||||
private static final String PROP_METRIC_REG_JVM_MEMORY = "jvm.memory";
|
||||
private static final String PROP_METRIC_REG_JVM_GARBAGE = "jvm.garbage";
|
||||
private static final String PROP_METRIC_REG_JVM_THREADS = "jvm.threads";
|
||||
private static final String PROP_METRIC_REG_JVM_FILES = "jvm.files";
|
||||
private static final String PROP_METRIC_REG_JVM_BUFFERS = "jvm.buffers";
|
||||
private static final String PROP_METRIC_REG_JVM_ATTRIBUTE_SET = "jvm.attributes";
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(MetricsConfiguration.class);
|
||||
|
||||
private MetricRegistry metricRegistry = new MetricRegistry();
|
||||
|
||||
private HealthCheckRegistry healthCheckRegistry = new HealthCheckRegistry();
|
||||
|
||||
private final JHipsterProperties jHipsterProperties;
|
||||
|
||||
private HikariDataSource hikariDataSource;
|
||||
|
||||
public MetricsConfiguration(JHipsterProperties jHipsterProperties) {
|
||||
this.jHipsterProperties = jHipsterProperties;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setHikariDataSource(HikariDataSource hikariDataSource) {
|
||||
this.hikariDataSource = hikariDataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
public MetricRegistry getMetricRegistry() {
|
||||
return metricRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
public HealthCheckRegistry getHealthCheckRegistry() {
|
||||
return healthCheckRegistry;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
log.debug("Registering JVM gauges");
|
||||
metricRegistry.register(PROP_METRIC_REG_JVM_MEMORY, new MemoryUsageGaugeSet());
|
||||
metricRegistry.register(PROP_METRIC_REG_JVM_GARBAGE, new GarbageCollectorMetricSet());
|
||||
metricRegistry.register(PROP_METRIC_REG_JVM_THREADS, new ThreadStatesGaugeSet());
|
||||
metricRegistry.register(PROP_METRIC_REG_JVM_FILES, new FileDescriptorRatioGauge());
|
||||
metricRegistry.register(PROP_METRIC_REG_JVM_BUFFERS, new BufferPoolMetricSet(ManagementFactory.getPlatformMBeanServer()));
|
||||
metricRegistry.register(PROP_METRIC_REG_JVM_ATTRIBUTE_SET, new JvmAttributeGaugeSet());
|
||||
if (hikariDataSource != null) {
|
||||
log.debug("Monitoring the datasource");
|
||||
// remove the factory created by HikariDataSourceMetricsPostProcessor until JHipster migrate to Micrometer
|
||||
hikariDataSource.setMetricsTrackerFactory(null);
|
||||
hikariDataSource.setMetricRegistry(metricRegistry);
|
||||
}
|
||||
if (jHipsterProperties.getMetrics().getJmx().isEnabled()) {
|
||||
log.debug("Initializing Metrics JMX reporting");
|
||||
JmxReporter jmxReporter = JmxReporter.forRegistry(metricRegistry).build();
|
||||
jmxReporter.start();
|
||||
}
|
||||
if (jHipsterProperties.getMetrics().getLogs().isEnabled()) {
|
||||
log.info("Initializing Metrics Log reporting");
|
||||
Marker metricsMarker = MarkerFactory.getMarker("metrics");
|
||||
final Slf4jReporter reporter = Slf4jReporter.forRegistry(metricRegistry)
|
||||
.outputTo(LoggerFactory.getLogger("metrics"))
|
||||
.markWith(metricsMarker)
|
||||
.convertRatesTo(TimeUnit.SECONDS)
|
||||
.convertDurationsTo(TimeUnit.MILLISECONDS)
|
||||
.build();
|
||||
reporter.start(jHipsterProperties.getMetrics().getLogs().getReportFrequency(), TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package com.baeldung.jhipster.gateway.config;
|
||||
|
||||
import com.baeldung.jhipster.gateway.config.oauth2.OAuth2JwtAccessTokenConverter;
|
||||
import com.baeldung.jhipster.gateway.config.oauth2.OAuth2Properties;
|
||||
import com.baeldung.jhipster.gateway.security.oauth2.OAuth2SignatureVerifierClient;
|
||||
import com.baeldung.jhipster.gateway.security.AuthoritiesConstants;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
|
||||
import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
|
||||
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
|
||||
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
|
||||
import org.springframework.security.web.csrf.CsrfFilter;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Configuration
|
||||
@EnableResourceServer
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
|
||||
public class SecurityConfiguration extends ResourceServerConfigurerAdapter {
|
||||
private final OAuth2Properties oAuth2Properties;
|
||||
|
||||
private final CorsFilter corsFilter;
|
||||
|
||||
public SecurityConfiguration(OAuth2Properties oAuth2Properties, CorsFilter corsFilter) {
|
||||
this.oAuth2Properties = oAuth2Properties;
|
||||
this.corsFilter = corsFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf()
|
||||
.ignoringAntMatchers("/h2-console/**")
|
||||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
||||
.and()
|
||||
.addFilterBefore(corsFilter, CsrfFilter.class)
|
||||
.headers()
|
||||
.frameOptions()
|
||||
.disable()
|
||||
.and()
|
||||
.sessionManagement()
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/api/**").authenticated()
|
||||
.antMatchers("/management/health").permitAll()
|
||||
.antMatchers("/management/info").permitAll()
|
||||
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
|
||||
return new JwtTokenStore(jwtAccessTokenConverter);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtAccessTokenConverter jwtAccessTokenConverter(OAuth2SignatureVerifierClient signatureVerifierClient) {
|
||||
return new OAuth2JwtAccessTokenConverter(oAuth2Properties, signatureVerifierClient);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Qualifier("loadBalancedRestTemplate")
|
||||
public RestTemplate loadBalancedRestTemplate(RestTemplateCustomizer customizer) {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
customizer.customize(restTemplate);
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Qualifier("vanillaRestTemplate")
|
||||
public RestTemplate vanillaRestTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
package com.baeldung.jhipster.gateway.config;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.servlet.InstrumentedFilter;
|
||||
import com.codahale.metrics.servlets.MetricsServlet;
|
||||
import io.github.jhipster.config.JHipsterConstants;
|
||||
import io.github.jhipster.config.JHipsterProperties;
|
||||
import io.github.jhipster.config.h2.H2ConfigurationHelper;
|
||||
import io.github.jhipster.web.filter.CachingHttpHeadersFilter;
|
||||
import io.undertow.UndertowOptions;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
|
||||
import org.springframework.boot.web.server.*;
|
||||
import org.springframework.boot.web.servlet.ServletContextInitializer;
|
||||
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
import javax.servlet.*;
|
||||
import java.io.File;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
|
||||
import static java.net.URLDecoder.decode;
|
||||
|
||||
/**
|
||||
* Configuration of web application with Servlet 3.0 APIs.
|
||||
*/
|
||||
@Configuration
|
||||
public class WebConfigurer implements ServletContextInitializer, WebServerFactoryCustomizer<WebServerFactory> {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(WebConfigurer.class);
|
||||
|
||||
private final Environment env;
|
||||
|
||||
private final JHipsterProperties jHipsterProperties;
|
||||
|
||||
private MetricRegistry metricRegistry;
|
||||
|
||||
public WebConfigurer(Environment env, JHipsterProperties jHipsterProperties) {
|
||||
|
||||
this.env = env;
|
||||
this.jHipsterProperties = jHipsterProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartup(ServletContext servletContext) throws ServletException {
|
||||
if (env.getActiveProfiles().length != 0) {
|
||||
log.info("Web application configuration, using profiles: {}", (Object[]) env.getActiveProfiles());
|
||||
}
|
||||
EnumSet<DispatcherType> disps = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC);
|
||||
initMetrics(servletContext, disps);
|
||||
if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) {
|
||||
initCachingHttpHeadersFilter(servletContext, disps);
|
||||
}
|
||||
if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)) {
|
||||
initH2Console(servletContext);
|
||||
}
|
||||
log.info("Web application fully configured");
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize the Servlet engine: Mime types, the document root, the cache.
|
||||
*/
|
||||
@Override
|
||||
public void customize(WebServerFactory server) {
|
||||
setMimeMappings(server);
|
||||
// When running in an IDE or with ./mvnw spring-boot:run, set location of the static web assets.
|
||||
setLocationForStaticAssets(server);
|
||||
|
||||
/*
|
||||
* Enable HTTP/2 for Undertow - https://twitter.com/ankinson/status/829256167700492288
|
||||
* HTTP/2 requires HTTPS, so HTTP requests will fallback to HTTP/1.1.
|
||||
* See the JHipsterProperties class and your application-*.yml configuration files
|
||||
* for more information.
|
||||
*/
|
||||
if (jHipsterProperties.getHttp().getVersion().equals(JHipsterProperties.Http.Version.V_2_0) &&
|
||||
server instanceof UndertowServletWebServerFactory) {
|
||||
|
||||
((UndertowServletWebServerFactory) server)
|
||||
.addBuilderCustomizers(builder ->
|
||||
builder.setServerOption(UndertowOptions.ENABLE_HTTP2, true));
|
||||
}
|
||||
}
|
||||
|
||||
private void setMimeMappings(WebServerFactory server) {
|
||||
if (server instanceof ConfigurableServletWebServerFactory) {
|
||||
MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT);
|
||||
// IE issue, see https://github.com/jhipster/generator-jhipster/pull/711
|
||||
mappings.add("html", MediaType.TEXT_HTML_VALUE + ";charset=" + StandardCharsets.UTF_8.name().toLowerCase());
|
||||
// CloudFoundry issue, see https://github.com/cloudfoundry/gorouter/issues/64
|
||||
mappings.add("json", MediaType.TEXT_HTML_VALUE + ";charset=" + StandardCharsets.UTF_8.name().toLowerCase());
|
||||
ConfigurableServletWebServerFactory servletWebServer = (ConfigurableServletWebServerFactory) server;
|
||||
servletWebServer.setMimeMappings(mappings);
|
||||
}
|
||||
}
|
||||
|
||||
private void setLocationForStaticAssets(WebServerFactory server) {
|
||||
if (server instanceof ConfigurableServletWebServerFactory) {
|
||||
ConfigurableServletWebServerFactory servletWebServer = (ConfigurableServletWebServerFactory) server;
|
||||
File root;
|
||||
String prefixPath = resolvePathPrefix();
|
||||
root = new File(prefixPath + "target/www/");
|
||||
if (root.exists() && root.isDirectory()) {
|
||||
servletWebServer.setDocumentRoot(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve path prefix to static resources.
|
||||
*/
|
||||
private String resolvePathPrefix() {
|
||||
String fullExecutablePath;
|
||||
try {
|
||||
fullExecutablePath = decode(this.getClass().getResource("").getPath(), StandardCharsets.UTF_8.name());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
/* try without decoding if this ever happens */
|
||||
fullExecutablePath = this.getClass().getResource("").getPath();
|
||||
}
|
||||
String rootPath = Paths.get(".").toUri().normalize().getPath();
|
||||
String extractedPath = fullExecutablePath.replace(rootPath, "");
|
||||
int extractionEndIndex = extractedPath.indexOf("target/");
|
||||
if (extractionEndIndex <= 0) {
|
||||
return "";
|
||||
}
|
||||
return extractedPath.substring(0, extractionEndIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the caching HTTP Headers Filter.
|
||||
*/
|
||||
private void initCachingHttpHeadersFilter(ServletContext servletContext,
|
||||
EnumSet<DispatcherType> disps) {
|
||||
log.debug("Registering Caching HTTP Headers Filter");
|
||||
FilterRegistration.Dynamic cachingHttpHeadersFilter =
|
||||
servletContext.addFilter("cachingHttpHeadersFilter",
|
||||
new CachingHttpHeadersFilter(jHipsterProperties));
|
||||
|
||||
cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/i18n/*");
|
||||
cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/content/*");
|
||||
cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/app/*");
|
||||
cachingHttpHeadersFilter.setAsyncSupported(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes Metrics.
|
||||
*/
|
||||
private void initMetrics(ServletContext servletContext, EnumSet<DispatcherType> disps) {
|
||||
log.debug("Initializing Metrics registries");
|
||||
servletContext.setAttribute(InstrumentedFilter.REGISTRY_ATTRIBUTE,
|
||||
metricRegistry);
|
||||
servletContext.setAttribute(MetricsServlet.METRICS_REGISTRY,
|
||||
metricRegistry);
|
||||
|
||||
log.debug("Registering Metrics Filter");
|
||||
FilterRegistration.Dynamic metricsFilter = servletContext.addFilter("webappMetricsFilter",
|
||||
new InstrumentedFilter());
|
||||
|
||||
metricsFilter.addMappingForUrlPatterns(disps, true, "/*");
|
||||
metricsFilter.setAsyncSupported(true);
|
||||
|
||||
log.debug("Registering Metrics Servlet");
|
||||
ServletRegistration.Dynamic metricsAdminServlet =
|
||||
servletContext.addServlet("metricsServlet", new MetricsServlet());
|
||||
|
||||
metricsAdminServlet.addMapping("/management/metrics/*");
|
||||
metricsAdminServlet.setAsyncSupported(true);
|
||||
metricsAdminServlet.setLoadOnStartup(2);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
CorsConfiguration config = jHipsterProperties.getCors();
|
||||
if (config.getAllowedOrigins() != null && !config.getAllowedOrigins().isEmpty()) {
|
||||
log.debug("Registering CORS filter");
|
||||
source.registerCorsConfiguration("/api/**", config);
|
||||
source.registerCorsConfiguration("/management/**", config);
|
||||
source.registerCorsConfiguration("/v2/api-docs", config);
|
||||
source.registerCorsConfiguration("/auth/**", config);
|
||||
source.registerCorsConfiguration("/*/api/**", config);
|
||||
source.registerCorsConfiguration("/*/management/**", config);
|
||||
source.registerCorsConfiguration("/*/oauth/**", config);
|
||||
}
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes H2 console.
|
||||
*/
|
||||
private void initH2Console(ServletContext servletContext) {
|
||||
log.debug("Initialize H2 console");
|
||||
H2ConfigurationHelper.initH2Console(servletContext);
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setMetricRegistry(MetricRegistry metricRegistry) {
|
||||
this.metricRegistry = metricRegistry;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package com.baeldung.jhipster.gateway.config.apidoc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.github.jhipster.config.JHipsterConstants;
|
||||
|
||||
import org.springframework.context.annotation.*;
|
||||
import org.springframework.cloud.netflix.zuul.filters.Route;
|
||||
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import springfox.documentation.swagger.web.SwaggerResource;
|
||||
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
|
||||
|
||||
/**
|
||||
* Retrieves all registered microservices Swagger resources.
|
||||
*/
|
||||
@Component
|
||||
@Primary
|
||||
@Profile(JHipsterConstants.SPRING_PROFILE_SWAGGER)
|
||||
public class GatewaySwaggerResourcesProvider implements SwaggerResourcesProvider {
|
||||
|
||||
private final RouteLocator routeLocator;
|
||||
|
||||
public GatewaySwaggerResourcesProvider(RouteLocator routeLocator) {
|
||||
this.routeLocator = routeLocator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SwaggerResource> get() {
|
||||
List<SwaggerResource> resources = new ArrayList<>();
|
||||
|
||||
//Add the default swagger resource that correspond to the gateway's own swagger doc
|
||||
resources.add(swaggerResource("default", "/v2/api-docs"));
|
||||
|
||||
//Add the registered microservices swagger docs as additional swagger resources
|
||||
List<Route> routes = routeLocator.getRoutes();
|
||||
routes.forEach(route -> {
|
||||
resources.add(swaggerResource(route.getId(), route.getFullPath().replace("**", "v2/api-docs")));
|
||||
});
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
private SwaggerResource swaggerResource(String name, String location) {
|
||||
SwaggerResource swaggerResource = new SwaggerResource();
|
||||
swaggerResource.setName(name);
|
||||
swaggerResource.setLocation(location);
|
||||
swaggerResource.setSwaggerVersion("2.0");
|
||||
return swaggerResource;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package com.baeldung.jhipster.gateway.config.audit;
|
||||
|
||||
import com.baeldung.jhipster.gateway.domain.PersistentAuditEvent;
|
||||
|
||||
import org.springframework.boot.actuate.audit.AuditEvent;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Component
|
||||
public class AuditEventConverter {
|
||||
|
||||
/**
|
||||
* Convert a list of PersistentAuditEvent to a list of AuditEvent
|
||||
*
|
||||
* @param persistentAuditEvents the list to convert
|
||||
* @return the converted list.
|
||||
*/
|
||||
public List<AuditEvent> convertToAuditEvent(Iterable<PersistentAuditEvent> persistentAuditEvents) {
|
||||
if (persistentAuditEvents == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<AuditEvent> auditEvents = new ArrayList<>();
|
||||
for (PersistentAuditEvent persistentAuditEvent : persistentAuditEvents) {
|
||||
auditEvents.add(convertToAuditEvent(persistentAuditEvent));
|
||||
}
|
||||
return auditEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a PersistentAuditEvent to an AuditEvent
|
||||
*
|
||||
* @param persistentAuditEvent the event to convert
|
||||
* @return the converted list.
|
||||
*/
|
||||
public AuditEvent convertToAuditEvent(PersistentAuditEvent persistentAuditEvent) {
|
||||
if (persistentAuditEvent == null) {
|
||||
return null;
|
||||
}
|
||||
return new AuditEvent(persistentAuditEvent.getAuditEventDate(), persistentAuditEvent.getPrincipal(),
|
||||
persistentAuditEvent.getAuditEventType(), convertDataToObjects(persistentAuditEvent.getData()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal conversion. This is needed to support the current SpringBoot actuator AuditEventRepository interface
|
||||
*
|
||||
* @param data the data to convert
|
||||
* @return a map of String, Object
|
||||
*/
|
||||
public Map<String, Object> convertDataToObjects(Map<String, String> data) {
|
||||
Map<String, Object> results = new HashMap<>();
|
||||
|
||||
if (data != null) {
|
||||
for (Map.Entry<String, String> entry : data.entrySet()) {
|
||||
results.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal conversion. This method will allow to save additional data.
|
||||
* By default, it will save the object as string
|
||||
*
|
||||
* @param data the data to convert
|
||||
* @return a map of String, String
|
||||
*/
|
||||
public Map<String, String> convertDataToStrings(Map<String, Object> data) {
|
||||
Map<String, String> results = new HashMap<>();
|
||||
|
||||
if (data != null) {
|
||||
for (Map.Entry<String, Object> entry : data.entrySet()) {
|
||||
// Extract the data that will be saved.
|
||||
if (entry.getValue() instanceof WebAuthenticationDetails) {
|
||||
WebAuthenticationDetails authenticationDetails = (WebAuthenticationDetails) entry.getValue();
|
||||
results.put("remoteAddress", authenticationDetails.getRemoteAddress());
|
||||
results.put("sessionId", authenticationDetails.getSessionId());
|
||||
} else {
|
||||
results.put(entry.getKey(), Objects.toString(entry.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Audit specific code.
|
||||
*/
|
||||
package com.baeldung.jhipster.gateway.config.audit;
|
|
@ -0,0 +1,80 @@
|
|||
package com.baeldung.jhipster.gateway.config.oauth2;
|
||||
|
||||
import com.baeldung.jhipster.gateway.security.oauth2.CookieTokenExtractor;
|
||||
import com.baeldung.jhipster.gateway.security.oauth2.OAuth2AuthenticationService;
|
||||
import com.baeldung.jhipster.gateway.security.oauth2.OAuth2CookieHelper;
|
||||
import com.baeldung.jhipster.gateway.security.oauth2.OAuth2TokenEndpointClient;
|
||||
import com.baeldung.jhipster.gateway.web.filter.RefreshTokenFilterConfigurer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
|
||||
import org.springframework.security.oauth2.provider.authentication.TokenExtractor;
|
||||
import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||
|
||||
/**
|
||||
* Configures the RefreshFilter refreshing expired OAuth2 token Cookies.
|
||||
*/
|
||||
@Configuration
|
||||
@EnableResourceServer
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
|
||||
public class OAuth2AuthenticationConfiguration extends ResourceServerConfigurerAdapter {
|
||||
private final OAuth2Properties oAuth2Properties;
|
||||
private final OAuth2TokenEndpointClient tokenEndpointClient;
|
||||
private final TokenStore tokenStore;
|
||||
|
||||
public OAuth2AuthenticationConfiguration(OAuth2Properties oAuth2Properties, OAuth2TokenEndpointClient tokenEndpointClient, TokenStore tokenStore) {
|
||||
this.oAuth2Properties = oAuth2Properties;
|
||||
this.tokenEndpointClient = tokenEndpointClient;
|
||||
this.tokenStore = tokenStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.antMatchers("/auth/login").permitAll()
|
||||
.antMatchers("/auth/logout").authenticated()
|
||||
.and()
|
||||
.apply(refreshTokenSecurityConfigurerAdapter());
|
||||
}
|
||||
|
||||
/**
|
||||
* A SecurityConfigurerAdapter to install a servlet filter that refreshes OAuth2 tokens.
|
||||
*/
|
||||
private RefreshTokenFilterConfigurer refreshTokenSecurityConfigurerAdapter() {
|
||||
return new RefreshTokenFilterConfigurer(uaaAuthenticationService(), tokenStore);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OAuth2CookieHelper cookieHelper() {
|
||||
return new OAuth2CookieHelper(oAuth2Properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OAuth2AuthenticationService uaaAuthenticationService() {
|
||||
return new OAuth2AuthenticationService(tokenEndpointClient, cookieHelper());
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the ResourceServer security by installing a new TokenExtractor.
|
||||
*/
|
||||
@Override
|
||||
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
|
||||
resources.tokenExtractor(tokenExtractor());
|
||||
}
|
||||
|
||||
/**
|
||||
* The new TokenExtractor can extract tokens from Cookies and Authorization headers.
|
||||
*
|
||||
* @return the CookieTokenExtractor bean.
|
||||
*/
|
||||
@Bean
|
||||
public TokenExtractor tokenExtractor() {
|
||||
return new CookieTokenExtractor();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
package com.baeldung.jhipster.gateway.config.oauth2;
|
||||
|
||||
import com.baeldung.jhipster.gateway.security.oauth2.OAuth2SignatureVerifierClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.jwt.crypto.sign.SignatureVerifier;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
|
||||
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Improved JwtAccessTokenConverter that can handle lazy fetching of public verifier keys.
|
||||
*/
|
||||
public class OAuth2JwtAccessTokenConverter extends JwtAccessTokenConverter {
|
||||
private final Logger log = LoggerFactory.getLogger(OAuth2JwtAccessTokenConverter.class);
|
||||
|
||||
private final OAuth2Properties oAuth2Properties;
|
||||
private final OAuth2SignatureVerifierClient signatureVerifierClient;
|
||||
/**
|
||||
* When did we last fetch the public key?
|
||||
*/
|
||||
private long lastKeyFetchTimestamp;
|
||||
|
||||
public OAuth2JwtAccessTokenConverter(OAuth2Properties oAuth2Properties, OAuth2SignatureVerifierClient signatureVerifierClient) {
|
||||
this.oAuth2Properties = oAuth2Properties;
|
||||
this.signatureVerifierClient = signatureVerifierClient;
|
||||
tryCreateSignatureVerifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to decode the token with the current public key.
|
||||
* If it fails, contact the OAuth2 server to get a new public key, then try again.
|
||||
* We might not have fetched it in the first place or it might have changed.
|
||||
*
|
||||
* @param token the JWT token to decode.
|
||||
* @return the resulting claims.
|
||||
* @throws InvalidTokenException if we cannot decode the token.
|
||||
*/
|
||||
@Override
|
||||
protected Map<String, Object> decode(String token) {
|
||||
try {
|
||||
//check if our public key and thus SignatureVerifier have expired
|
||||
long ttl = oAuth2Properties.getSignatureVerification().getTtl();
|
||||
if (ttl > 0 && System.currentTimeMillis() - lastKeyFetchTimestamp > ttl) {
|
||||
throw new InvalidTokenException("public key expired");
|
||||
}
|
||||
return super.decode(token);
|
||||
} catch (InvalidTokenException ex) {
|
||||
if (tryCreateSignatureVerifier()) {
|
||||
return super.decode(token);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a new public key from the AuthorizationServer.
|
||||
*
|
||||
* @return true, if we could fetch it; false, if we could not.
|
||||
*/
|
||||
private boolean tryCreateSignatureVerifier() {
|
||||
long t = System.currentTimeMillis();
|
||||
if (t - lastKeyFetchTimestamp < oAuth2Properties.getSignatureVerification().getPublicKeyRefreshRateLimit()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
SignatureVerifier verifier = signatureVerifierClient.getSignatureVerifier();
|
||||
if (verifier != null) {
|
||||
setVerifier(verifier);
|
||||
lastKeyFetchTimestamp = t;
|
||||
log.debug("Public key retrieved from OAuth2 server to create SignatureVerifier");
|
||||
return true;
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
log.error("could not get public key from OAuth2 server to create SignatureVerifier", ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Extract JWT claims and set it to OAuth2Authentication decoded details.
|
||||
* Here is how to get details:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
* Authentication authentication = securityContext.getAuthentication();
|
||||
* if (authentication != null) {
|
||||
* Object details = authentication.getDetails();
|
||||
* if(details instanceof OAuth2AuthenticationDetails) {
|
||||
* Object decodedDetails = ((OAuth2AuthenticationDetails) details).getDecodedDetails();
|
||||
* if(decodedDetails != null && decodedDetails instanceof Map) {
|
||||
* String detailFoo = ((Map) decodedDetails).get("foo");
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
* @param claims OAuth2JWTToken claims
|
||||
* @return OAuth2Authentication
|
||||
*/
|
||||
@Override
|
||||
public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
|
||||
OAuth2Authentication authentication = super.extractAuthentication(claims);
|
||||
authentication.setDetails(claims);
|
||||
return authentication;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package com.baeldung.jhipster.gateway.config.oauth2;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* OAuth2 properties define properties for OAuth2-based microservices.
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "oauth2", ignoreUnknownFields = false)
|
||||
public class OAuth2Properties {
|
||||
private WebClientConfiguration webClientConfiguration = new WebClientConfiguration();
|
||||
|
||||
private SignatureVerification signatureVerification = new SignatureVerification();
|
||||
|
||||
public WebClientConfiguration getWebClientConfiguration() {
|
||||
return webClientConfiguration;
|
||||
}
|
||||
|
||||
public SignatureVerification getSignatureVerification() {
|
||||
return signatureVerification;
|
||||
}
|
||||
|
||||
public static class WebClientConfiguration {
|
||||
private String clientId = "web_app";
|
||||
private String secret = "changeit";
|
||||
/**
|
||||
* Holds the session timeout in seconds for non-remember-me sessions.
|
||||
* After so many seconds of inactivity, the session will be terminated.
|
||||
* Only checked during token refresh, so long access token validity may
|
||||
* delay the session timeout accordingly.
|
||||
*/
|
||||
private int sessionTimeoutInSeconds = 1800;
|
||||
/**
|
||||
* Defines the cookie domain. If specified, cookies will be set on this domain.
|
||||
* If not configured, then cookies will be set on the top-level domain of the
|
||||
* request you sent, i.e. if you send a request to <code>app1.your-domain.com</code>,
|
||||
* then cookies will be set <code>on .your-domain.com</code>, such that they
|
||||
* are also valid for <code>app2.your-domain.com</code>.
|
||||
*/
|
||||
private String cookieDomain;
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getSecret() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
public void setSecret(String secret) {
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
public int getSessionTimeoutInSeconds() {
|
||||
return sessionTimeoutInSeconds;
|
||||
}
|
||||
|
||||
public void setSessionTimeoutInSeconds(int sessionTimeoutInSeconds) {
|
||||
this.sessionTimeoutInSeconds = sessionTimeoutInSeconds;
|
||||
}
|
||||
|
||||
public String getCookieDomain() {
|
||||
return cookieDomain;
|
||||
}
|
||||
|
||||
public void setCookieDomain(String cookieDomain) {
|
||||
this.cookieDomain = cookieDomain;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SignatureVerification {
|
||||
/**
|
||||
* Maximum refresh rate for public keys in ms.
|
||||
* We won't fetch new public keys any faster than that to avoid spamming UAA in case
|
||||
* we receive a lot of "illegal" tokens.
|
||||
*/
|
||||
private long publicKeyRefreshRateLimit = 10 * 1000L;
|
||||
/**
|
||||
* Maximum TTL for the public key in ms.
|
||||
* The public key will be fetched again from UAA if it gets older than that.
|
||||
* That way, we make sure that we get the newest keys always in case they are updated there.
|
||||
*/
|
||||
private long ttl = 24 * 60 * 60 * 1000L;
|
||||
/**
|
||||
* Endpoint where to retrieve the public key used to verify token signatures.
|
||||
*/
|
||||
private String publicKeyEndpointUri = "http://uaa/oauth/token_key";
|
||||
|
||||
public long getPublicKeyRefreshRateLimit() {
|
||||
return publicKeyRefreshRateLimit;
|
||||
}
|
||||
|
||||
public void setPublicKeyRefreshRateLimit(long publicKeyRefreshRateLimit) {
|
||||
this.publicKeyRefreshRateLimit = publicKeyRefreshRateLimit;
|
||||
}
|
||||
|
||||
public long getTtl() {
|
||||
return ttl;
|
||||
}
|
||||
|
||||
public void setTtl(long ttl) {
|
||||
this.ttl = ttl;
|
||||
}
|
||||
|
||||
public String getPublicKeyEndpointUri() {
|
||||
return publicKeyEndpointUri;
|
||||
}
|
||||
|
||||
public void setPublicKeyEndpointUri(String publicKeyEndpointUri) {
|
||||
this.publicKeyEndpointUri = publicKeyEndpointUri;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Spring Framework configuration files.
|
||||
*/
|
||||
package com.baeldung.jhipster.gateway.config;
|
|
@ -0,0 +1,79 @@
|
|||
package com.baeldung.jhipster.gateway.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import org.hibernate.envers.Audited;
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedBy;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.EntityListeners;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
|
||||
/**
|
||||
* Base abstract class for entities which will hold definitions for created, last modified by and created,
|
||||
* last modified by date.
|
||||
*/
|
||||
@MappedSuperclass
|
||||
@Audited
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public abstract class AbstractAuditingEntity implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@CreatedBy
|
||||
@Column(name = "created_by", nullable = false, length = 50, updatable = false)
|
||||
@JsonIgnore
|
||||
private String createdBy;
|
||||
|
||||
@CreatedDate
|
||||
@Column(name = "created_date", nullable = false, updatable = false)
|
||||
@JsonIgnore
|
||||
private Instant createdDate = Instant.now();
|
||||
|
||||
@LastModifiedBy
|
||||
@Column(name = "last_modified_by", length = 50)
|
||||
@JsonIgnore
|
||||
private String lastModifiedBy;
|
||||
|
||||
@LastModifiedDate
|
||||
@Column(name = "last_modified_date")
|
||||
@JsonIgnore
|
||||
private Instant lastModifiedDate = Instant.now();
|
||||
|
||||
public String getCreatedBy() {
|
||||
return createdBy;
|
||||
}
|
||||
|
||||
public void setCreatedBy(String createdBy) {
|
||||
this.createdBy = createdBy;
|
||||
}
|
||||
|
||||
public Instant getCreatedDate() {
|
||||
return createdDate;
|
||||
}
|
||||
|
||||
public void setCreatedDate(Instant createdDate) {
|
||||
this.createdDate = createdDate;
|
||||
}
|
||||
|
||||
public String getLastModifiedBy() {
|
||||
return lastModifiedBy;
|
||||
}
|
||||
|
||||
public void setLastModifiedBy(String lastModifiedBy) {
|
||||
this.lastModifiedBy = lastModifiedBy;
|
||||
}
|
||||
|
||||
public Instant getLastModifiedDate() {
|
||||
return lastModifiedDate;
|
||||
}
|
||||
|
||||
public void setLastModifiedDate(Instant lastModifiedDate) {
|
||||
this.lastModifiedDate = lastModifiedDate;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package com.baeldung.jhipster.gateway.domain;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Persist AuditEvent managed by the Spring Boot actuator.
|
||||
*
|
||||
* @see org.springframework.boot.actuate.audit.AuditEvent
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "jhi_persistent_audit_event")
|
||||
public class PersistentAuditEvent implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "event_id")
|
||||
private Long id;
|
||||
|
||||
@NotNull
|
||||
@Column(nullable = false)
|
||||
private String principal;
|
||||
|
||||
@Column(name = "event_date")
|
||||
private Instant auditEventDate;
|
||||
|
||||
@Column(name = "event_type")
|
||||
private String auditEventType;
|
||||
|
||||
@ElementCollection
|
||||
@MapKeyColumn(name = "name")
|
||||
@Column(name = "value")
|
||||
@CollectionTable(name = "jhi_persistent_audit_evt_data", joinColumns=@JoinColumn(name="event_id"))
|
||||
private Map<String, String> data = new HashMap<>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getPrincipal() {
|
||||
return principal;
|
||||
}
|
||||
|
||||
public void setPrincipal(String principal) {
|
||||
this.principal = principal;
|
||||
}
|
||||
|
||||
public Instant getAuditEventDate() {
|
||||
return auditEventDate;
|
||||
}
|
||||
|
||||
public void setAuditEventDate(Instant auditEventDate) {
|
||||
this.auditEventDate = auditEventDate;
|
||||
}
|
||||
|
||||
public String getAuditEventType() {
|
||||
return auditEventType;
|
||||
}
|
||||
|
||||
public void setAuditEventType(String auditEventType) {
|
||||
this.auditEventType = auditEventType;
|
||||
}
|
||||
|
||||
public Map<String, String> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(Map<String, String> data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* JPA domain objects.
|
||||
*/
|
||||
package com.baeldung.jhipster.gateway.domain;
|
|
@ -0,0 +1,99 @@
|
|||
package com.baeldung.jhipster.gateway.gateway.accesscontrol;
|
||||
|
||||
import io.github.jhipster.config.JHipsterProperties;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.Route;
|
||||
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
|
||||
/**
|
||||
* Zuul filter for restricting access to backend micro-services endpoints.
|
||||
*/
|
||||
public class AccessControlFilter extends ZuulFilter {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(AccessControlFilter.class);
|
||||
|
||||
private final RouteLocator routeLocator;
|
||||
|
||||
private final JHipsterProperties jHipsterProperties;
|
||||
|
||||
public AccessControlFilter(RouteLocator routeLocator, JHipsterProperties jHipsterProperties) {
|
||||
this.routeLocator = routeLocator;
|
||||
this.jHipsterProperties = jHipsterProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filterType() {
|
||||
return "pre";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter requests on endpoints that are not in the list of authorized microservices endpoints.
|
||||
*/
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
String requestUri = RequestContext.getCurrentContext().getRequest().getRequestURI();
|
||||
String contextPath = RequestContext.getCurrentContext().getRequest().getContextPath();
|
||||
|
||||
// If the request Uri does not start with the path of the authorized endpoints, we block the request
|
||||
for (Route route : routeLocator.getRoutes()) {
|
||||
String serviceUrl = contextPath + route.getFullPath();
|
||||
String serviceName = route.getId();
|
||||
|
||||
// If this route correspond to the current request URI
|
||||
// We do a substring to remove the "**" at the end of the route URL
|
||||
if (requestUri.startsWith(serviceUrl.substring(0, serviceUrl.length() - 2))) {
|
||||
return !isAuthorizedRequest(serviceUrl, serviceName, requestUri);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isAuthorizedRequest(String serviceUrl, String serviceName, String requestUri) {
|
||||
Map<String, List<String>> authorizedMicroservicesEndpoints = jHipsterProperties.getGateway()
|
||||
.getAuthorizedMicroservicesEndpoints();
|
||||
|
||||
// If the authorized endpoints list was left empty for this route, all access are allowed
|
||||
if (authorizedMicroservicesEndpoints.get(serviceName) == null) {
|
||||
log.debug("Access Control: allowing access for {}, as no access control policy has been set up for " +
|
||||
"service: {}", requestUri, serviceName);
|
||||
return true;
|
||||
} else {
|
||||
List<String> authorizedEndpoints = authorizedMicroservicesEndpoints.get(serviceName);
|
||||
|
||||
// Go over the authorized endpoints to control that the request URI matches it
|
||||
for (String endpoint : authorizedEndpoints) {
|
||||
// We do a substring to remove the "**/" at the end of the route URL
|
||||
String gatewayEndpoint = serviceUrl.substring(0, serviceUrl.length() - 3) + endpoint;
|
||||
if (requestUri.startsWith(gatewayEndpoint)) {
|
||||
log.debug("Access Control: allowing access for {}, as it matches the following authorized " +
|
||||
"microservice endpoint: {}", requestUri, gatewayEndpoint);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run() {
|
||||
RequestContext ctx = RequestContext.getCurrentContext();
|
||||
ctx.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
|
||||
ctx.setSendZuulResponse(false);
|
||||
log.debug("Access Control: filtered unauthorized access on endpoint {}", ctx.getRequest().getRequestURI());
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package com.baeldung.jhipster.gateway.gateway.ratelimiting;
|
||||
|
||||
import com.baeldung.jhipster.gateway.security.SecurityUtils;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.function.Supplier;
|
||||
import javax.cache.CacheManager;
|
||||
import javax.cache.Caching;
|
||||
import javax.cache.configuration.CompleteConfiguration;
|
||||
import javax.cache.configuration.MutableConfiguration;
|
||||
import javax.cache.spi.CachingProvider;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
|
||||
import io.github.bucket4j.*;
|
||||
import io.github.bucket4j.grid.GridBucketState;
|
||||
import io.github.bucket4j.grid.ProxyManager;
|
||||
import io.github.bucket4j.grid.jcache.JCache;
|
||||
import io.github.jhipster.config.JHipsterProperties;
|
||||
|
||||
/**
|
||||
* Zuul filter for limiting the number of HTTP calls per client.
|
||||
*
|
||||
* See the Bucket4j documentation at https://github.com/vladimir-bukhtoyarov/bucket4j
|
||||
* https://github.com/vladimir-bukhtoyarov/bucket4j/blob/master/doc-pages/jcache-usage
|
||||
* .md#example-1---limiting-access-to-http-server-by-ip-address
|
||||
*/
|
||||
public class RateLimitingFilter extends ZuulFilter {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(RateLimitingFilter.class);
|
||||
|
||||
public final static String GATEWAY_RATE_LIMITING_CACHE_NAME = "gateway-rate-limiting";
|
||||
|
||||
private final JHipsterProperties jHipsterProperties;
|
||||
|
||||
private javax.cache.Cache<String, GridBucketState> cache;
|
||||
|
||||
private ProxyManager<String> buckets;
|
||||
|
||||
public RateLimitingFilter(JHipsterProperties jHipsterProperties) {
|
||||
this.jHipsterProperties = jHipsterProperties;
|
||||
|
||||
CachingProvider cachingProvider = Caching.getCachingProvider();
|
||||
CacheManager cacheManager = cachingProvider.getCacheManager();
|
||||
CompleteConfiguration<String, GridBucketState> config =
|
||||
new MutableConfiguration<String, GridBucketState>()
|
||||
.setTypes(String.class, GridBucketState.class);
|
||||
|
||||
this.cache = cacheManager.createCache(GATEWAY_RATE_LIMITING_CACHE_NAME, config);
|
||||
this.buckets = Bucket4j.extension(JCache.class).proxyManagerForCache(cache);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filterType() {
|
||||
return "pre";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
// specific APIs can be filtered out using
|
||||
// if (RequestContext.getCurrentContext().getRequest().getRequestURI().startsWith("/api")) { ... }
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run() {
|
||||
String bucketId = getId(RequestContext.getCurrentContext().getRequest());
|
||||
Bucket bucket = buckets.getProxy(bucketId, getConfigSupplier());
|
||||
if (bucket.tryConsume(1)) {
|
||||
// the limit is not exceeded
|
||||
log.debug("API rate limit OK for {}", bucketId);
|
||||
} else {
|
||||
// limit is exceeded
|
||||
log.info("API rate limit exceeded for {}", bucketId);
|
||||
apiLimitExceeded();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Supplier<BucketConfiguration> getConfigSupplier() {
|
||||
return () -> {
|
||||
JHipsterProperties.Gateway.RateLimiting rateLimitingProperties =
|
||||
jHipsterProperties.getGateway().getRateLimiting();
|
||||
|
||||
return Bucket4j.configurationBuilder()
|
||||
.addLimit(Bandwidth.simple(rateLimitingProperties.getLimit(),
|
||||
Duration.ofSeconds(rateLimitingProperties.getDurationInSeconds())))
|
||||
.build();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Zuul response error when the API limit is exceeded.
|
||||
*/
|
||||
private void apiLimitExceeded() {
|
||||
RequestContext ctx = RequestContext.getCurrentContext();
|
||||
ctx.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
|
||||
if (ctx.getResponseBody() == null) {
|
||||
ctx.setResponseBody("API rate limit exceeded");
|
||||
ctx.setSendZuulResponse(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The ID that will identify the limit: the user login or the user IP address.
|
||||
*/
|
||||
private String getId(HttpServletRequest httpServletRequest) {
|
||||
return SecurityUtils.getCurrentUserLogin().orElse(httpServletRequest.getRemoteAddr());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package com.baeldung.jhipster.gateway.gateway.responserewriting;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter;
|
||||
import springfox.documentation.swagger2.web.Swagger2Controller;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
/**
|
||||
* Zuul filter to rewrite micro-services Swagger URL Base Path.
|
||||
*/
|
||||
public class SwaggerBasePathRewritingFilter extends SendResponseFilter {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(SwaggerBasePathRewritingFilter.class);
|
||||
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
public SwaggerBasePathRewritingFilter() {
|
||||
super(new ZuulProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filterType() {
|
||||
return "post";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter requests to micro-services Swagger docs.
|
||||
*/
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
return RequestContext.getCurrentContext().getRequest().getRequestURI().endsWith(Swagger2Controller.DEFAULT_URL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run() {
|
||||
RequestContext context = RequestContext.getCurrentContext();
|
||||
|
||||
context.getResponse().setCharacterEncoding("UTF-8");
|
||||
|
||||
String rewrittenResponse = rewriteBasePath(context);
|
||||
if (context.getResponseGZipped()) {
|
||||
try {
|
||||
context.setResponseDataStream(new ByteArrayInputStream(gzipData(rewrittenResponse)));
|
||||
} catch (IOException e) {
|
||||
log.error("Swagger-docs filter error", e);
|
||||
}
|
||||
} else {
|
||||
context.setResponseBody(rewrittenResponse);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private String rewriteBasePath(RequestContext context) {
|
||||
InputStream responseDataStream = context.getResponseDataStream();
|
||||
String requestUri = RequestContext.getCurrentContext().getRequest().getRequestURI();
|
||||
try {
|
||||
if (context.getResponseGZipped()) {
|
||||
responseDataStream = new GZIPInputStream(context.getResponseDataStream());
|
||||
}
|
||||
String response = IOUtils.toString(responseDataStream, StandardCharsets.UTF_8);
|
||||
if (response != null) {
|
||||
LinkedHashMap<String, Object> map = this.mapper.readValue(response, LinkedHashMap.class);
|
||||
|
||||
String basePath = requestUri.replace(Swagger2Controller.DEFAULT_URL, "");
|
||||
map.put("basePath", basePath);
|
||||
log.debug("Swagger-docs: rewritten Base URL with correct micro-service route: {}", basePath);
|
||||
return mapper.writeValueAsString(map);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Swagger-docs filter error", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static byte[] gzipData(String content) throws IOException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
PrintWriter gzip = new PrintWriter(new GZIPOutputStream(bos));
|
||||
gzip.print(content);
|
||||
gzip.flush();
|
||||
gzip.close();
|
||||
return bos.toByteArray();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Spring Data JPA repositories.
|
||||
*/
|
||||
package com.baeldung.jhipster.gateway.repository;
|
|
@ -0,0 +1,16 @@
|
|||
package com.baeldung.jhipster.gateway.security;
|
||||
|
||||
/**
|
||||
* Constants for Spring Security authorities.
|
||||
*/
|
||||
public final class AuthoritiesConstants {
|
||||
|
||||
public static final String ADMIN = "ROLE_ADMIN";
|
||||
|
||||
public static final String USER = "ROLE_USER";
|
||||
|
||||
public static final String ANONYMOUS = "ROLE_ANONYMOUS";
|
||||
|
||||
private AuthoritiesConstants() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package com.baeldung.jhipster.gateway.security;
|
||||
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Utility class for Spring Security.
|
||||
*/
|
||||
public final class SecurityUtils {
|
||||
|
||||
private SecurityUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the login of the current user.
|
||||
*
|
||||
* @return the login of the current user
|
||||
*/
|
||||
public static Optional<String> getCurrentUserLogin() {
|
||||
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
return Optional.ofNullable(securityContext.getAuthentication())
|
||||
.map(authentication -> {
|
||||
if (authentication.getPrincipal() instanceof UserDetails) {
|
||||
UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
|
||||
return springSecurityUser.getUsername();
|
||||
} else if (authentication.getPrincipal() instanceof String) {
|
||||
return (String) authentication.getPrincipal();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user is authenticated.
|
||||
*
|
||||
* @return true if the user is authenticated, false otherwise
|
||||
*/
|
||||
public static boolean isAuthenticated() {
|
||||
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
return Optional.ofNullable(securityContext.getAuthentication())
|
||||
.map(authentication -> authentication.getAuthorities().stream()
|
||||
.noneMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(AuthoritiesConstants.ANONYMOUS)))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current user has a specific authority (security role).
|
||||
* <p>
|
||||
* The name of this method comes from the isUserInRole() method in the Servlet API
|
||||
*
|
||||
* @param authority the authority to check
|
||||
* @return true if the current user has the authority, false otherwise
|
||||
*/
|
||||
public static boolean isCurrentUserInRole(String authority) {
|
||||
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
return Optional.ofNullable(securityContext.getAuthentication())
|
||||
.map(authentication -> authentication.getAuthorities().stream()
|
||||
.anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(authority)))
|
||||
.orElse(false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.baeldung.jhipster.gateway.security;
|
||||
|
||||
import com.baeldung.jhipster.gateway.config.Constants;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.domain.AuditorAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Implementation of AuditorAware based on Spring Security.
|
||||
*/
|
||||
@Component
|
||||
public class SpringSecurityAuditorAware implements AuditorAware<String> {
|
||||
|
||||
@Override
|
||||
public Optional<String> getCurrentAuditor() {
|
||||
return Optional.of(SecurityUtils.getCurrentUserLogin().orElse(Constants.SYSTEM_ACCOUNT));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
package com.baeldung.jhipster.gateway.security.oauth2;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A Collection of Cookies that allows modification - unlike a mere array.
|
||||
* <p>
|
||||
* Since {@link Cookie} doesn't implement <code>hashCode</code> nor <code>equals</code>,
|
||||
* we cannot simply put it into a <code>HashSet</code>.
|
||||
*/
|
||||
public class CookieCollection implements Collection<Cookie> {
|
||||
private final Map<String, Cookie> cookieMap;
|
||||
|
||||
public CookieCollection() {
|
||||
cookieMap = new HashMap<>();
|
||||
}
|
||||
|
||||
public CookieCollection(Cookie... cookies) {
|
||||
this(Arrays.asList(cookies));
|
||||
}
|
||||
|
||||
public CookieCollection(Collection<? extends Cookie> cookies) {
|
||||
cookieMap = new HashMap<>(cookies.size());
|
||||
addAll(cookies);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return cookieMap.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return cookieMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
if (o instanceof String) {
|
||||
return cookieMap.containsKey(o);
|
||||
}
|
||||
if (o instanceof Cookie) {
|
||||
return cookieMap.containsValue(o);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Cookie> iterator() {
|
||||
return cookieMap.values().iterator();
|
||||
}
|
||||
|
||||
public Cookie[] toArray() {
|
||||
Cookie[] cookies = new Cookie[cookieMap.size()];
|
||||
return toArray(cookies);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] ts) {
|
||||
return cookieMap.values().toArray(ts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Cookie cookie) {
|
||||
if (cookie == null) {
|
||||
return false;
|
||||
}
|
||||
cookieMap.put(cookie.getName(), cookie);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
if (o instanceof String) {
|
||||
return cookieMap.remove((String)o) != null;
|
||||
}
|
||||
if (o instanceof Cookie) {
|
||||
Cookie c = (Cookie)o;
|
||||
return cookieMap.remove(c.getName()) != null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Cookie get(String name) {
|
||||
return cookieMap.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> collection) {
|
||||
for(Object o : collection) {
|
||||
if (!contains(o)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends Cookie> collection) {
|
||||
boolean result = false;
|
||||
for(Cookie cookie : collection) {
|
||||
result|= add(cookie);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> collection) {
|
||||
boolean result = false;
|
||||
for(Object cookie : collection) {
|
||||
result|= remove(cookie);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> collection) {
|
||||
boolean result = false;
|
||||
Iterator<Map.Entry<String, Cookie>> it = cookieMap.entrySet().iterator();
|
||||
while(it.hasNext()) {
|
||||
Map.Entry<String, Cookie> e = it.next();
|
||||
if (!collection.contains(e.getKey()) && !collection.contains(e.getValue())) {
|
||||
it.remove();
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
cookieMap.clear();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package com.baeldung.jhipster.gateway.security.oauth2;
|
||||
|
||||
import org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* Extracts the access token from a cookie.
|
||||
* Falls back to a <code>BearerTokenExtractor</code> extracting information from the Authorization header, if no
|
||||
* cookie was found.
|
||||
*/
|
||||
public class CookieTokenExtractor extends BearerTokenExtractor {
|
||||
/**
|
||||
* Extract the JWT access token from the request, if present.
|
||||
* If not, then it falls back to the {@link BearerTokenExtractor} behaviour.
|
||||
*
|
||||
* @param request the request containing the cookies.
|
||||
* @return the extracted JWT token; or null.
|
||||
*/
|
||||
@Override
|
||||
protected String extractToken(HttpServletRequest request) {
|
||||
String result;
|
||||
Cookie accessTokenCookie = OAuth2CookieHelper.getAccessTokenCookie(request);
|
||||
if (accessTokenCookie != null) {
|
||||
result = accessTokenCookie.getValue();
|
||||
} else {
|
||||
result = super.extractToken(request);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package com.baeldung.jhipster.gateway.security.oauth2;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
|
||||
/**
|
||||
* A request mapper used to modify the cookies in the original request.
|
||||
* This is needed such that we can modify the cookies of the request during a token refresh.
|
||||
* The token refresh happens before authentication by the <code>OAuth2AuthenticationProcessingFilter</code>
|
||||
* so we must make sure that further in the filter chain, we have the new cookies and not the expired/missing ones.
|
||||
*/
|
||||
class CookiesHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
||||
/**
|
||||
* The new cookies of the request. Use these instead of the ones found in the wrapped request.
|
||||
*/
|
||||
private Cookie[] cookies;
|
||||
|
||||
public CookiesHttpServletRequestWrapper(HttpServletRequest request, Cookie[] cookies) {
|
||||
super(request);
|
||||
this.cookies = cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the modified cookies instead of the original ones.
|
||||
*/
|
||||
@Override
|
||||
public Cookie[] getCookies() {
|
||||
return cookies;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
package com.baeldung.jhipster.gateway.security.oauth2;
|
||||
|
||||
import com.baeldung.jhipster.gateway.web.rest.errors.InvalidPasswordException;
|
||||
import io.github.jhipster.security.PersistentTokenCache;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Manages authentication cases for OAuth2 updating the cookies holding access and refresh tokens accordingly.
|
||||
* <p>
|
||||
* It can authenticate users, refresh the token cookies should they expire and log users out.
|
||||
*/
|
||||
public class OAuth2AuthenticationService {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(OAuth2AuthenticationService.class);
|
||||
|
||||
/**
|
||||
* Number of milliseconds to cache refresh token grants so we don't have to repeat them in case of parallel requests.
|
||||
*/
|
||||
private static final long REFRESH_TOKEN_VALIDITY_MILLIS = 10000l;
|
||||
|
||||
/**
|
||||
* Used to contact the OAuth2 token endpoint.
|
||||
*/
|
||||
private final OAuth2TokenEndpointClient authorizationClient;
|
||||
|
||||
/**
|
||||
* Helps us with cookie handling.
|
||||
*/
|
||||
private final OAuth2CookieHelper cookieHelper;
|
||||
|
||||
/**
|
||||
* Caches Refresh grant results for a refresh token value so we can reuse them.
|
||||
* This avoids hammering UAA in case of several multi-threaded requests arriving in parallel.
|
||||
*/
|
||||
private final PersistentTokenCache<OAuth2Cookies> recentlyRefreshed;
|
||||
|
||||
public OAuth2AuthenticationService(OAuth2TokenEndpointClient authorizationClient, OAuth2CookieHelper cookieHelper) {
|
||||
this.authorizationClient = authorizationClient;
|
||||
this.cookieHelper = cookieHelper;
|
||||
recentlyRefreshed = new PersistentTokenCache<>(REFRESH_TOKEN_VALIDITY_MILLIS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user by username and password.
|
||||
*
|
||||
* @param request the request coming from the client.
|
||||
* @param response the response going back to the server.
|
||||
* @param params the params holding the username, password and rememberMe.
|
||||
* @return the OAuth2AccessToken as a ResponseEntity. Will return OK (200), if successful.
|
||||
* If the UAA cannot authenticate the user, the status code returned by UAA will be returned.
|
||||
*/
|
||||
public ResponseEntity<OAuth2AccessToken> authenticate(HttpServletRequest request, HttpServletResponse response,
|
||||
Map<String, String> params) {
|
||||
try {
|
||||
String username = params.get("username");
|
||||
String password = params.get("password");
|
||||
boolean rememberMe = Boolean.valueOf(params.get("rememberMe"));
|
||||
OAuth2AccessToken accessToken = authorizationClient.sendPasswordGrant(username, password);
|
||||
OAuth2Cookies cookies = new OAuth2Cookies();
|
||||
cookieHelper.createCookies(request, accessToken, rememberMe, cookies);
|
||||
cookies.addCookiesTo(response);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("successfully authenticated user {}", params.get("username"));
|
||||
}
|
||||
return ResponseEntity.ok(accessToken);
|
||||
} catch (HttpClientErrorException ex) {
|
||||
log.error("failed to get OAuth2 tokens from UAA", ex);
|
||||
throw new InvalidPasswordException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to refresh the access token using the refresh token provided as cookie.
|
||||
* Note that browsers typically send multiple requests in parallel which means the access token
|
||||
* will be expired on multiple threads. We don't want to send multiple requests to UAA though,
|
||||
* so we need to cache results for a certain duration and synchronize threads to avoid sending
|
||||
* multiple requests in parallel.
|
||||
*
|
||||
* @param request the request potentially holding the refresh token.
|
||||
* @param response the response setting the new cookies (if refresh was successful).
|
||||
* @param refreshCookie the refresh token cookie. Must not be null.
|
||||
* @return the new servlet request containing the updated cookies for relaying downstream.
|
||||
*/
|
||||
public HttpServletRequest refreshToken(HttpServletRequest request, HttpServletResponse response, Cookie
|
||||
refreshCookie) {
|
||||
//check if non-remember-me session has expired
|
||||
if (cookieHelper.isSessionExpired(refreshCookie)) {
|
||||
log.info("session has expired due to inactivity");
|
||||
logout(request, response); //logout to clear cookies in browser
|
||||
return stripTokens(request); //don't include cookies downstream
|
||||
}
|
||||
OAuth2Cookies cookies = getCachedCookies(refreshCookie.getValue());
|
||||
synchronized (cookies) {
|
||||
//check if we have a result from another thread already
|
||||
if (cookies.getAccessTokenCookie() == null) { //no, we are first!
|
||||
//send a refresh_token grant to UAA, getting new tokens
|
||||
String refreshCookieValue = OAuth2CookieHelper.getRefreshTokenValue(refreshCookie);
|
||||
OAuth2AccessToken accessToken = authorizationClient.sendRefreshGrant(refreshCookieValue);
|
||||
boolean rememberMe = OAuth2CookieHelper.isRememberMe(refreshCookie);
|
||||
cookieHelper.createCookies(request, accessToken, rememberMe, cookies);
|
||||
//add cookies to response to update browser
|
||||
cookies.addCookiesTo(response);
|
||||
} else {
|
||||
log.debug("reusing cached refresh_token grant");
|
||||
}
|
||||
//replace cookies in original request with new ones
|
||||
CookieCollection requestCookies = new CookieCollection(request.getCookies());
|
||||
requestCookies.add(cookies.getAccessTokenCookie());
|
||||
requestCookies.add(cookies.getRefreshTokenCookie());
|
||||
return new CookiesHttpServletRequestWrapper(request, requestCookies.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the result from the cache in a thread-safe manner.
|
||||
*
|
||||
* @param refreshTokenValue the refresh token for which we want the results.
|
||||
* @return a RefreshGrantResult for that token. This will either be empty, if we are the first one to do the
|
||||
* request,
|
||||
* or contain some results already, if another thread already handled the grant for us.
|
||||
*/
|
||||
private OAuth2Cookies getCachedCookies(String refreshTokenValue) {
|
||||
synchronized (recentlyRefreshed) {
|
||||
OAuth2Cookies ctx = recentlyRefreshed.get(refreshTokenValue);
|
||||
if (ctx == null) {
|
||||
ctx = new OAuth2Cookies();
|
||||
recentlyRefreshed.put(refreshTokenValue, ctx);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the user out by clearing all cookies.
|
||||
*
|
||||
* @param httpServletRequest the request containing the Cookies.
|
||||
* @param httpServletResponse the response used to clear them.
|
||||
*/
|
||||
public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
|
||||
cookieHelper.clearCookies(httpServletRequest, httpServletResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips token cookies preventing them from being used further down the chain.
|
||||
* For example, the OAuth2 client won't checked them and they won't be relayed to other services.
|
||||
*
|
||||
* @param httpServletRequest the incoming request.
|
||||
* @return the request to replace it with which has the tokens stripped.
|
||||
*/
|
||||
public HttpServletRequest stripTokens(HttpServletRequest httpServletRequest) {
|
||||
Cookie[] cookies = cookieHelper.stripCookies(httpServletRequest.getCookies());
|
||||
return new CookiesHttpServletRequestWrapper(httpServletRequest, cookies);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,334 @@
|
|||
package com.baeldung.jhipster.gateway.security.oauth2;
|
||||
|
||||
import com.baeldung.jhipster.gateway.config.oauth2.OAuth2Properties;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.json.JsonParser;
|
||||
import org.springframework.boot.json.JsonParserFactory;
|
||||
import org.springframework.security.jwt.Jwt;
|
||||
import org.springframework.security.jwt.JwtHelper;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
|
||||
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.apache.http.conn.util.InetAddressUtils.isIPv4Address;
|
||||
import static org.apache.http.conn.util.InetAddressUtils.isIPv6Address;
|
||||
import org.apache.http.conn.util.PublicSuffixMatcher;
|
||||
import org.apache.http.conn.util.PublicSuffixMatcherLoader;
|
||||
|
||||
/**
|
||||
* Helps with OAuth2 cookie handling.
|
||||
*/
|
||||
public class OAuth2CookieHelper {
|
||||
/**
|
||||
* Name of the access token cookie.
|
||||
*/
|
||||
public static final String ACCESS_TOKEN_COOKIE = OAuth2AccessToken.ACCESS_TOKEN;
|
||||
/**
|
||||
* Name of the refresh token cookie in case of remember me.
|
||||
*/
|
||||
public static final String REFRESH_TOKEN_COOKIE = OAuth2AccessToken.REFRESH_TOKEN;
|
||||
/**
|
||||
* Name of the session-only refresh token in case the user did not check remember me.
|
||||
*/
|
||||
public static final String SESSION_TOKEN_COOKIE = "session_token";
|
||||
/**
|
||||
* The names of the Cookies we set.
|
||||
*/
|
||||
private static final List<String> COOKIE_NAMES = Arrays.asList(ACCESS_TOKEN_COOKIE, REFRESH_TOKEN_COOKIE,
|
||||
SESSION_TOKEN_COOKIE);
|
||||
/**
|
||||
* Number of seconds to expire refresh token cookies before the enclosed token expires.
|
||||
* This makes sure we don't run into race conditions where the cookie is still there but
|
||||
* expires while we process it.
|
||||
*/
|
||||
private static final long REFRESH_TOKEN_EXPIRATION_WINDOW_SECS = 3L;
|
||||
|
||||
/**
|
||||
* Public suffix matcher (to strip private subdomains off cookie scope).
|
||||
*/
|
||||
PublicSuffixMatcher suffixMatcher;
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(OAuth2CookieHelper.class);
|
||||
|
||||
private OAuth2Properties oAuth2Properties;
|
||||
|
||||
/**
|
||||
* Used to parse JWT claims.
|
||||
*/
|
||||
private JsonParser jsonParser = JsonParserFactory.getJsonParser();
|
||||
|
||||
public OAuth2CookieHelper(OAuth2Properties oAuth2Properties) {
|
||||
this.oAuth2Properties = oAuth2Properties;
|
||||
|
||||
// Alternatively, always get an up-to-date list by passing an URL
|
||||
this.suffixMatcher = PublicSuffixMatcherLoader.getDefault();
|
||||
}
|
||||
|
||||
public static Cookie getAccessTokenCookie(HttpServletRequest request) {
|
||||
return getCookie(request, ACCESS_TOKEN_COOKIE);
|
||||
}
|
||||
|
||||
public static Cookie getRefreshTokenCookie(HttpServletRequest request) {
|
||||
Cookie cookie = getCookie(request, REFRESH_TOKEN_COOKIE);
|
||||
if (cookie == null) {
|
||||
cookie = getCookie(request, SESSION_TOKEN_COOKIE);
|
||||
}
|
||||
return cookie;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a cookie by name from the given servlet request.
|
||||
*
|
||||
* @param request the request containing the cookie.
|
||||
* @param cookieName the case-sensitive name of the cookie to get.
|
||||
* @return the resulting Cookie; or null, if not found.
|
||||
*/
|
||||
private static Cookie getCookie(HttpServletRequest request, String cookieName) {
|
||||
if (request.getCookies() != null) {
|
||||
for (Cookie cookie : request.getCookies()) {
|
||||
if (cookie.getName().equals(cookieName)) {
|
||||
String value = cookie.getValue();
|
||||
if (StringUtils.hasText(value)) {
|
||||
return cookie;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create cookies using the provided values.
|
||||
*
|
||||
* @param request the request we are handling.
|
||||
* @param accessToken the access token and enclosed refresh token for our cookies.
|
||||
* @param rememberMe whether the user had originally checked "remember me".
|
||||
* @param result will get the resulting cookies set.
|
||||
*/
|
||||
public void createCookies(HttpServletRequest request, OAuth2AccessToken accessToken, boolean rememberMe,
|
||||
OAuth2Cookies result) {
|
||||
String domain = getCookieDomain(request);
|
||||
log.debug("creating cookies for domain {}", domain);
|
||||
Cookie accessTokenCookie = new Cookie(ACCESS_TOKEN_COOKIE, accessToken.getValue());
|
||||
setCookieProperties(accessTokenCookie, request.isSecure(), domain);
|
||||
log.debug("created access token cookie '{}'", accessTokenCookie.getName());
|
||||
|
||||
OAuth2RefreshToken refreshToken = accessToken.getRefreshToken();
|
||||
Cookie refreshTokenCookie = createRefreshTokenCookie(refreshToken, rememberMe);
|
||||
setCookieProperties(refreshTokenCookie, request.isSecure(), domain);
|
||||
log.debug("created refresh token cookie '{}', age: {}", refreshTokenCookie.getName(), refreshTokenCookie
|
||||
.getMaxAge());
|
||||
|
||||
result.setCookies(accessTokenCookie, refreshTokenCookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a cookie out of the given refresh token.
|
||||
* Refresh token cookies contain the base64 encoded refresh token (a JWT token).
|
||||
* They also contain a hint whether the refresh token was for remember me or not.
|
||||
* If not, then the cookie will be prefixed by the timestamp it was created at followed by a pipe '|'.
|
||||
* This gives us the chance to expire session cookies regardless of the token duration.
|
||||
*/
|
||||
private Cookie createRefreshTokenCookie(OAuth2RefreshToken refreshToken, boolean rememberMe) {
|
||||
int maxAge = -1;
|
||||
String name = SESSION_TOKEN_COOKIE;
|
||||
String value = refreshToken.getValue();
|
||||
if (rememberMe) {
|
||||
name = REFRESH_TOKEN_COOKIE;
|
||||
//get expiration in seconds from the token's "exp" claim
|
||||
Integer exp = getClaim(refreshToken.getValue(), AccessTokenConverter.EXP, Integer.class);
|
||||
if (exp != null) {
|
||||
int now = (int) (System.currentTimeMillis() / 1000L);
|
||||
maxAge = exp - now;
|
||||
log.debug("refresh token valid for another {} secs", maxAge);
|
||||
//let cookie expire a bit earlier than the token to avoid race conditions
|
||||
maxAge -= REFRESH_TOKEN_EXPIRATION_WINDOW_SECS;
|
||||
}
|
||||
}
|
||||
Cookie refreshTokenCookie = new Cookie(name, value);
|
||||
refreshTokenCookie.setMaxAge(maxAge);
|
||||
return refreshTokenCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the refresh token cookie was set with remember me checked.
|
||||
* We can recognize this by the name of the cookie.
|
||||
*
|
||||
* @param refreshTokenCookie the cookie holding the refresh token.
|
||||
* @return true, if it was set persistently (i.e. for "remember me").
|
||||
*/
|
||||
public static boolean isRememberMe(Cookie refreshTokenCookie) {
|
||||
return refreshTokenCookie.getName().equals(REFRESH_TOKEN_COOKIE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the refresh token from the refresh token cookie.
|
||||
* Since we encode additional information into the cookie, this needs to be called to get
|
||||
* hold of the enclosed JWT.
|
||||
*
|
||||
* @param refreshCookie the cookie we store the value in.
|
||||
* @return the refresh JWT from the cookie.
|
||||
*/
|
||||
public static String getRefreshTokenValue(Cookie refreshCookie) {
|
||||
String value = refreshCookie.getValue();
|
||||
int i = value.indexOf('|');
|
||||
if (i > 0) {
|
||||
return value.substring(i + 1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the refresh token session has expired.
|
||||
* Only makes sense for non-persistent cookies, i.e. when remember me was not checked.
|
||||
* The motivation for this is that we want to throw out a user after a while if he's inactive.
|
||||
* We cannot do this via refresh token validity because that one is also used for remember me.
|
||||
*
|
||||
* @param refreshCookie the refresh token cookie to check.
|
||||
* @return true, if the session is expired.
|
||||
*/
|
||||
public boolean isSessionExpired(Cookie refreshCookie) {
|
||||
if (isRememberMe(refreshCookie)) { //no session expiration for "remember me"
|
||||
return false;
|
||||
}
|
||||
//read non-remember-me session length in secs
|
||||
int validity = oAuth2Properties.getWebClientConfiguration().getSessionTimeoutInSeconds();
|
||||
if (validity < 0) { //no session expiration configured
|
||||
return false;
|
||||
}
|
||||
Integer iat = getClaim(refreshCookie.getValue(), "iat", Integer.class);
|
||||
if (iat == null) { //token creating timestamp in secs is missing, session does not expire
|
||||
return false;
|
||||
}
|
||||
int now = (int) (System.currentTimeMillis() / 1000L);
|
||||
int sessionDuration = now - iat;
|
||||
log.debug("session duration {} secs, will timeout at {}", sessionDuration, validity);
|
||||
return sessionDuration > validity; //session has expired
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the given claim from the given token.
|
||||
*
|
||||
* @param refreshToken the JWT token to examine.
|
||||
* @param claimName name of the claim to get.
|
||||
* @param clazz the Class we expect to find there.
|
||||
* @return the desired claim.
|
||||
* @throws InvalidTokenException if we cannot find the claim in the token or it is of wrong type.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getClaim(String refreshToken, String claimName, Class<T> clazz) {
|
||||
Jwt jwt = JwtHelper.decode(refreshToken);
|
||||
String claims = jwt.getClaims();
|
||||
Map<String, Object> claimsMap = jsonParser.parseMap(claims);
|
||||
Object claimValue = claimsMap.get(claimName);
|
||||
if (claimValue == null) {
|
||||
return null;
|
||||
}
|
||||
if (!clazz.isAssignableFrom(claimValue.getClass())) {
|
||||
throw new InvalidTokenException("claim is not of expected type: " + claimName);
|
||||
}
|
||||
return (T) claimValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cookie properties of access and refresh tokens.
|
||||
*
|
||||
* @param cookie the cookie to modify.
|
||||
* @param isSecure whether it is coming from a secure request.
|
||||
* @param domain the domain for which the cookie is valid. If null, then will fall back to default.
|
||||
*/
|
||||
private void setCookieProperties(Cookie cookie, boolean isSecure, String domain) {
|
||||
cookie.setHttpOnly(true);
|
||||
cookie.setPath("/");
|
||||
cookie.setSecure(isSecure); //if the request comes per HTTPS set the secure option on the cookie
|
||||
if (domain != null) {
|
||||
cookie.setDomain(domain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the user out by clearing all cookies.
|
||||
*
|
||||
* @param httpServletRequest the request containing the Cookies.
|
||||
* @param httpServletResponse the response used to clear them.
|
||||
*/
|
||||
public void clearCookies(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
|
||||
String domain = getCookieDomain(httpServletRequest);
|
||||
for (String cookieName : COOKIE_NAMES) {
|
||||
clearCookie(httpServletRequest, httpServletResponse, domain, cookieName);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearCookie(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
|
||||
String domain, String cookieName) {
|
||||
Cookie cookie = new Cookie(cookieName, "");
|
||||
setCookieProperties(cookie, httpServletRequest.isSecure(), domain);
|
||||
cookie.setMaxAge(0);
|
||||
httpServletResponse.addCookie(cookie);
|
||||
log.debug("clearing cookie {}", cookie.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the top level domain of the server from the request. This is used to limit the Cookie
|
||||
* to the top domain instead of the full domain name.
|
||||
* <p>
|
||||
* A lot of times, individual gateways of the same domain get their own subdomain but authentication
|
||||
* shall work across all subdomains of the top level domain.
|
||||
* <p>
|
||||
* For example, when sending a request to <code>app1.domain.com</code>,
|
||||
* this returns <code>.domain.com</code>.
|
||||
*
|
||||
* @param request the HTTP request we received from the client.
|
||||
* @return the top level domain to set the cookies for.
|
||||
* Returns null if the domain is not under a public suffix (.com, .co.uk), e.g. for localhost.
|
||||
*/
|
||||
private String getCookieDomain(HttpServletRequest request) {
|
||||
String domain = oAuth2Properties.getWebClientConfiguration().getCookieDomain();
|
||||
if (domain != null) {
|
||||
return domain;
|
||||
}
|
||||
// if not explicitly defined, use top-level domain
|
||||
domain = request.getServerName().toLowerCase();
|
||||
// strip off leading www.
|
||||
if (domain.startsWith("www.")) {
|
||||
domain = domain.substring(4);
|
||||
}
|
||||
// if it isn't an IP address
|
||||
if (!isIPv4Address(domain) && !isIPv6Address(domain)) {
|
||||
// strip off private subdomains, leaving public TLD only
|
||||
String suffix = suffixMatcher.getDomainRoot(domain);
|
||||
if (suffix != null && !suffix.equals(domain)) {
|
||||
// preserve leading dot
|
||||
return "." + suffix;
|
||||
}
|
||||
}
|
||||
// no top-level domain, stick with default domain
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip our token cookies from the array.
|
||||
*
|
||||
* @param cookies the cookies we receive as input.
|
||||
* @return the new cookie array without our tokens.
|
||||
*/
|
||||
Cookie[] stripCookies(Cookie[] cookies) {
|
||||
CookieCollection cc = new CookieCollection(cookies);
|
||||
if (cc.removeAll(COOKIE_NAMES)) {
|
||||
return cc.toArray();
|
||||
}
|
||||
return cookies;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package com.baeldung.jhipster.gateway.security.oauth2;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Holds the access token and refresh token cookies.
|
||||
*/
|
||||
class OAuth2Cookies {
|
||||
private Cookie accessTokenCookie;
|
||||
private Cookie refreshTokenCookie;
|
||||
|
||||
public Cookie getAccessTokenCookie() {
|
||||
return accessTokenCookie;
|
||||
}
|
||||
|
||||
public Cookie getRefreshTokenCookie() {
|
||||
return refreshTokenCookie;
|
||||
}
|
||||
|
||||
public void setCookies(Cookie accessTokenCookie, Cookie refreshTokenCookie) {
|
||||
this.accessTokenCookie = accessTokenCookie;
|
||||
this.refreshTokenCookie = refreshTokenCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the access token and refresh token as cookies to the response after successful authentication.
|
||||
*
|
||||
* @param response the response to add them to.
|
||||
*/
|
||||
void addCookiesTo(HttpServletResponse response) {
|
||||
response.addCookie(getAccessTokenCookie());
|
||||
response.addCookie(getRefreshTokenCookie());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.baeldung.jhipster.gateway.security.oauth2;
|
||||
|
||||
import org.springframework.security.jwt.crypto.sign.SignatureVerifier;
|
||||
|
||||
/**
|
||||
* Abstracts how to create a SignatureVerifier to verify JWT tokens with a public key.
|
||||
* Implementations will have to contact the OAuth2 authorization server to fetch the public key
|
||||
* and use it to build a SignatureVerifier in a server specific way.
|
||||
*
|
||||
* @see UaaSignatureVerifierClient
|
||||
*/
|
||||
public interface OAuth2SignatureVerifierClient {
|
||||
/**
|
||||
* Returns the SignatureVerifier used to verify JWT tokens.
|
||||
* Fetches the public key from the Authorization server to create
|
||||
* this verifier.
|
||||
*
|
||||
* @return the new verifier used to verify JWT signatures.
|
||||
* Will be null if we cannot contact the token endpoint.
|
||||
* @throws Exception if we could not create a SignatureVerifier or contact the token endpoint.
|
||||
*/
|
||||
SignatureVerifier getSignatureVerifier() throws Exception;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.baeldung.jhipster.gateway.security.oauth2;
|
||||
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
|
||||
/**
|
||||
* Client talking to an OAuth2 Authorization server token endpoint.
|
||||
*
|
||||
* @see UaaTokenEndpointClient
|
||||
* @see OAuth2TokenEndpointClientAdapter
|
||||
*/
|
||||
public interface OAuth2TokenEndpointClient {
|
||||
/**
|
||||
* Send a password grant to the token endpoint.
|
||||
*
|
||||
* @param username the username to authenticate.
|
||||
* @param password his password.
|
||||
* @return the access token and enclosed refresh token received from the token endpoint.
|
||||
* @throws org.springframework.security.oauth2.common.exceptions.ClientAuthenticationException
|
||||
* if we cannot contact the token endpoint.
|
||||
*/
|
||||
OAuth2AccessToken sendPasswordGrant(String username, String password);
|
||||
|
||||
/**
|
||||
* Send a refresh_token grant to the token endpoint.
|
||||
*
|
||||
* @param refreshTokenValue the refresh token used to get new tokens.
|
||||
* @return the new access/refresh token pair.
|
||||
* @throws org.springframework.security.oauth2.common.exceptions.ClientAuthenticationException
|
||||
* if we cannot contact the token endpoint.
|
||||
*/
|
||||
OAuth2AccessToken sendRefreshGrant(String refreshTokenValue);
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
package com.baeldung.jhipster.gateway.security.oauth2;
|
||||
|
||||
import com.baeldung.jhipster.gateway.config.oauth2.OAuth2Properties;
|
||||
import io.github.jhipster.config.JHipsterProperties;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* Default base class for an OAuth2TokenEndpointClient.
|
||||
* Individual implementations for a particular OAuth2 provider can use this as a starting point.
|
||||
*/
|
||||
public abstract class OAuth2TokenEndpointClientAdapter implements OAuth2TokenEndpointClient {
|
||||
private final Logger log = LoggerFactory.getLogger(OAuth2TokenEndpointClientAdapter.class);
|
||||
protected final RestTemplate restTemplate;
|
||||
protected final JHipsterProperties jHipsterProperties;
|
||||
protected final OAuth2Properties oAuth2Properties;
|
||||
|
||||
public OAuth2TokenEndpointClientAdapter(RestTemplate restTemplate, JHipsterProperties jHipsterProperties, OAuth2Properties oAuth2Properties) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.jHipsterProperties = jHipsterProperties;
|
||||
this.oAuth2Properties = oAuth2Properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a password grant to the token endpoint.
|
||||
*
|
||||
* @param username the username to authenticate.
|
||||
* @param password his password.
|
||||
* @return the access token.
|
||||
*/
|
||||
@Override
|
||||
public OAuth2AccessToken sendPasswordGrant(String username, String password) {
|
||||
HttpHeaders reqHeaders = new HttpHeaders();
|
||||
reqHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
MultiValueMap<String, String> formParams = new LinkedMultiValueMap<>();
|
||||
formParams.set("username", username);
|
||||
formParams.set("password", password);
|
||||
formParams.set("grant_type", "password");
|
||||
addAuthentication(reqHeaders, formParams);
|
||||
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(formParams, reqHeaders);
|
||||
log.debug("contacting OAuth2 token endpoint to login user: {}", username);
|
||||
ResponseEntity<OAuth2AccessToken>
|
||||
responseEntity = restTemplate.postForEntity(getTokenEndpoint(), entity, OAuth2AccessToken.class);
|
||||
if (responseEntity.getStatusCode() != HttpStatus.OK) {
|
||||
log.debug("failed to authenticate user with OAuth2 token endpoint, status: {}", responseEntity.getStatusCodeValue());
|
||||
throw new HttpClientErrorException(responseEntity.getStatusCode());
|
||||
}
|
||||
OAuth2AccessToken accessToken = responseEntity.getBody();
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a refresh grant to the token endpoint using the current refresh token to obtain new tokens.
|
||||
*
|
||||
* @param refreshTokenValue the refresh token to use to obtain new tokens.
|
||||
* @return the new, refreshed access token.
|
||||
*/
|
||||
@Override
|
||||
public OAuth2AccessToken sendRefreshGrant(String refreshTokenValue) {
|
||||
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
|
||||
params.add("grant_type", "refresh_token");
|
||||
params.add("refresh_token", refreshTokenValue);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
addAuthentication(headers, params);
|
||||
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);
|
||||
log.debug("contacting OAuth2 token endpoint to refresh OAuth2 JWT tokens");
|
||||
ResponseEntity<OAuth2AccessToken> responseEntity = restTemplate.postForEntity(getTokenEndpoint(), entity,
|
||||
OAuth2AccessToken.class);
|
||||
if (responseEntity.getStatusCode() != HttpStatus.OK) {
|
||||
log.debug("failed to refresh tokens: {}", responseEntity.getStatusCodeValue());
|
||||
throw new HttpClientErrorException(responseEntity.getStatusCode());
|
||||
}
|
||||
OAuth2AccessToken accessToken = responseEntity.getBody();
|
||||
log.info("refreshed OAuth2 JWT cookies using refresh_token grant");
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
protected abstract void addAuthentication(HttpHeaders reqHeaders, MultiValueMap<String, String> formParams);
|
||||
|
||||
protected String getClientSecret() {
|
||||
String clientSecret = oAuth2Properties.getWebClientConfiguration().getSecret();
|
||||
if (clientSecret == null) {
|
||||
throw new InvalidClientException("no client-secret configured in application properties");
|
||||
}
|
||||
return clientSecret;
|
||||
}
|
||||
|
||||
protected String getClientId() {
|
||||
String clientId = oAuth2Properties.getWebClientConfiguration().getClientId();
|
||||
if (clientId == null) {
|
||||
throw new InvalidClientException("no client-id configured in application properties");
|
||||
}
|
||||
return clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured OAuth2 token endpoint URI.
|
||||
*
|
||||
* @return the OAuth2 token endpoint URI.
|
||||
*/
|
||||
protected String getTokenEndpoint() {
|
||||
String tokenEndpointUrl = jHipsterProperties.getSecurity().getClientAuthorization().getAccessTokenUri();
|
||||
if (tokenEndpointUrl == null) {
|
||||
throw new InvalidClientException("no token endpoint configured in application properties");
|
||||
}
|
||||
return tokenEndpointUrl;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package com.baeldung.jhipster.gateway.security.oauth2;
|
||||
|
||||
import com.baeldung.jhipster.gateway.config.oauth2.OAuth2Properties;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
|
||||
import org.springframework.security.jwt.crypto.sign.SignatureVerifier;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Client fetching the public key from UAA to create a SignatureVerifier.
|
||||
*/
|
||||
@Component
|
||||
public class UaaSignatureVerifierClient implements OAuth2SignatureVerifierClient {
|
||||
private final Logger log = LoggerFactory.getLogger(UaaSignatureVerifierClient.class);
|
||||
private final RestTemplate restTemplate;
|
||||
protected final OAuth2Properties oAuth2Properties;
|
||||
|
||||
public UaaSignatureVerifierClient(DiscoveryClient discoveryClient, @Qualifier("loadBalancedRestTemplate") RestTemplate restTemplate,
|
||||
OAuth2Properties oAuth2Properties) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.oAuth2Properties = oAuth2Properties;
|
||||
// Load available UAA servers
|
||||
discoveryClient.getServices();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the public key from the UAA.
|
||||
*
|
||||
* @return the public key used to verify JWT tokens; or null.
|
||||
*/
|
||||
@Override
|
||||
public SignatureVerifier getSignatureVerifier() throws Exception {
|
||||
try {
|
||||
HttpEntity<Void> request = new HttpEntity<Void>(new HttpHeaders());
|
||||
String key = (String) restTemplate
|
||||
.exchange(getPublicKeyEndpoint(), HttpMethod.GET, request, Map.class).getBody()
|
||||
.get("value");
|
||||
return new RsaVerifier(key);
|
||||
} catch (IllegalStateException ex) {
|
||||
log.warn("could not contact UAA to get public key");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the configured endpoint URI to retrieve the public key. */
|
||||
private String getPublicKeyEndpoint() {
|
||||
String tokenEndpointUrl = oAuth2Properties.getSignatureVerification().getPublicKeyEndpointUri();
|
||||
if (tokenEndpointUrl == null) {
|
||||
throw new InvalidClientException("no token endpoint configured in application properties");
|
||||
}
|
||||
return tokenEndpointUrl;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package com.baeldung.jhipster.gateway.security.oauth2;
|
||||
|
||||
import com.baeldung.jhipster.gateway.config.oauth2.OAuth2Properties;
|
||||
import io.github.jhipster.config.JHipsterProperties;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Client talking to UAA's token endpoint to do different OAuth2 grants.
|
||||
*/
|
||||
@Component
|
||||
public class UaaTokenEndpointClient extends OAuth2TokenEndpointClientAdapter implements OAuth2TokenEndpointClient {
|
||||
|
||||
public UaaTokenEndpointClient(@Qualifier("loadBalancedRestTemplate") RestTemplate restTemplate,
|
||||
JHipsterProperties jHipsterProperties, OAuth2Properties oAuth2Properties) {
|
||||
super(restTemplate, jHipsterProperties, oAuth2Properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAuthentication(HttpHeaders reqHeaders, MultiValueMap<String, String> formParams) {
|
||||
reqHeaders.add("Authorization", getAuthorizationHeader());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a Basic authorization header to be used to talk to UAA.
|
||||
*/
|
||||
protected String getAuthorizationHeader() {
|
||||
String clientId = getClientId();
|
||||
String clientSecret = getClientSecret();
|
||||
String authorization = clientId + ":" + clientSecret;
|
||||
return "Basic " + Base64Utils.encodeToString(authorization.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Spring Security configuration.
|
||||
*/
|
||||
package com.baeldung.jhipster.gateway.security;
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Service layer beans.
|
||||
*/
|
||||
package com.baeldung.jhipster.gateway.service;
|
|
@ -0,0 +1,118 @@
|
|||
package com.baeldung.jhipster.gateway.web.filter;
|
||||
|
||||
import com.baeldung.jhipster.gateway.security.oauth2.OAuth2AuthenticationService;
|
||||
import com.baeldung.jhipster.gateway.security.oauth2.OAuth2CookieHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.common.exceptions.ClientAuthenticationException;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
|
||||
import org.springframework.security.oauth2.common.exceptions.UnauthorizedClientException;
|
||||
import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Filters incoming requests and refreshes the access token before it expires.
|
||||
*/
|
||||
public class RefreshTokenFilter extends GenericFilterBean {
|
||||
/**
|
||||
* Number of seconds before expiry to start refreshing access tokens so we don't run into race conditions when forwarding
|
||||
* requests downstream. Otherwise, access tokens may still be valid when we check here but may then be expired
|
||||
* when relayed to another microservice a wee bit later.
|
||||
*/
|
||||
private static final int REFRESH_WINDOW_SECS = 30;
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(RefreshTokenFilter.class);
|
||||
|
||||
/**
|
||||
* The OAuth2AuthenticationService is doing the actual work. We are just a simple filter after all.
|
||||
*/
|
||||
private final OAuth2AuthenticationService authenticationService;
|
||||
private final TokenStore tokenStore;
|
||||
|
||||
public RefreshTokenFilter(OAuth2AuthenticationService authenticationService, TokenStore tokenStore) {
|
||||
this.authenticationService = authenticationService;
|
||||
this.tokenStore = tokenStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check access token cookie and refresh it, if it is either not present, expired or about to expire.
|
||||
*/
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
||||
throws IOException, ServletException {
|
||||
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
|
||||
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
|
||||
try {
|
||||
httpServletRequest = refreshTokensIfExpiring(httpServletRequest, httpServletResponse);
|
||||
} catch (ClientAuthenticationException ex) {
|
||||
log.warn("Security exception: could not refresh tokens", ex);
|
||||
httpServletRequest = authenticationService.stripTokens(httpServletRequest);
|
||||
}
|
||||
filterChain.doFilter(httpServletRequest, servletResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the access and refresh tokens if they are about to expire.
|
||||
*
|
||||
* @param httpServletRequest the servlet request holding the current cookies. If no refresh cookie is present,
|
||||
* then we are out of luck.
|
||||
* @param httpServletResponse the servlet response that gets the new set-cookie headers, if they had to be
|
||||
* refreshed.
|
||||
* @return a new request to use downstream that contains the new cookies, if they had to be refreshed.
|
||||
* @throws InvalidTokenException if the tokens could not be refreshed.
|
||||
*/
|
||||
public HttpServletRequest refreshTokensIfExpiring(HttpServletRequest httpServletRequest, HttpServletResponse
|
||||
httpServletResponse) {
|
||||
HttpServletRequest newHttpServletRequest = httpServletRequest;
|
||||
//get access token from cookie
|
||||
Cookie accessTokenCookie = OAuth2CookieHelper.getAccessTokenCookie(httpServletRequest);
|
||||
if (mustRefreshToken(accessTokenCookie)) { //we either have no access token, or it is expired, or it is about to expire
|
||||
//get the refresh token cookie and, if present, request new tokens
|
||||
Cookie refreshCookie = OAuth2CookieHelper.getRefreshTokenCookie(httpServletRequest);
|
||||
if (refreshCookie != null) {
|
||||
try {
|
||||
newHttpServletRequest = authenticationService.refreshToken(httpServletRequest, httpServletResponse, refreshCookie);
|
||||
} catch (HttpClientErrorException ex) {
|
||||
throw new UnauthorizedClientException("could not refresh OAuth2 token", ex);
|
||||
}
|
||||
} else if (accessTokenCookie != null) {
|
||||
log.warn("access token found, but no refresh token, stripping them all");
|
||||
OAuth2AccessToken token = tokenStore.readAccessToken(accessTokenCookie.getValue());
|
||||
if (token.isExpired()) {
|
||||
throw new InvalidTokenException("access token has expired, but there's no refresh token");
|
||||
}
|
||||
}
|
||||
}
|
||||
return newHttpServletRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we must refresh the access token.
|
||||
* We must refresh it, if we either have no access token, or it is expired, or it is about to expire.
|
||||
*
|
||||
* @param accessTokenCookie the current access token.
|
||||
* @return true, if it must be refreshed; false, otherwise.
|
||||
*/
|
||||
private boolean mustRefreshToken(Cookie accessTokenCookie) {
|
||||
if (accessTokenCookie == null) {
|
||||
return true;
|
||||
}
|
||||
OAuth2AccessToken token = tokenStore.readAccessToken(accessTokenCookie.getValue());
|
||||
//check if token is expired or about to expire
|
||||
if (token.isExpired() || token.getExpiresIn() < REFRESH_WINDOW_SECS) {
|
||||
return true;
|
||||
}
|
||||
return false; //access token is still fine
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package com.baeldung.jhipster.gateway.web.filter;
|
||||
|
||||
import com.baeldung.jhipster.gateway.security.oauth2.OAuth2AuthenticationService;
|
||||
|
||||
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter;
|
||||
import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain;
|
||||
|
||||
/**
|
||||
* Configures a RefreshTokenFilter to refresh access tokens if they are about to expire.
|
||||
*
|
||||
* @see RefreshTokenFilter
|
||||
*/
|
||||
public class RefreshTokenFilterConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
|
||||
/**
|
||||
* RefreshTokenFilter needs the OAuth2AuthenticationService to refresh cookies using the refresh token.
|
||||
*/
|
||||
private OAuth2AuthenticationService authenticationService;
|
||||
private final TokenStore tokenStore;
|
||||
|
||||
public RefreshTokenFilterConfigurer(OAuth2AuthenticationService authenticationService, TokenStore tokenStore) {
|
||||
this.authenticationService = authenticationService;
|
||||
this.tokenStore = tokenStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install RefreshTokenFilter as a servlet Filter.
|
||||
*/
|
||||
@Override
|
||||
public void configure(HttpSecurity http) throws Exception {
|
||||
RefreshTokenFilter customFilter = new RefreshTokenFilter(authenticationService, tokenStore);
|
||||
http.addFilterBefore(customFilter, OAuth2AuthenticationProcessingFilter.class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package com.baeldung.jhipster.gateway.web.rest;
|
||||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import com.baeldung.jhipster.gateway.security.oauth2.OAuth2AuthenticationService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Authentication endpoint for web client.
|
||||
* Used to authenticate a user using OAuth2 access tokens or log him out.
|
||||
*
|
||||
* @author markus.oellinger
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
public class AuthResource {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(AuthResource.class);
|
||||
|
||||
private OAuth2AuthenticationService authenticationService;
|
||||
|
||||
public AuthResource(OAuth2AuthenticationService authenticationService) {
|
||||
this.authenticationService = authenticationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates a user setting the access and refresh token cookies.
|
||||
*
|
||||
* @param request the HttpServletRequest holding - among others - the headers passed from the client.
|
||||
* @param response the HttpServletResponse getting the cookies set upon successful authentication.
|
||||
* @param params the login params (username, password, rememberMe).
|
||||
* @return the access token of the authenticated user. Will return an error code if it fails to authenticate the user.
|
||||
*/
|
||||
@RequestMapping(value = "/login", method = RequestMethod.POST, consumes = MediaType
|
||||
.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Timed
|
||||
public ResponseEntity<OAuth2AccessToken> authenticate(HttpServletRequest request, HttpServletResponse response, @RequestBody
|
||||
Map<String, String> params) {
|
||||
return authenticationService.authenticate(request, response, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout current user deleting his cookies.
|
||||
*
|
||||
* @param request the HttpServletRequest holding - among others - the headers passed from the client.
|
||||
* @param response the HttpServletResponse getting the cookies set upon successful authentication.
|
||||
* @return an empty response entity.
|
||||
*/
|
||||
@RequestMapping(value = "/logout", method = RequestMethod.POST)
|
||||
@Timed
|
||||
public ResponseEntity<?> logout(HttpServletRequest request, HttpServletResponse response) {
|
||||
log.info("logging out user {}", SecurityContextHolder.getContext().getAuthentication().getName());
|
||||
authenticationService.logout(request, response);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package com.baeldung.jhipster.gateway.web.rest;
|
||||
|
||||
import com.baeldung.jhipster.gateway.web.rest.vm.RouteVM;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.netflix.zuul.filters.Route;
|
||||
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import com.baeldung.jhipster.gateway.security.AuthoritiesConstants;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
|
||||
/**
|
||||
* REST controller for managing Gateway configuration.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/gateway")
|
||||
public class GatewayResource {
|
||||
|
||||
private final RouteLocator routeLocator;
|
||||
|
||||
private final DiscoveryClient discoveryClient;
|
||||
|
||||
public GatewayResource(RouteLocator routeLocator, DiscoveryClient discoveryClient) {
|
||||
this.routeLocator = routeLocator;
|
||||
this.discoveryClient = discoveryClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /routes : get the active routes.
|
||||
*
|
||||
* @return the ResponseEntity with status 200 (OK) and with body the list of routes
|
||||
*/
|
||||
@GetMapping("/routes")
|
||||
@Timed
|
||||
@Secured(AuthoritiesConstants.ADMIN)
|
||||
public ResponseEntity<List<RouteVM>> activeRoutes() {
|
||||
List<Route> routes = routeLocator.getRoutes();
|
||||
List<RouteVM> routeVMs = new ArrayList<>();
|
||||
routes.forEach(route -> {
|
||||
RouteVM routeVM = new RouteVM();
|
||||
routeVM.setPath(route.getFullPath());
|
||||
routeVM.setServiceId(route.getId());
|
||||
routeVM.setServiceInstances(discoveryClient.getInstances(route.getLocation()));
|
||||
routeVMs.add(routeVM);
|
||||
});
|
||||
return new ResponseEntity<>(routeVMs, HttpStatus.OK);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package com.baeldung.jhipster.gateway.web.rest;
|
||||
|
||||
import com.baeldung.jhipster.gateway.web.rest.vm.LoggerVM;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Controller for view and managing Log Level at runtime.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/management")
|
||||
public class LogsResource {
|
||||
|
||||
@GetMapping("/logs")
|
||||
@Timed
|
||||
public List<LoggerVM> getList() {
|
||||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
return context.getLoggerList()
|
||||
.stream()
|
||||
.map(LoggerVM::new)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@PutMapping("/logs")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@Timed
|
||||
public void changeLevel(@RequestBody LoggerVM jsonLogger) {
|
||||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
context.getLogger(jsonLogger.getName()).setLevel(Level.valueOf(jsonLogger.getLevel()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package com.baeldung.jhipster.gateway.web.rest.errors;
|
||||
|
||||
import org.zalando.problem.AbstractThrowableProblem;
|
||||
import org.zalando.problem.Status;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class BadRequestAlertException extends AbstractThrowableProblem {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String entityName;
|
||||
|
||||
private final String errorKey;
|
||||
|
||||
public BadRequestAlertException(String defaultMessage, String entityName, String errorKey) {
|
||||
this(ErrorConstants.DEFAULT_TYPE, defaultMessage, entityName, errorKey);
|
||||
}
|
||||
|
||||
public BadRequestAlertException(URI type, String defaultMessage, String entityName, String errorKey) {
|
||||
super(type, defaultMessage, Status.BAD_REQUEST, null, null, null, getAlertParameters(entityName, errorKey));
|
||||
this.entityName = entityName;
|
||||
this.errorKey = errorKey;
|
||||
}
|
||||
|
||||
public String getEntityName() {
|
||||
return entityName;
|
||||
}
|
||||
|
||||
public String getErrorKey() {
|
||||
return errorKey;
|
||||
}
|
||||
|
||||
private static Map<String, Object> getAlertParameters(String entityName, String errorKey) {
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
parameters.put("message", "error." + errorKey);
|
||||
parameters.put("params", entityName);
|
||||
return parameters;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package com.baeldung.jhipster.gateway.web.rest.errors;
|
||||
|
||||
import org.zalando.problem.AbstractThrowableProblem;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.zalando.problem.Status.BAD_REQUEST;
|
||||
|
||||
/**
|
||||
* Custom, parameterized exception, which can be translated on the client side.
|
||||
* For example:
|
||||
*
|
||||
* <pre>
|
||||
* throw new CustomParameterizedException("myCustomError", "hello", "world");
|
||||
* </pre>
|
||||
*
|
||||
* Can be translated with:
|
||||
*
|
||||
* <pre>
|
||||
* "error.myCustomError" : "The server says {{param0}} to {{param1}}"
|
||||
* </pre>
|
||||
*/
|
||||
public class CustomParameterizedException extends AbstractThrowableProblem {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String PARAM = "param";
|
||||
|
||||
public CustomParameterizedException(String message, String... params) {
|
||||
this(message, toParamMap(params));
|
||||
}
|
||||
|
||||
public CustomParameterizedException(String message, Map<String, Object> paramMap) {
|
||||
super(ErrorConstants.PARAMETERIZED_TYPE, "Parameterized Exception", BAD_REQUEST, null, null, null, toProblemParameters(message, paramMap));
|
||||
}
|
||||
|
||||
public static Map<String, Object> toParamMap(String... params) {
|
||||
Map<String, Object> paramMap = new HashMap<>();
|
||||
if (params != null && params.length > 0) {
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
paramMap.put(PARAM + i, params[i]);
|
||||
}
|
||||
}
|
||||
return paramMap;
|
||||
}
|
||||
|
||||
public static Map<String, Object> toProblemParameters(String message, Map<String, Object> paramMap) {
|
||||
Map<String, Object> parameters = new HashMap<>();
|
||||
parameters.put("message", message);
|
||||
parameters.put("params", paramMap);
|
||||
return parameters;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.baeldung.jhipster.gateway.web.rest.errors;
|
||||
|
||||
public class EmailAlreadyUsedException extends BadRequestAlertException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public EmailAlreadyUsedException() {
|
||||
super(ErrorConstants.EMAIL_ALREADY_USED_TYPE, "Email is already in use!", "userManagement", "emailexists");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.baeldung.jhipster.gateway.web.rest.errors;
|
||||
|
||||
import org.zalando.problem.AbstractThrowableProblem;
|
||||
import org.zalando.problem.Status;
|
||||
|
||||
public class EmailNotFoundException extends AbstractThrowableProblem {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public EmailNotFoundException() {
|
||||
super(ErrorConstants.EMAIL_NOT_FOUND_TYPE, "Email address not registered", Status.BAD_REQUEST);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.baeldung.jhipster.gateway.web.rest.errors;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
public final class ErrorConstants {
|
||||
|
||||
public static final String ERR_CONCURRENCY_FAILURE = "error.concurrencyFailure";
|
||||
public static final String ERR_VALIDATION = "error.validation";
|
||||
public static final String PROBLEM_BASE_URL = "https://www.jhipster.tech/problem";
|
||||
public static final URI DEFAULT_TYPE = URI.create(PROBLEM_BASE_URL + "/problem-with-message");
|
||||
public static final URI CONSTRAINT_VIOLATION_TYPE = URI.create(PROBLEM_BASE_URL + "/constraint-violation");
|
||||
public static final URI PARAMETERIZED_TYPE = URI.create(PROBLEM_BASE_URL + "/parameterized");
|
||||
public static final URI ENTITY_NOT_FOUND_TYPE = URI.create(PROBLEM_BASE_URL + "/entity-not-found");
|
||||
public static final URI INVALID_PASSWORD_TYPE = URI.create(PROBLEM_BASE_URL + "/invalid-password");
|
||||
public static final URI EMAIL_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/email-already-used");
|
||||
public static final URI LOGIN_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/login-already-used");
|
||||
public static final URI EMAIL_NOT_FOUND_TYPE = URI.create(PROBLEM_BASE_URL + "/email-not-found");
|
||||
|
||||
private ErrorConstants() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package com.baeldung.jhipster.gateway.web.rest.errors;
|
||||
|
||||
import com.baeldung.jhipster.gateway.web.rest.util.HeaderUtil;
|
||||
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.zalando.problem.DefaultProblem;
|
||||
import org.zalando.problem.Problem;
|
||||
import org.zalando.problem.ProblemBuilder;
|
||||
import org.zalando.problem.Status;
|
||||
import org.zalando.problem.spring.web.advice.ProblemHandling;
|
||||
import org.zalando.problem.violations.ConstraintViolationProblem;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Controller advice to translate the server side exceptions to client-friendly json structures.
|
||||
* The error response follows RFC7807 - Problem Details for HTTP APIs (https://tools.ietf.org/html/rfc7807)
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class ExceptionTranslator implements ProblemHandling {
|
||||
|
||||
/**
|
||||
* Post-process the Problem payload to add the message key for the front-end if needed
|
||||
*/
|
||||
@Override
|
||||
public ResponseEntity<Problem> process(@Nullable ResponseEntity<Problem> entity, NativeWebRequest request) {
|
||||
if (entity == null) {
|
||||
return entity;
|
||||
}
|
||||
Problem problem = entity.getBody();
|
||||
if (!(problem instanceof ConstraintViolationProblem || problem instanceof DefaultProblem)) {
|
||||
return entity;
|
||||
}
|
||||
ProblemBuilder builder = Problem.builder()
|
||||
.withType(Problem.DEFAULT_TYPE.equals(problem.getType()) ? ErrorConstants.DEFAULT_TYPE : problem.getType())
|
||||
.withStatus(problem.getStatus())
|
||||
.withTitle(problem.getTitle())
|
||||
.with("path", request.getNativeRequest(HttpServletRequest.class).getRequestURI());
|
||||
|
||||
if (problem instanceof ConstraintViolationProblem) {
|
||||
builder
|
||||
.with("violations", ((ConstraintViolationProblem) problem).getViolations())
|
||||
.with("message", ErrorConstants.ERR_VALIDATION);
|
||||
} else {
|
||||
builder
|
||||
.withCause(((DefaultProblem) problem).getCause())
|
||||
.withDetail(problem.getDetail())
|
||||
.withInstance(problem.getInstance());
|
||||
problem.getParameters().forEach(builder::with);
|
||||
if (!problem.getParameters().containsKey("message") && problem.getStatus() != null) {
|
||||
builder.with("message", "error.http." + problem.getStatus().getStatusCode());
|
||||
}
|
||||
}
|
||||
return new ResponseEntity<>(builder.build(), entity.getHeaders(), entity.getStatusCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Problem> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, @Nonnull NativeWebRequest request) {
|
||||
BindingResult result = ex.getBindingResult();
|
||||
List<FieldErrorVM> fieldErrors = result.getFieldErrors().stream()
|
||||
.map(f -> new FieldErrorVM(f.getObjectName(), f.getField(), f.getCode()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Problem problem = Problem.builder()
|
||||
.withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE)
|
||||
.withTitle("Method argument not valid")
|
||||
.withStatus(defaultConstraintViolationStatus())
|
||||
.with("message", ErrorConstants.ERR_VALIDATION)
|
||||
.with("fieldErrors", fieldErrors)
|
||||
.build();
|
||||
return create(ex, problem, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler
|
||||
public ResponseEntity<Problem> handleNoSuchElementException(NoSuchElementException ex, NativeWebRequest request) {
|
||||
Problem problem = Problem.builder()
|
||||
.withStatus(Status.NOT_FOUND)
|
||||
.with("message", ErrorConstants.ENTITY_NOT_FOUND_TYPE)
|
||||
.build();
|
||||
return create(ex, problem, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler
|
||||
public ResponseEntity<Problem> handleBadRequestAlertException(BadRequestAlertException ex, NativeWebRequest request) {
|
||||
return create(ex, request, HeaderUtil.createFailureAlert(ex.getEntityName(), ex.getErrorKey(), ex.getMessage()));
|
||||
}
|
||||
|
||||
@ExceptionHandler
|
||||
public ResponseEntity<Problem> handleConcurrencyFailure(ConcurrencyFailureException ex, NativeWebRequest request) {
|
||||
Problem problem = Problem.builder()
|
||||
.withStatus(Status.CONFLICT)
|
||||
.with("message", ErrorConstants.ERR_CONCURRENCY_FAILURE)
|
||||
.build();
|
||||
return create(ex, problem, request);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package com.baeldung.jhipster.gateway.web.rest.errors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class FieldErrorVM implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String objectName;
|
||||
|
||||
private final String field;
|
||||
|
||||
private final String message;
|
||||
|
||||
public FieldErrorVM(String dto, String field, String message) {
|
||||
this.objectName = dto;
|
||||
this.field = field;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getObjectName() {
|
||||
return objectName;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.baeldung.jhipster.gateway.web.rest.errors;
|
||||
|
||||
import org.zalando.problem.AbstractThrowableProblem;
|
||||
import org.zalando.problem.Status;
|
||||
|
||||
/**
|
||||
* Simple exception with a message, that returns an Internal Server Error code.
|
||||
*/
|
||||
public class InternalServerErrorException extends AbstractThrowableProblem {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public InternalServerErrorException(String message) {
|
||||
super(ErrorConstants.DEFAULT_TYPE, message, Status.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.baeldung.jhipster.gateway.web.rest.errors;
|
||||
|
||||
import org.zalando.problem.AbstractThrowableProblem;
|
||||
import org.zalando.problem.Status;
|
||||
|
||||
public class InvalidPasswordException extends AbstractThrowableProblem {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public InvalidPasswordException() {
|
||||
super(ErrorConstants.INVALID_PASSWORD_TYPE, "Incorrect password", Status.BAD_REQUEST);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.baeldung.jhipster.gateway.web.rest.errors;
|
||||
|
||||
public class LoginAlreadyUsedException extends BadRequestAlertException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public LoginAlreadyUsedException() {
|
||||
super(ErrorConstants.LOGIN_ALREADY_USED_TYPE, "Login name already used!", "userManagement", "userexists");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* Specific errors used with Zalando's "problem-spring-web" library.
|
||||
*
|
||||
* More information on https://github.com/zalando/problem-spring-web
|
||||
*/
|
||||
package com.baeldung.jhipster.gateway.web.rest.errors;
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* Spring MVC REST controllers.
|
||||
*/
|
||||
package com.baeldung.jhipster.gateway.web.rest;
|
|
@ -0,0 +1,45 @@
|
|||
package com.baeldung.jhipster.gateway.web.rest.util;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
/**
|
||||
* Utility class for HTTP headers creation.
|
||||
*/
|
||||
public final class HeaderUtil {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(HeaderUtil.class);
|
||||
|
||||
private static final String APPLICATION_NAME = "gatewayApp";
|
||||
|
||||
private HeaderUtil() {
|
||||
}
|
||||
|
||||
public static HttpHeaders createAlert(String message, String param) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("X-" + APPLICATION_NAME + "-alert", message);
|
||||
headers.add("X-" + APPLICATION_NAME + "-params", param);
|
||||
return headers;
|
||||
}
|
||||
|
||||
public static HttpHeaders createEntityCreationAlert(String entityName, String param) {
|
||||
return createAlert(APPLICATION_NAME + "." + entityName + ".created", param);
|
||||
}
|
||||
|
||||
public static HttpHeaders createEntityUpdateAlert(String entityName, String param) {
|
||||
return createAlert(APPLICATION_NAME + "." + entityName + ".updated", param);
|
||||
}
|
||||
|
||||
public static HttpHeaders createEntityDeletionAlert(String entityName, String param) {
|
||||
return createAlert(APPLICATION_NAME + "." + entityName + ".deleted", param);
|
||||
}
|
||||
|
||||
public static HttpHeaders createFailureAlert(String entityName, String errorKey, String defaultMessage) {
|
||||
log.error("Entity processing failed, {}", defaultMessage);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("X-" + APPLICATION_NAME + "-error", "error." + errorKey);
|
||||
headers.add("X-" + APPLICATION_NAME + "-params", entityName);
|
||||
return headers;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package com.baeldung.jhipster.gateway.web.rest.util;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Utility class for handling pagination.
|
||||
*
|
||||
* <p>
|
||||
* Pagination uses the same principles as the <a href="https://developer.github.com/v3/#pagination">GitHub API</a>,
|
||||
* and follow <a href="http://tools.ietf.org/html/rfc5988">RFC 5988 (Link header)</a>.
|
||||
*/
|
||||
public final class PaginationUtil {
|
||||
|
||||
private PaginationUtil() {
|
||||
}
|
||||
|
||||
public static <T> HttpHeaders generatePaginationHttpHeaders(Page<T> page, String baseUrl) {
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("X-Total-Count", Long.toString(page.getTotalElements()));
|
||||
String link = "";
|
||||
if ((page.getNumber() + 1) < page.getTotalPages()) {
|
||||
link = "<" + generateUri(baseUrl, page.getNumber() + 1, page.getSize()) + ">; rel=\"next\",";
|
||||
}
|
||||
// prev link
|
||||
if ((page.getNumber()) > 0) {
|
||||
link += "<" + generateUri(baseUrl, page.getNumber() - 1, page.getSize()) + ">; rel=\"prev\",";
|
||||
}
|
||||
// last and first link
|
||||
int lastPage = 0;
|
||||
if (page.getTotalPages() > 0) {
|
||||
lastPage = page.getTotalPages() - 1;
|
||||
}
|
||||
link += "<" + generateUri(baseUrl, lastPage, page.getSize()) + ">; rel=\"last\",";
|
||||
link += "<" + generateUri(baseUrl, 0, page.getSize()) + ">; rel=\"first\"";
|
||||
headers.add(HttpHeaders.LINK, link);
|
||||
return headers;
|
||||
}
|
||||
|
||||
private static String generateUri(String baseUrl, int page, int size) {
|
||||
return UriComponentsBuilder.fromUriString(baseUrl).queryParam("page", page).queryParam("size", size).toUriString();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue