I am currently trying to learn backend development using Java Spring Boot Framework. And for POC I am building Security Token Service (STS) Server whose role is to generate JWT tokens for valid User Auth Requests.
I stuck in a problem where I am trying to fetch JWT Secret Key from application.yml file using @Value annotation. As hardcoding secrets in the code is considered as bad practice. So I was thinking to inject this secret from application.yml file using @Value annotation and then using this secret key to sign the tokens but I am getting NULL VALUE EXCEPTION here. Below is the code snippet of what I am trying to do
@Slf4j
@Data
@Component
public class JwtUtil {
@Value("${JWT.secret: JWT_SECRET_KEY}")
public String jwtSecret;
private Integer expiryTimeInMinutes = 100000;
private Algorithm tokenSigningAlgo = Algorithm.HMAC256(jwtSecret);
public String generateJWT(String subject, Map<String, Object> claims) {
return JWT
.create()
.withSubject(subject)
.withPayload(claims)
.withExpiresAt(new Date(System.currentTimeMillis() expiryTimeInMinutes * 60 * 1000))
.sign(tokenSigningAlgo);
}
}
And this is the exception which I am getting
Constructor threw exception; nested exception is java.lang.IllegalArgumentException: The Secret cannot be null
I have stored this JWT.secret value inside application.yml.
JWT:
secret: "SECRET_KEY"
expiryTimeInMinutes: 20
From my debugging I got to know that @Value annotation initialises the value of secret after the bean creation of class JwtUtil. Because I tried to log the value of secret after removing the JWT creation logic (which is throwing Null Value Exception) and then it's working fine. I am getting the expected value from the application.yml.
Is there any way by which I can get @Value annotations value before the JwtUtil bean creation? or can you guys suggest me best practices to achieve this.
Thanks in Advance!
CodePudding user response:
try @ConfigurationProperties
@Component
@Getter
@Setter
@ConfigurationProperties(prefix = "oauth2", ignoreUnknownFields = false)
public class OauthProperties {
private String jwtSecret;
private Long jwtExpirationInMs;
}
application.yml
oauth2:
jwtSecret: SecretKey
jwtExpirationInMs: 86400000
Inject your configuration class here
@Component
@AllArgsConstructor
public class JwtTokenProvider {
private final Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class);
private OauthProperties oauthProperties;
...
CodePudding user response:
Creating a bean happens after calling the constructor of the target class.
The constructor of class JwtUtil
was called and then was called this part private Algorithm tokenSigningAlgo = Algorithm.HMAC256(jwtSecret);
Bean of JwtUtil
has not been created yet.
One of the ways replaces Algorithm.HMAC256(jwtSecret)
to init method with PostCostruct
annotation
CodePudding user response:
The problem you are facing can be solved by adding a constructor, e.g.
@Slf4j
@Data
@Component
public class JwtUtil {
private static finalInteger expiryTimeInMinutes = 100000;
private Algorithm tokenSigningAlgo;
JwtUtil(@Value("${JWT.secret: JWT_SECRET_KEY}") String jwtSecret) {
tokenSigningAlgo = Algorithm.HMAC256(jwtSecret);
};
public String generateJWT(String subject, Map<String, Object> claims){
return JWT
.create()
.withSubject(subject)
.withPayload(claims)
.withExpiresAt(new Date(System.currentTimeMillis() expiryTimeInMinutes * 60 * 1000))
.sign(tokenSigningAlgo);
}
}
This change the instantiation flow slightly as it will make sure that the jwtSecret
constructor param has been assigned the correct value before the Algorithm.HMAC256(jwtSecret)
method is executed.
CodePudding user response:
change the annotation value to @Value("${JWT.secret}")
you just have to specify the
key
if you want a default value you can use SpEL expression like@Value("${JWT.secret ?: 'some default'}")
a good practice would also be to have the String jwtSecret
injected in the constructor for easier testing like:
public JwtUtil(@Value("${JWT.secret}") String jwtSecret) {
tokenSigningAlgo = Algorithm.HMAC256(jwtSecret);
}