iSLDevs is an all-in-one developer toolset focused on building secure, scalable applications with advanced authentication, user, and location management features. It provides a flexible OAuth2 Authorization Server supporting multiple grant types and social login integrations, backed by JWT and RSA cryptography for robust security. Its extensive configuration options streamline build automation, security, scheduling, caching, and internationalization, enabling developers to accelerate development while maintaining high standards of security and maintainability.
This project aims to simplify complex security and data management tasks for developers. The core features include:
- OAuth2 Authorization Server (Spring Authorization Server 1.5.x)
- Supported flows:
- Authorization Code with PKCE
- Client Credentials
- JWT Bearer Grant
- Refresh Token
- Configurable SSL/TLS support (Tomcat HTTPS)
- Database-backed client, user, role, and permission management
- Token customization (JWT with claims)
- Postman-ready endpoints for testing
- Login with GitHub, Google, Facebook, and centralized user principal mapping (convert external providers to internal user model)
- Automatic token exchange and persistence with Spring Security
- Java 25 (toolchain configured in Gradle)
- Spring Boot 3.5.6
- Spring Authorization Server 1.5.x
- PostgreSQL/MySQL (via JDBC; dev profile uses PostgreSQL)
- Tomcat 10.1.39 (embedded; version forced via Gradle resolution strategy)
- Gradle (Wrapper included)
- Optional: GraalVM for native image builds
Build isldevs from the source and install dependencies:
- Clone the repository:
git clone https://github.com/isldevs/isldevs
- Navigate to the project directory:
cd isldevs
- Install the dependencies:
./gradlew dependencies --refresh-dependencies
-
Setup datasource connection:
This project demonstrates how to configure PostgreSQL in Spring Boot 3.5.x using environment variables instead of property files, following security best practices.
- Required Environment Variables
Variable | Example Value | Description |
---|---|---|
DB_URL |
jdbc:{youurl}://localhost:{port}}/db |
JDBC URL |
DB_USERNAME |
youruser |
Username |
DB_PASSWORD |
yourpass |
Password |
DB_DRIVER_CLASS |
yourdriverclassname |
Driver class name |
- Optional Configuration
Variable | Default | Description |
---|---|---|
DB_POOL_MAX_SIZE |
10 | Maximum connection pool size |
DB_POOL_MIN_IDLE |
5 | Minimum idle connections |
DB_CONNECTION_TIMEOUT |
5000 (ms) | Fail fast in production (5s) |
DB_IDLE_TIMEOUT |
120000 (ms) | Reclaim unused connections faster (2m) |
DB_MAX_LIFETIME |
1800000 (ms) | Prevent stale connections (30m) |
DB_LEAK_DETECTION_THRESHOLD |
60000 (ms) | Lead to resource exhaustion and application crashes (60s) |
- Run project
./gradlew clean build
./gradlew bootRun -Pspring.profiles.active=dev
OR
./gradlew clean build
./gradlew bootRun -Pspring.profiles.active=prod
This document explains how to configure SSL/TLS for the embedded Tomcat server in the iSLDevs application using Spring Boot 3.5.x.
For development environments, generate a self-signed certificate:
keytool -genkeypair -alias yourserver -keyalg RSA -keysize 4096 \
-validity 365 -storetype PKCS12 -keystore src/main/resources/yourserver.p12 \
-storepass changeit -keypass changeit \
-dname "CN=localhost" -ext "SAN=DNS:localhost,IP:127.0.0.1"
Check certificate details
keytool -list -v -keystore src/main/resources/yourserver.p12 -storepass changeit
Check supported protocols (should only show TLS 1.2/1.3)
nmap --script ssl-enum-ciphers -p 8443 localhost
- Set environment variables:
# Linux/macOS export DB_URL=jdbc:{youurl}://localhost:{port}}/db export DB_USERNAME=youruser export DB_PASSWORD=yourpass export DB_DRIVER_CLASS=yourdriverclassname # Windows set DB_URL=jdbc:{youurl}://localhost:{port}}/db set DB_USERNAME=youruser set DB_PASSWORD=yourpass set DB_DRIVER_CLASS=yourdriverclassname
-
Open Run Configurations
- Click the dropdown near "Run" β Edit Configurations...
-
Add Environment Variables
- Select your Spring Boot application configuration
- Under Environment Variables, add:
DB_URL=jdbc:{youurl}://localhost:{port}}/db DB_USERNAME=youruser DB_PASSWORD=yourpass DB_DRIVER_CLASS=yourdriverclassname
- Click Apply β OK
-
Open Run Configurations
- Right-click project β Run As β Run Configurations...
-
Add Environment Variables
- Select your Spring Boot application
- Go to Environment tab β Add:
Name Value DB_URL
jdbc:{youurl}://localhost:{port}}/db
DB_USERNAME
youruser
DB_PASSWORD
yourpass
DB_DRIVER_CLASS
yourdriverclassname
- Click Apply β Run
- Select your Spring Boot application
application.properties
spring.profiles.active=dev
spring.env.file=classpath:config/.dev
#spring.profiles.active=prod
#spring.env.file:classpath:config/.prod
application-dev.properties
DB_URL=jdbc:{youurl}://localhost:{port}}/db
DB_USERNAME=youruser
DB_PASSWORD=yourpass
DB_DRIVER_CLASS=yourdriverclassname
DB_POOL_MAX_SIZE=15
DB_POOL_MIN_IDLE=5
DB_CONNECTION_TIMEOUT=30000
DB_IDLE_TIMEOUT=600000
DB_MAX_LIFETIME=1800000
DB_LEAK_DETECTION_THRESHOLD=0
HIBERNATE_DIALECT=yourhibernatedialect
HIBERNATE_DDL_AUTO=update
HIBERNATE_SHOW_SQL=true
HIBERNATE_FORMAT_SQL=true
REDIS_ENABLED=false
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=password
application-prod.properties
DB_URL=jdbc:{youurl}://localhost:{port}}/db
DB_USERNAME=youruser
DB_PASSWORD=yourpass
DB_DRIVER_CLASS=yourdriverclassname
DB_POOL_MAX_SIZE=25
DB_POOL_MIN_IDLE=10
DB_CONNECTION_TIMEOUT=5000
DB_IDLE_TIMEOUT=120000
DB_MAX_LIFETIME=900000
DB_LEAK_DETECTION_THRESHOLD=60000
HIBERNATE_DIALECT=yourhibernatedialect
HIBERNATE_DDL_AUTO=validate
HIBERNATE_SHOW_SQL=false
HIBERNATE_FORMAT_SQL=true
REDIS_ENABLED=false
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=password
This provides specific Postman endpoint configurations for testing each client ID flow.
- Postman: Download and install Postman from www.postman.com.
- Running Application: Ensure your Spring Boot application is running and accessible.
- Environment Variables (Optional): Consider setting up Postman environment variables for host, port, and client secrets.
Here are the Postman endpoints for each client ID flow:
- Client ID:
{client-id}
- Grant Type: Authorization Code with PKCE
-
Authorization Endpoint:
- Method:
GET
- URL:
https://{host}:{port}/oauth2/authorize
- Query Parameters:
response_type
:code
client_id
:{client-id}
redirect_uri
:https://{redirect-uri}:{port}/login/oauth2/code/{client-id}
scope
:openid profile email
code_challenge
:{code_challenge}
code_challenge_method
:S256
state
:{random_state}
- Method:
-
Token Endpoint (Authorization Code):
- Method:
POST
- URL:
https://{host}:{port}/oauth2/token
- Headers:
Content-Type
:application/x-www-form-urlencoded
Authorization
: Basic base64({client-id}
:{client-secret}
)
- Body (x-www-form-urlencoded):
grant_type
:authorization_code
code
:{authorization_code}
(Obtained from the Authorization Endpoint redirect)redirect_uri
:https://{redirect-uri}:{port}/login/oauth2/code/{client-id}
code_verifier
:{code_verifier}
- Method:
-
Token Endpoint (Refresh Token):
- Method:
POST
- URL:
https://{host}:{port}/oauth2/token
- Headers:
Content-Type
:application/x-www-form-urlencoded
Authorization
: Basic base64({client-id}
:{client-secret}
)
- Body (x-www-form-urlencoded):
grant_type
:refresh_token
refresh_token
:{refresh_token}
- Method:
-
PKCE Code Generation (JavaScript Example):
- Generate Code Verifier and Code Challenge:
- Create a random string. It should be between 43 and 128 characters long.
- Example (JavaScript):
function generateCodeVerifier() { const array = new Uint32Array(56); window.crypto.getRandomValues(array); return Array.from(array, dec => ('0' + dec.toString(16)).slice(-2)).join(''); } const codeVerifier = generateCodeVerifier(); console.log("Code Verifier:", codeVerifier); async function generateCodeChallenge(codeVerifier) { const data = new TextEncoder().encode(codeVerifier); const digest = await crypto.subtle.digest("SHA-256", data); return btoa(String.fromCharCode(...new Uint8Array(digest))) .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); } generateCodeChallenge(codeVerifier).then(challenge => { console.log("Code Challenge:", challenge); });
- Use in Authorization Request:
- Send the
code_challenge
andcode_challenge_method=S256
in the authorization endpoint request.
- Send the
- Use Code verifier in Token Request:
- Send the original
code_verifier
in the token endpoint request.
- Send the original
- Generate Code Verifier and Code Challenge:
-
- Client ID:
service-account
- Grant Type: Client Credentials
- Token Endpoint:
- Method:
POST
- URL:
https://{host}:{port}/oauth2/token
- Headers:
Content-Type
:application/x-www-form-urlencoded
Authorization
: Basic base64({client-id}
:{client-secret}
)
- Body (x-www-form-urlencoded):
grant_type
:client_credentials
scope
:api.internal monitoring.read
- Method:
- Token Endpoint:
- Client ID:
microservice
- Grant Type: JWT Bearer
-
Token Endpoint:
- Method:
POST
- URL:
https://{host}:{port}/oauth2/token
- Headers:
Content-Type
:application/x-www-form-urlencoded
- Body (x-www-form-urlencoded):
grant_type
:urn:ietf:params:oauth:grant-type:jwt-bearer
assertion
:{signed_jwt}
client_id
:{client-id}
- Method:
-
JWT Creation Notes:
- Obtain Private Key:
- Retrieve the private key associated with the
microservice
client. This key is used to sign the JWT.
- Retrieve the private key associated with the
- Construct JWT Claims:
- Create a JSON object with the required JWT claims.
- Required claims:
*
iss
(Issuer): The issuer of the JWT (your authorization server). *sub
(Subject): Theclient_id
(microservice
). *aud
(Audience): The token endpoint URL. *exp
(Expiration Time): The expiration time of the JWT (in seconds since epoch). *iat
(Issued At): The time the JWT was issued (in seconds since epoch). - Example (JSON):
{ "iss": "https://{host}:{port}", "sub": "microservice", "aud": "https://{host}:{port}/oauth2/token", "exp": 1678886400, "iat": 1678882800 }
- Sign the JWT:
- Sign the JWT using the private key and the appropriate algorithm (e.g., RS256).
- Libraries in most programming languages can handle this.
- Use in Token Request:
- Send the signed JWT as the
assertion
parameter in the token endpoint request.
- Send the signed JWT as the
- Obtain Private Key:
-
- Client ID:
iot-device
- Grant Type: Device Code
-
Device Authorization Endpoint:
- Method:
POST
- URL:
https://{host}:{port}/oauth2/device_authorization
- Headers:
Content-Type
:application/x-www-form-urlencoded
- Body (x-www-form-urlencoded):
client_id
:{client-id}
scope
:openid device.manage
- Method:
-
Token Endpoint (Polling):
- Method:
POST
- URL:
https://{host}:{port}/oauth2/token
- Headers:
Content-Type
:application/x-www-form-urlencoded
Authorization
: Basic base64({client-id}
:{client-secret}
)
- Body (x-www-form-urlencoded):
grant_type
:urn:ietf:params:oauth:grant-type:device_code
device_code
:{device_code}
(Obtained from the Device Authorization Endpoint)
- Method:
-
Token Endpoint (Refresh Token):
- Method:
POST
- URL:
https://{host}:{port}/oauth2/token
- Headers:
Content-Type
:application/x-www-form-urlencoded
Authorization
: Basic base64({client-id}
:{client-secret}
)
- Body (x-www-form-urlencoded):
grant_type
:refresh_token
refresh_token
:{refresh_token}
(Obtained from the initial token response)
- Method:
-
Noted:
Client secret and id handle value in table, go to config value of CLIENT_ID and CLIENT_SECRET in table config. Then restart your service.
- Replace
{host}
and{port}
with your actual host and port values. - The
secret
client secrets are for development purposes only. Use secure storage in production. - For the JWT Bearer flow, you'll need to implement JWT creation in your application.
- For the Device Code flow, be aware of the user interaction required.
- For all flows, ensure your client secrets are handled securely.
- Remember to replace any bracketed placeholders with actual values.
- Ensure that your redirect URIs are correct for your frontend application.
- Adjust the scopes to match the access requirements of your clients.
- Be mindful of token expiration times and refresh token usage.
This project uses the gradle-license-plugin
by Hierynomus to automatically add the Apache 2.0 license header to all Java source files. This ensures compliance with the Apache 2.0 license and clearly defines the terms under which the software is distributed.
Steps to Add the License Header:
-
Add the Plugin Dependency to
build.gradle
: Open yourbuild.gradle
file and add the following line to theplugins
block:plugins { // ... other plugins id "com.github.hierynomus.license" version "0.16.1" }
Make sure to check the plugin's GitHub repository (https://github.com/hierynomus/gradle-license) for the latest version if needed.
-
Create the
LICENSE_HEADER
File: In the root of your project directory, create a new file namedLICENSE_HEADER
. -
Populate
LICENSE_HEADER
with the Apache 2.0 License Text: Paste the following content into theLICENSE_HEADER
file, making sure to replace[yyyy]
with the appropriate year and[name of copyright owner]
with the correct copyright holder:Copyright [yyyy] [name of copyright owner] Licensed 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](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.
-
Configure the License Plugin in
build.gradle
: Add the followinglicense
block to yourbuild.gradle
file:license { header = rootProject.file('LICENSE_HEADER') // You can add exclusions if needed: // exclude "**/*.properties" // exclude "**/*.yml" // exclude "**/*.md" }
-
Apply the License Headers: Open your terminal in the root of your project directory and run the following Gradle command:
./gradlew licenseFormat
This will add the license header to all your Java source files that are missing it or have an incorrect one.
-
Verify License Headers: You can verify that all files have the correct license header by running:
./gradlew licenseCheck
This task will fail if any files are missing the header or have an incorrect one.
Important Considerations:
- Backup: Before running
licenseFormat
for the first time, it's recommended to commit your current changes or create a backup of your project. - Existing Headers: The plugin might replace existing license headers with the configured one. Review the changes carefully after running
licenseFormat
. - Exclusions: Use the
exclude
configuration option to prevent the plugin from adding headers to specific files or directories (e.g., configuration files, documentation). - Copyright Year: Ensure the copyright year in your
LICENSE_HEADER
is accurate. - CI/CD Integration: Consider adding the
gradle licenseCheck
task to your GitHub Actions workflow (ingradle.yml
) to automatically verify license headers during your CI builds. This helps maintain consistency.
This project uses the Gradle Versions Plugin to maintain dependency hygiene, security, and stability. The system provides automated checks for outdated libraries with configurable update policies.
1.Standard check
./gradlew dependencyUpdate
2.Stable releases only
./gradlew dependencyUpdate -Drevision=release
This document explains how to manage and configure RSA key pairs for JWT signing and verification, including secure storage and key loading in a Spring Boot application.
Use OpenSSL to generate a 2048-bit RSA private key and extract its public key:
# Generate private key
openssl genrsa -out key.private 2048
# Extract public key
openssl rsa -in key.private -pubout -out key.public
Place the generated keys (key.private and key.public) in the src/main/resources directory for easy classpath loading.
The salt value is used for encrypting the private key or other cryptographic operations. You can derive the salt from a meaningful string by converting it to hexadecimal.
For example, for "isldevs":
echo -n "isldevs" | xxd -p
# Output: 69736c64657673
# Use the hex string 69736c64657673 as your salt value.
Configure your application.properties or application.yml with the following properties:
jwt.key.public=classpath:key.public
jwt.key.private=classpath:key.private
jwt.key.id=isldevs
jwt.persistence.password=iamsldevs
jwt.persistence.salt=69736c64657673
This project uses Gradle dependency locking to ensure consistent builds across all environments. A gradle.lockfile is automatically generated and tracked in version control. This file captures the exact resolved versions of all dependencies and prevents unexpected upgrades due to dynamic or transitive versions.
Dependency locking is enabled via dependencyLocking in build.gradle.
- π§ To update the lock file: Use the following command to regenerate it after modifying dependencies:
./gradlew dependencies --write-locks
- To refresh all dependencies and update the lock file:
./gradlew dependencies --write-locks --refresh-dependencies
- To verify all dependencies:
./gradlew dependencyUpdates
π‘ The gradle.lockfile is intentionally located at the project root, following Gradle's standard behavior.
- Language: Java 25
- Build: Gradle (Wrapper included)
- Frameworks:
- Spring Boot 3.5.6
- Spring Web, Spring MVC (embedded Tomcat 10.1.39)
- Spring Security + Spring Authorization Server 1.5.x
- Spring Data JPA (Hibernate)
- Thymeleaf (templates configured under classpath:templates)
- Optional: Spring Data Redis (disabled by default)
- Flyway (database migrations)
- Database: PostgreSQL by default in dev profile (JDBC). MySQL possible with driver swap.
- Native image: GraalVM Native Build Tools plugin
- Packaging: Bootable JAR and WAR (war plugin enabled)
- Main class:
com.base.ISLDevsApplication
- Spring profile: default
dev
(seesrc/main/resources/application.properties
) - Web server: Embedded Tomcat (forced to 10.1.39 in Gradle)
- Build and run with the dev profile:
- ./gradlew clean build
- ./gradlew bootRun -Pspring.profiles.active=dev
- Switch to prod profile:
- ./gradlew bootRun -Pspring.profiles.active=prod
- Run as JAR after build:
- java -Dspring.profiles.active=dev -jar build/libs/isldevs-0.0.1-SNAPSHOT.jar
- Run as WAR (if deploying to external container):
- Produce WAR via: ./gradlew clean build
- Deploy generated WAR to a Servlet 6.0+ compatible container (Tomcat 10.1+)
- Application
- ./gradlew bootRun β run the app
- ./gradlew build β compile and package
- ./gradlew test β run unit tests
- Code quality
- ./gradlew spotlessApply β format code
- ./gradlew dependencyUpdates β check dependency versions
- Database (Flyway)
- ./gradlew flywayInfo
- ./gradlew flywayMigrate
- ./gradlew flywayClean # caution: destructive
- Native Image (GraalVM)
- ./gradlew nativeCompile # produces native binary (requires GraalVM toolchain)
You can configure the application via Spring properties files or environment variables.
-
Profiles
- SPRING_PROFILES_ACTIVE=dev|prod (default is dev in application.properties)
-
Database (Springβs standard variables)
- SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/isldevs_db
- SPRING_DATASOURCE_USERNAME=postgres
- SPRING_DATASOURCE_PASSWORD=secret
- SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.postgresql.Driver
-
HikariCP tuning (optional)
- SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE=15
- SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE=5
- SPRING_DATASOURCE_HIKARI_CONNECTION_TIMEOUT=30000
- SPRING_DATASOURCE_HIKARI_IDLE_TIMEOUT=600000
- SPRING_DATASOURCE_HIKARI_MAX_LIFETIME=1800000
-
OAuth2 / Security
- SPRING_SECURITY_OAUTH2_ISSUER_URI=https://localhost:8443/api/v1
- JWT key locations can be set via properties (see
application-dev.properties
):- jwt.key.public=classpath:key.public
- jwt.key.private=classpath:key.private
-
TODO: Social login client IDs/secrets for GitHub/Google/Facebook should be documented with exact property names once configured.
-
NOTE: A custom
DB_*
variable approach is described earlier in this README; consider unifying on Springβs standard env names. TODO: consolidate configuration docs.
- Run all tests: ./gradlew test
- Run with debug logs: ./gradlew test --info
- Run a single test class (example): ./gradlew test --tests "com.base.SomeTestClass"
/ (project root)
ββ build.gradle
ββ settings.gradle
ββ gradlew / gradlew.bat
ββ gradle/
ββ src/
β ββ main/
β β ββ java/com/base/
β β β ββ ISLDevsApplication.java
β β β ββ config/** (security, global config, etc.)
β β β ββ ...
β β ββ resources/
β β ββ application.properties
β β ββ application-dev.properties
β β ββ application-pord.properties
β β ββ templates/ (Thymeleaf)
β β ββ db/ (Flyway)
β ββ test/
β ββ java/ (tests)
ββ README.md
ββ LICENSE_HEADER
- Source files include Apache License 2.0 headers.
- TODO: Add a top-level LICENSE file (Apache-2.0) to match headers and clarify licensing.