Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apigateway/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.5</version>
<version>0.13.0</version>
<scope>runtime</scope>
</dependency>

Expand Down
15 changes: 13 additions & 2 deletions apigateway/src/main/java/vaultweb/apigateway/model/User.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package vaultweb.apigateway.model;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
Expand All @@ -13,11 +13,22 @@
@AllArgsConstructor
@NoArgsConstructor
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;

private String name;
private String email;
private String password;

}
public static record PublicData(int id, String name, String email) {
public static PublicData from(User user) {
return new PublicData(
user.getId(),
user.getName(),
user.getEmail()
);
}
}
}
112 changes: 112 additions & 0 deletions apigateway/src/main/java/vaultweb/apigateway/service/JwtService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package vaultweb.apigateway.service;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import java.util.Date;
import javax.crypto.SecretKey;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class JwtService {

@Value("${jwt.secret}")
private String SECRET_KEY;

private JwtParser parser = Jwts.parser()
.verifyWith(getSigningKey())
.build();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SECRET_KEY is injected by Spring after object construction via @value("${jwt.secret}").
This means that when the JwtParser is initialized at field level, SECRET_KEY is still null, causing getSigningKey() to be called with null, leading to a potential NullPointerException.

Consider initializing the parser lazily (e.g., in a getter) or inside a @PostConstruct method after the secret has been set.

private static final long EXPIRATION_TIME_MILLIS = 3600000; // 1 hour

/**
* Creates a secret key to sign JWT tokens.
* <p>
* It always creates the exact same key, with the same secret key.
* This method decodes the secret key from the application.properties file into a BASE 64 encoded string.
* It is done to ensure that the secret is properly formed, since {@code SECRET_KEY} could contain non UTF-8 characters.
* @return {@code SecretKey}
*/
private SecretKey getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(SECRET_KEY);
return Keys.hmacShaKeyFor(keyBytes);
}

/**
* Checks if the token is valid.
* <p>
* It verifies the token's signature and expiration time.
* @param token the token to be validated
* @return {@code true} if the token is valid, {@code false} otherwise
*/
public boolean isValid(final String token) {
return isUnmodified(token) && !isExpired(token);
}

/**
* Generates a JWT token.
* <p>
* The generated token is valid for {@code EXPIRATION_TIME_MILLIS} milliseconds.
* Additionally, the token is always signed with the {@code SECRET_KEY} to ensure its integrity.
* The token is always going to contain the user's id and name.
* @param id of the user
* @param username of the user
* @return the generated signed jwt token as a {@link String}
*/
public String generateToken(final Integer id, final String username) {
return Jwts.builder()
.subject(username)
.claim("userId", id)
.issuedAt(new Date())
.expiration(
new Date(System.currentTimeMillis() + EXPIRATION_TIME_MILLIS)
)
.signWith(getSigningKey())
.compact();
}

/**
* Extracts all claims from the given token.
* @param token the token to extract claims from
* @return the extracted claims as a {@link Claims} object
*/
private Claims extractAllClaims(final String token) {
return this.parser.parseSignedClaims(token).getPayload();
}

/**
* Extracts the expiration date of the token.
* @param token the token to extract claims from
* @return the expiration date of the token and return it as a {@link Date}
*/
private Date extractExpiration(final String token) {
return extractAllClaims(token).getExpiration();
}

/**
* Checks if the token is expired.
* @param token the token to check
* @return true if the token is expired and false otherwise
*/
private boolean isExpired(final String token) {
return this.extractExpiration(token).before(new Date());
}

/**
* Checks if the token is unmodified.
* @param token the token to check
* @return true if the token is unmodified and false otherwise
*/
private boolean isUnmodified(final String token) {
try {
this.parser.parseSignedClaims(token);
return true;
} catch (final JwtException e) {
return false;
}
}
}

This file was deleted.