@ -16,16 +16,19 @@
package org.thingsboard.server.service.security.model.token ;
package org.thingsboard.server.service.security.model.token ;
import io.jsonwebtoken.Claims ;
import io.jsonwebtoken.Claims ;
import io.jsonwebtoken.ClaimsBuilder ;
import io.jsonwebtoken.ExpiredJwtException ;
import io.jsonwebtoken.ExpiredJwtException ;
import io.jsonwebtoken.Jws ;
import io.jsonwebtoken.Jws ;
import io.jsonwebtoken.JwtBuilder ;
import io.jsonwebtoken.JwtBuilder ;
import io.jsonwebtoken.JwtParser ;
import io.jsonwebtoken.Jwts ;
import io.jsonwebtoken.Jwts ;
import io.jsonwebtoken.MalformedJwtException ;
import io.jsonwebtoken.MalformedJwtException ;
import io.jsonwebtoken.SignatureAlgorithm ;
import io.jsonwebtoken.SignatureException ;
import io.jsonwebtoken.SignatureException ;
import io.jsonwebtoken.UnsupportedJwtException ;
import io.jsonwebtoken.UnsupportedJwtException ;
import io.jsonwebtoken.security.Keys ;
import lombok.RequiredArgsConstructor ;
import lombok.RequiredArgsConstructor ;
import lombok.extern.slf4j.Slf4j ;
import lombok.extern.slf4j.Slf4j ;
import org.springframework.context.annotation.Lazy ;
import org.springframework.security.authentication.BadCredentialsException ;
import org.springframework.security.authentication.BadCredentialsException ;
import org.springframework.security.core.GrantedAuthority ;
import org.springframework.security.core.GrantedAuthority ;
import org.springframework.stereotype.Component ;
import org.springframework.stereotype.Component ;
@ -41,7 +44,10 @@ import org.thingsboard.server.service.security.exception.JwtExpiredTokenExceptio
import org.thingsboard.server.service.security.model.SecurityUser ;
import org.thingsboard.server.service.security.model.SecurityUser ;
import org.thingsboard.server.service.security.model.UserPrincipal ;
import org.thingsboard.server.service.security.model.UserPrincipal ;
import javax.crypto.SecretKey ;
import javax.crypto.spec.SecretKeySpec ;
import java.time.ZonedDateTime ;
import java.time.ZonedDateTime ;
import java.util.Base64 ;
import java.util.Collections ;
import java.util.Collections ;
import java.util.Date ;
import java.util.Date ;
import java.util.List ;
import java.util.List ;
@ -53,6 +59,8 @@ import java.util.stream.Collectors;
@Slf4j
@Slf4j
public class JwtTokenFactory {
public class JwtTokenFactory {
public static int KEY_LENGTH = Jwts . SIG . HS512 . getKeyBitLength ( ) ;
private static final String SCOPES = "scopes" ;
private static final String SCOPES = "scopes" ;
private static final String USER_ID = "userId" ;
private static final String USER_ID = "userId" ;
private static final String FIRST_NAME = "firstName" ;
private static final String FIRST_NAME = "firstName" ;
@ -63,8 +71,12 @@ public class JwtTokenFactory {
private static final String CUSTOMER_ID = "customerId" ;
private static final String CUSTOMER_ID = "customerId" ;
private static final String SESSION_ID = "sessionId" ;
private static final String SESSION_ID = "sessionId" ;
@Lazy
private final JwtSettingsService jwtSettingsService ;
private final JwtSettingsService jwtSettingsService ;
private volatile JwtParser jwtParser ;
private volatile SecretKey secretKey ;
/ * *
/ * *
* Factory method for issuing new JWT Tokens .
* Factory method for issuing new JWT Tokens .
* /
* /
@ -95,7 +107,7 @@ public class JwtTokenFactory {
public SecurityUser parseAccessJwtToken ( String token ) {
public SecurityUser parseAccessJwtToken ( String token ) {
Jws < Claims > jwsClaims = parseTokenClaims ( token ) ;
Jws < Claims > jwsClaims = parseTokenClaims ( token ) ;
Claims claims = jwsClaims . getBody ( ) ;
Claims claims = jwsClaims . getPayload ( ) ;
String subject = claims . getSubject ( ) ;
String subject = claims . getSubject ( ) ;
@SuppressWarnings ( "unchecked" )
@SuppressWarnings ( "unchecked" )
List < String > scopes = claims . get ( SCOPES , List . class ) ;
List < String > scopes = claims . get ( SCOPES , List . class ) ;
@ -140,14 +152,14 @@ public class JwtTokenFactory {
String token = setUpToken ( securityUser , Collections . singletonList ( Authority . REFRESH_TOKEN . name ( ) ) , jwtSettingsService . getJwtSettings ( ) . getRefreshTokenExpTime ( ) )
String token = setUpToken ( securityUser , Collections . singletonList ( Authority . REFRESH_TOKEN . name ( ) ) , jwtSettingsService . getJwtSettings ( ) . getRefreshTokenExpTime ( ) )
. claim ( IS_PUBLIC , principal . getType ( ) = = UserPrincipal . Type . PUBLIC_ID )
. claim ( IS_PUBLIC , principal . getType ( ) = = UserPrincipal . Type . PUBLIC_ID )
. setI d( UUID . randomUUID ( ) . toString ( ) ) . compact ( ) ;
. i d( UUID . randomUUID ( ) . toString ( ) ) . compact ( ) ;
return new AccessJwtToken ( token ) ;
return new AccessJwtToken ( token ) ;
}
}
public SecurityUser parseRefreshToken ( String token ) {
public SecurityUser parseRefreshToken ( String token ) {
Jws < Claims > jwsClaims = parseTokenClaims ( token ) ;
Jws < Claims > jwsClaims = parseTokenClaims ( token ) ;
Claims claims = jwsClaims . getBody ( ) ;
Claims claims = jwsClaims . getPayload ( ) ;
String subject = claims . getSubject ( ) ;
String subject = claims . getSubject ( ) ;
@SuppressWarnings ( "unchecked" )
@SuppressWarnings ( "unchecked" )
List < String > scopes = claims . get ( SCOPES , List . class ) ;
List < String > scopes = claims . get ( SCOPES , List . class ) ;
@ -176,6 +188,11 @@ public class JwtTokenFactory {
return new AccessJwtToken ( jwtBuilder . compact ( ) ) ;
return new AccessJwtToken ( jwtBuilder . compact ( ) ) ;
}
}
public void reload ( ) {
getSecretKey ( true ) ;
getJwtParser ( true ) ;
}
private JwtBuilder setUpToken ( SecurityUser securityUser , List < String > scopes , long expirationTime ) {
private JwtBuilder setUpToken ( SecurityUser securityUser , List < String > scopes , long expirationTime ) {
if ( StringUtils . isBlank ( securityUser . getEmail ( ) ) ) {
if ( StringUtils . isBlank ( securityUser . getEmail ( ) ) ) {
throw new IllegalArgumentException ( "Cannot create JWT Token without username/email" ) ;
throw new IllegalArgumentException ( "Cannot create JWT Token without username/email" ) ;
@ -183,28 +200,27 @@ public class JwtTokenFactory {
UserPrincipal principal = securityUser . getUserPrincipal ( ) ;
UserPrincipal principal = securityUser . getUserPrincipal ( ) ;
Claims claims = Jwts . claims ( ) . setSubject ( principal . getValue ( ) ) ;
ClaimsBuilder claimsBuilder = Jwts . claims ( )
claims . put ( USER_ID , securityUser . getId ( ) . getId ( ) . toString ( ) ) ;
. subject ( principal . getValue ( ) )
claims . put ( SCOPES , scopes ) ;
. add ( USER_ID , securityUser . getId ( ) . getId ( ) . toString ( ) )
. add ( SCOPES , scopes ) ;
if ( securityUser . getSessionId ( ) ! = null ) {
if ( securityUser . getSessionId ( ) ! = null ) {
claims . put ( SESSION_ID , securityUser . getSessionId ( ) ) ;
claimsBuilder . add ( SESSION_ID , securityUser . getSessionId ( ) ) ;
}
}
ZonedDateTime currentTime = ZonedDateTime . now ( ) ;
ZonedDateTime currentTime = ZonedDateTime . now ( ) ;
return Jwts . builder ( )
return Jwts . builder ( )
. setC laims( claims )
. c laims( claimsBuilder . build ( ) )
. setI ssuer( jwtSettingsService . getJwtSettings ( ) . getTokenIssuer ( ) )
. i ssuer( jwtSettingsService . getJwtSettings ( ) . getTokenIssuer ( ) )
. setI ssuedAt( Date . from ( currentTime . toInstant ( ) ) )
. i ssuedAt( Date . from ( currentTime . toInstant ( ) ) )
. s etE xpiration( Date . from ( currentTime . plusSeconds ( expirationTime ) . toInstant ( ) ) )
. expiration ( Date . from ( currentTime . plusSeconds ( expirationTime ) . toInstant ( ) ) )
. signWith ( SignatureAlgorithm . HS512 , jwtSettingsService . getJwtSettings ( ) . getTokenSigningKey ( ) ) ;
. signWith ( getSecretKey ( false ) , Jwts . SIG . HS512 ) ;
}
}
public Jws < Claims > parseTokenClaims ( String token ) {
public Jws < Claims > parseTokenClaims ( String token ) {
try {
try {
return Jwts . parser ( )
return getJwtParser ( false ) . parseSignedClaims ( token ) ;
. setSigningKey ( jwtSettingsService . getJwtSettings ( ) . getTokenSigningKey ( ) )
. parseClaimsJws ( token ) ;
} catch ( UnsupportedJwtException | MalformedJwtException | IllegalArgumentException ex ) {
} catch ( UnsupportedJwtException | MalformedJwtException | IllegalArgumentException ex ) {
log . debug ( "Invalid JWT Token" , ex ) ;
log . debug ( "Invalid JWT Token" , ex ) ;
throw new BadCredentialsException ( "Invalid JWT token: " , ex ) ;
throw new BadCredentialsException ( "Invalid JWT token: " , ex ) ;
@ -220,4 +236,28 @@ public class JwtTokenFactory {
return new JwtPair ( accessToken . getToken ( ) , refreshToken . getToken ( ) ) ;
return new JwtPair ( accessToken . getToken ( ) , refreshToken . getToken ( ) ) ;
}
}
private SecretKey getSecretKey ( boolean forceReload ) {
if ( secretKey = = null | | forceReload ) {
synchronized ( this ) {
if ( secretKey = = null | | forceReload ) {
byte [ ] decodedToken = Base64 . getDecoder ( ) . decode ( jwtSettingsService . getJwtSettings ( ) . getTokenSigningKey ( ) ) ;
secretKey = new SecretKeySpec ( decodedToken , "HmacSHA512" ) ;
}
}
}
return secretKey ;
}
private JwtParser getJwtParser ( boolean forceReload ) {
if ( jwtParser = = null | | forceReload ) {
synchronized ( this ) {
if ( jwtParser = = null | | forceReload ) {
jwtParser = Jwts . parser ( )
. verifyWith ( Keys . hmacShaKeyFor ( Base64 . getDecoder ( ) . decode ( jwtSettingsService . getJwtSettings ( ) . getTokenSigningKey ( ) ) ) )
. build ( ) ;
}
}
}
return jwtParser ;
}
}
}