* [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:
psevestre 2018-11-03 13:00:06 -03:00 committed by KevinGilmore
parent ab821cb4e5
commit df8158ad81
678 changed files with 69838 additions and 0 deletions

View File

@ -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

View File

@ -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

145
jhipster/jhipster-uaa/gateway/.gitignore vendored Normal file
View File

@ -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

View File

@ -0,0 +1,5 @@
{
"hooks": {
"pre-commit": "lint-staged"
}
}

View File

@ -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"
}

View File

@ -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.

View File

@ -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

View File

@ -0,0 +1,2 @@
node_modules
target

View File

@ -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

View File

@ -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"
]
}
}

View File

@ -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/

View File

@ -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
}
}
}

286
jhipster/jhipster-uaa/gateway/mvnw vendored Normal file
View File

@ -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 "$@"

161
jhipster/jhipster-uaa/gateway/mvnw.cmd vendored Normal file
View File

@ -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

View File

@ -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

View File

@ -0,0 +1,3 @@
module.exports = {
plugins: []
}

View File

@ -0,0 +1,7 @@
{
"*": {
"target": "http://localhost:8080",
"secure": false,
"loglevel": "debug"
}
}

View File

@ -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

View File

@ -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

View File

@ -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/

View File

@ -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

View File

@ -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/

View File

@ -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/

View File

@ -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" "$@"

View File

@ -0,0 +1,6 @@
version: '2'
services:
gateway-hazelcast-management-center:
image: hazelcast/management-center:3.9.3
ports:
- 8180:8080

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,7 @@
version: '2'
services:
gateway-sonar:
image: sonarqube:7.1-alpine
ports:
- 9001:9000
- 9092:9092

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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 {
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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() {
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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.
}
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,4 @@
/**
* Audit specific code.
*/
package com.baeldung.jhipster.gateway.config.audit;

View File

@ -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();
}
}

View File

@ -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 &amp;&amp; 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;
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,4 @@
/**
* Spring Framework configuration files.
*/
package com.baeldung.jhipster.gateway.config;

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,4 @@
/**
* JPA domain objects.
*/
package com.baeldung.jhipster.gateway.domain;

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,4 @@
/**
* Spring Data JPA repositories.
*/
package com.baeldung.jhipster.gateway.repository;

View File

@ -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() {
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -0,0 +1,4 @@
/**
* Spring Security configuration.
*/
package com.baeldung.jhipster.gateway.security;

View File

@ -0,0 +1,4 @@
/**
* Service layer beans.
*/
package com.baeldung.jhipster.gateway.service;

View File

@ -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
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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()));
}
}

View File

@ -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;
}
}

View File

@ -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(&quot;myCustomError&quot;, &quot;hello&quot;, &quot;world&quot;);
* </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;
}
}

View File

@ -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");
}
}

View File

@ -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);
}
}

View File

@ -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() {
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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");
}
}

View File

@ -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;

View File

@ -0,0 +1,4 @@
/**
* Spring MVC REST controllers.
*/
package com.baeldung.jhipster.gateway.web.rest;

View File

@ -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;
}
}

View File

@ -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