Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/hardiksinghbehl/jwt-auth-flow-spring-security
Java backend application using Spring-security to implement JWT based Authentication and Authorization
https://github.com/hardiksinghbehl/jwt-auth-flow-spring-security
jwt jwt-authentication rs512 spring-boot spring-security spring-security-jwt spring-security-rsa token-revocation
Last synced: 16 days ago
JSON representation
Java backend application using Spring-security to implement JWT based Authentication and Authorization
- Host: GitHub
- URL: https://github.com/hardiksinghbehl/jwt-auth-flow-spring-security
- Owner: hardikSinghBehl
- License: unlicense
- Created: 2021-05-07T08:06:43.000Z (over 3 years ago)
- Default Branch: master
- Last Pushed: 2024-06-08T08:07:02.000Z (7 months ago)
- Last Synced: 2024-06-08T09:25:19.694Z (7 months ago)
- Topics: jwt, jwt-authentication, rs512, spring-boot, spring-security, spring-security-jwt, spring-security-rsa, token-revocation
- Language: Java
- Homepage:
- Size: 237 KB
- Stars: 95
- Watchers: 4
- Forks: 25
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
## JWT Authentication and Authorization Flow using Spring Security
##### A reference proof-of-concept that leverages Spring-security to implement JWT based authentication, API access control, Token revocation and Compromised password detection.
##### 🛠upgraded to Spring Boot 3 and Spring Security 6 ðŸ›### Key Components
* [JwtAuthenticationFilter.java](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/filter/JwtAuthenticationFilter.java)
* [SecurityConfiguration.java](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/configuration/SecurityConfiguration.java)
* [ApiEndpointSecurityInspector.java](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/utility/ApiEndpointSecurityInspector.java)
* [TokenConfigurationProperties.java](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/configuration/TokenConfigurationProperties.java)
* [JwtUtility.java](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/utility/JwtUtility.java)
* [TokenRevocationService.java](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/service/TokenRevocationService.java)Any request to a secured endpoint is intercepted by the [JwtAuthenticationFilter](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/filter/JwtAuthenticationFilter.java), which is added to the security filter chain and configured in the [SecurityConfiguration](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/configuration/SecurityConfiguration.java). The custom filter holds the responsibility for verifying the authenticity of the incoming access token and populating the security context.
### Public API declaration
Any API that needs to be made public can be annotated with [@PublicEndpoint](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/configuration/PublicEndpoint.java). Requests to the configured API paths will not evaluated by the custom security filter with the logic being governed by [ApiEndpointSecurityInspector](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/utility/ApiEndpointSecurityInspector.java).
Below is a sample controller method declared as public which will be exempted from authentication checks:
```java
@PublicEndpoint
@GetMapping(value = "/api/v1/something")
public ResponseEntity getSomething() {
var something = someService.fetch();
return ResponseEntity.ok(something);
}
```### Token Generation and Configuration
The application uses Access Tokens (JWT) and Refresh Tokens, both of which are returned to the client upon successful authentication. JWTs are signed and verified using RS512 asymmetric key pair, wherein a private key (PKCS#8 format) is used for signing and the corresponding public key is used for verification whenever a private endpoint is invoked, with these operations handled by [JwtUtility](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/utility/JwtUtility.java). Refresh tokens are random 256-bit values generated by [RefreshTokenGenerator](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/utility/RefreshTokenGenerator.java) and stored in a cache against the user identifier by [AuthenticationService](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/service/AuthenticationService.java).Token validity/expiration (In minutes) and the asymmetric key pairs can be configured in the active `.yml` file. The configured values are populated in [TokenConfigurationProperties](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/configuration/TokenConfigurationProperties.java) and referenced by the application. Below is a sample snippet.
```yaml
com:
behl:
cerberus:
token:
access-token:
private-key: ${JWT_PRIVATE_KEY}
public-key: ${JWT_PUBLIC_KEY}
validity: 30
refresh-token:
validity: 120
```
### API Access ControlAccess control is imposed by the application based on the user's current status within the system. The corresponding permissions are embedded into the generated JWT which allows for stateless access control and authorization process.
For detailed explanation, this [Document](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/documentation/API_ACCESS_CONTROL.md) can be referenced.
### Token Revocation
When a user's status is demoted to one with fewer privileges, there is a need to invalidate the existing Access Token since it retains the previous enhanced scopes. The [implementation](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/service/TokenRevocationService.java) leverages the JWT Token Identifier (JTI) and a caching mechanism to achieve this. Incoming Bearer tokens are validated by the [JwtAuthenticationFilter](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/filter/JwtAuthenticationFilter.java) to ensure they've not been revoked. By revoking access tokens promptly, the system maintains the integrity of access control.
For detailed explanation, this [Document](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/documentation/TOKEN_REVOCATION.md) can be referenced.
### Authentication Failure
Spring security exceptions are commenced at the AuthenticationEntryPoint. A custom implementation, [CustomAuthenticationEntryPoint](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/configuration/CustomAuthenticationEntryPoint.java) is configured in [SecurityConfiguration](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/configuration/SecurityConfiguration.java) which assumes any exceptions thrown by the authentication filters are due to token verification failure. Hence, the implementation instantiates [TokenVerificationException](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/exception/TokenVerificationException.java) and delegates the responsibility of exception handling to HandlerExceptionResolver. The exception finally gets evaluated by [ExceptionResponseHandler](https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/blob/master/src/main/java/com/behl/cerberus/exception/ExceptionResponseHandler.java) and approprate exception response is returned to the client.
The below API response is returned by the application in the event of a token verification failure during security evaluation.
```json
{
"Status": "401 UNAUTHORIZED",
"Description": "Authentication failure: Token missing, invalid, revoked or expired"
}
```The below API response is returned when authentication succeeds i.e Access Token is validated and Spring context is populated successfully, However the token does not have the permission required to access the API.
```json
{
"Status": "403 FORBIDDEN",
"Description": "Access Denied: You do not have sufficient privileges to access this resource."
}
```
If the user's permissions have changed, the client can leverage available refresh token to request a new JWT, reflecting the new permissions that the user has obtained.### Compromised Password Detection
To protect user accounts from the use of vulnerable passwords that have been exposed in data breaches, the project uses the new compromised password detection feature added in `spring-security:6.3`. The default implementation provided uses the [Have I Been Pwned API](https://haveibeenpwned.com/API/v3#PwnedPasswords) under the hood.
The compromised password check is performed during two key scenarios:
* **User creation**: When a new user is being registered via the user creation API, the provided password is checked.
* **User Login**: Even if a password was not compromised at the time of user creation, it may become compromised at a later point. To address this, the login API also incorporates the compromised password check.In case of compromised password detection in any of the above scenarios, the server responds with the below error:
```json
{
"Status": "422 UNPROCESSABLE_ENTITY",
"Description": "The provided password is compromised and cannot be used for account creation."
}
```To recover from a compromised password situation during login, a new API endpoint PUT `/users/reset-password` is exposed. This endpoint accepts the below request body payload:
```json
{
"EmailId": "[email protected]",
"CurrentPassword": "somethingCompromised",
"NewPassword": "somethingSecured"
}
```
The new password is also checked for compromise before allowing the password reset.---
### Local Setup
The below given commands can be executed in the project's base directory to build an image and start required container(s). Docker compose will initiate a MySQL and Redis container as well, with the backend swagger-ui accessible at `http://localhost:8080/swagger-ui.html````bash
JWT_PRIVATE_KEY=$(openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048)
```
```bash
JWT_PUBLIC_KEY=$(echo "$JWT_PRIVATE_KEY" | openssl rsa -pubout -outform PEM)
```
```bash
sudo docker-compose build
```
```bash
sudo JWT_PRIVATE_KEY="$JWT_PRIVATE_KEY" JWT_PUBLIC_KEY="$JWT_PUBLIC_KEY" docker-compose up -d
```
The above commands also generate an RSA key pair locally and pass them as environment variables when starting Docker Compose, so that the application can make use of the generated key pair for signing and verifying JWTs.To remove the environment variables from memory after the application has started, the below commands can be executed
```bash
unset JWT_PRIVATE_KEY
```
```bash
unset JWT_PUBLIC_KEY
```---
### Visual Walkthrough
https://github.com/hardikSinghBehl/jwt-auth-flow-spring-security/assets/69693621/54ef4877-49b9-4112-9f85-9b9abda74068