Generate JWT Token In Java for hasura authorization - java

We are using spring boot as backend to for only auth and generating jwt token rest is handled in hasura.
I am facing problem in generating JWT properly.
public String generateToken(String email,String role,Long id) {
Map<String, Object> claims = new HashMap<>();
Map<String,Object> claim =new HashMap<>();
claims.put("x-hasura-user-id",id);
claims.put("x-hasura-default-role",role);
claims.put("x-hasura-allowed-roles", new String[]{"job_seeker", "employer", "admin"});
claim.put("https://hasura.io/jwt/claims",claims);
System.out.println(claim);
return doGenerateToken(claim, email);
}
private String doGenerateToken(Map<String, Object> claim, String subject) {
return Jwts.builder().setClaims(claim).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + Long.parseLong(tokenValidity))).signWith(SignatureAlgorithm.HS256, secret).compact();
}
This is generating jwt token as
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJyb3NhbjEyM0BnbWFpbC5jb20iLCJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWRlZmF1bHQtcm9sZSI6ImFkbWluIiwieC1oYXN1cmEtdXNlci1pZCI6NCwieC1oYXN1cmEtYWxsb3dlZC1yb2xlcyI6WyJqb2Jfc2Vla2VyIiwiZW1wbG95ZXIiLCJhZG1pbiJdfSwiZXhwIjoxNjA5ODU1OTA2LCJpYXQiOjE2MDk4NTExMDZ9.WqJE1xLIsycW92tzFXdq0UHub3qUfQbUvUax9rvks4Q
but it hasura is returning Invalid signature. Where as in node
generateToken: (user: any) => {
const payload = {
sub: user.email,
"https://hasura.io/jwt/claims": {
"x-hasura-default-role": `${user.role.name}`,
"x-hasura-user-id": `${user.id}`,
"x-hasura-allowed-roles": ["job_seeker", "employer", "admin"],
},
};
return jwt.sign(payload, secretkey);
},
jwt from node
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJib2hhcmFuaXNjaGFsQGdtYWlsLmNvbSIsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoiam9iX3NlZWtlciIsIngtaGFzdXJhLXVzZXItaWQiOiIyNCIsIngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsiam9iX3NlZWtlciIsImVtcGxveWVyIiwiYWRtaW4iXX0sImlhdCI6MTYwOTg1NDEzMX0.8UDrqvRujakGsEtGEAu1XWl5RsFda8HaA_-97vwY62I
using same secret key and algorithm is working perfectly fine. For node i have used jsonwebtoken library.

I found the solution. String should be converted to byte[]:
private String doGenerateToken(Map<String,Object> header,Map<String, Object> claim, String subject) {
return Jwts.builder().setHeader(header).setClaims(claim).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + Long.parseLong(tokenValidity))).signWith(SignatureAlgorithm.HS256, secret.getBytes(StandardCharsets.UTF_8)).compact();
}

Related

Test with MockMvc in Java / Spring Boot - header "Authorization"

How can i test an endpoint with authorization header with a user context?
mockMvc.perform(post("/endpoint")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.header("Authorization", authorizationToken(APP_AUTHORIZATION)))
.andDo(print()).andExpect(status().is2xxSuccessful());
I tried to do this, but it didn't work.
private String buildToken(#Nonnull ApiClient.JwtData data) throws JsonProcessingException {
final var token = JWT.create()
.withSubject(data.getUserId())
.withIssuedAt(new Date())
.withClaim("name", data.getUsername())
.withClaim("fullname", data.getUserFullName())
.withClaim("userid", data.getUserId())
.withClaim("locale", Objects.requireNonNullElse(data.getLocale(), DEFAULT_LOCALE));
final var authsMap = new HashMap<String, String[]>();
token.withClaim("authorities", objectMapper.writeValueAsString(authsMap));
return token.sign(Algorithm.HMAC256(SECRET));
}
protected String authorizationToken(String... authorities) throws JsonProcessingException {
final var jwtData = new ApiClient.JwtData("USER_ID", "USER_NAME", "USER_FULLNAME"
, "locale", "USER_AUTHORITIES", "US-EN", authorities);
return buildToken(jwtData);
}

Using a custom JWT Decoder in Spring boot resource server

I'm using the Spring boot resource server. The authentication server issues a JWT. This JWT is re-encoded(with AES) with a key and in the Resource server, I should decode the JWT (from AES) before sending it to the JwtAuthenticator.
Now, I have a security configuration.
#Override
protected void configure(HttpSecurity http) throws Exception {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new KeycloakRoleConverter());
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/users/status/check")
.hasRole("developer")
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.decoder(new JWTDecoder())
.jwtAuthenticationConverter(jwtAuthenticationConverter);
}
and a JWT Decoder
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTParser;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtException;
import java.text.ParseException;
public class JWTDecoder implements JwtDecoder {
#Override
public Jwt decode(String token) throws JwtException {
//decrypt from AES here
JWT jwt = null;
try {
jwt = JWTParser.parse(token);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
What should I do then? The function should return org.springframework.security.oauth2.jwt.Jwt. How can I convert String token to Jwt?
I tried the following, but a problem occurred.
private Jwt createJwt(String token, JWT parsedJwt) {
try {
Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
Map<String, Object> claims = parsedJwt.getJWTClaimsSet().getClaims();
return Jwt.withTokenValue(token)
.headers(h -> h.putAll(headers))
.claims(c -> c.putAll(claims))
.build();
} catch (Exception ex) {
if (ex.getCause() instanceof ParseException) {
throw new JwtException("There is a problem parsing the JWT.");
} else {
throw new JwtException("There is a problem decoding the JWT.");
}
}
}
The error I received:
java.lang.IllegalArgumentException: timestamps must be of type Instant: java.lang.Long
I'm using Keycloak to generate the JWT. So, the exp field of the token in the jwt.io is "exp": 1657363340,. But after parsing the JWT in my code, It changes to the Date format. So, I changed the exp to Instant and my final method is like the following:
private Jwt createJwt(String token, JWT parsedJwt) {
try {
Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
Map<String, Object> claims = parsedJwt.getJWTClaimsSet().getClaims();
Jwt.Builder finalJwt = Jwt.withTokenValue(token)
.headers(h -> h.putAll(headers))
.claims(c -> c.putAll(claims));
finalJwt.expiresAt(((Date) claims.get("exp")).toInstant());
return finalJwt.build();
} catch (Exception ex) {
if (ex.getCause() instanceof ParseException) {
throw new JwtException("There is a problem parsing the JWT: " + ex.getMessage());
} else {
throw new JwtException("There is a problem decoding the JWT: " + ex.getMessage());
}
}
}
But the problem exists yet.
It could be due to expiration date of your token is a timestamp, and it should be a number (Long). Or you are trying to parse a timestamp to number Long.
As #Jose told me, I set the value of expiration time with an Instant type of the timestamp. Then, I set it to both the exp and iat fields of the JWT. My final function is like the following:
Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
Map<String, Object> claims = new HashMap<>();
for (String key : parsedJwt.getJWTClaimsSet().getClaims().keySet()) {
Object value = parsedJwt.getJWTClaimsSet().getClaims().get(key);
if (key.equals("exp") || key.equals("iat")) {
value = ((Date) value).toInstant();
}
claims.put(key, value);
}
return Jwt.withTokenValue(token)
.headers(h -> h.putAll(headers))
.claims(c -> c.putAll(claims))
.build();

JWT secrect is different but it can also be parse

#Test
public void testJwtBuilder() {
JwtBuilder jwtBuilder = Jwts.builder()
.setId("123456")
.setSubject("Snake")
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, "123456789");
String token = jwtBuilder.compact();
System.out.println(token);
for (String s : token.split("\\.")) {
System.out.println(Base64Codec.BASE64.decodeToString(s));
}
}
This is the token I generated: eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjM0NTYiLCJzdWIiOiJTbmFrZSIsImlhdCI6MTYyNjg4NTMwMH0.R0WmOmXaH93DiY_On98p7wSmKMsYpQN4a0T8-b82-bA
I set secret to "123456789",but I can parse it with "123456789x" or "12345678".
Here is my parsing code:
#Test
public void parseToken() {
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjM0NTYiLCJzdWIiOiJTbmFrZSIsImlhdCI6MTYyNjg4NTMwMH0.R0WmOmXaH93DiY_On98p7wSmKMsYpQN4a0T8-b82-bA";
Claims claims = Jwts.parser()
.setSigningKey("123456789x")
.parseClaimsJws(token)
.getBody();
System.out.println(claims);
}
Why does this happen?

Get Payload JWT

I work with springboot and use oath2 (JWT).
My token is the next
token
I want to get the payload from a controller or a filter but i dont know how.
I try this but it doesn't work
#GetMapping("/user")
public String getB(#AuthenticationPrincipal Jwt principal, #RequestHeader("refresh") String refresh) {
System.out.println(principal.getClaims());;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object details = authentication.getDetails();
if ( details instanceof OAuth2AuthenticationDetails ){
OAuth2AuthenticationDetails oAuth2AuthenticationDetails = (OAuth2AuthenticationDetails)details;
Map<String, Object> decodedDetails = (Map<String, Object>)oAuth2AuthenticationDetails.getDecodedDetails();
System.out.println( decodedDetails.get("time_created") );
}
(SORRY FOR MY ENGLISH)
I can get a details with the next code:
get access token
decode this token
add decode token in a map
get detail that i need by key in the map
#GetMapping("/user")
public String getB(OAuth2Authentication auth) throws JsonMappingException, JsonProcessingException {
final OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
// token
String accessToken = details.getTokenValue();
System.out.println(accessToken);
JsonParser parser = JsonParserFactory.getJsonParser();
Map<String, ?> tokenData = parser.parseMap(JwtHelper.decode(accessToken).getClaims());
String customField = (String) tokenData.get("time_created");

How to generate and access key for JWT

I'm developing a REST API and I decided to use JWT for authentication/security. There is a service to handle the login validation, and a filter to bound to every service that will need authentication.
LoginService.java:
#Path("login")
public class LoginService {
private final static long EXPIRATION_TIME = 60000;
#POST
#Produces("application/json")
#Consumes("application/json")
public Response authenticateUser(Credentials c) {
Users login;
UsersDAO u = new UsersDAO();
try {
login = u.getAuthentication(c);
String token = generateToken(login.getIdUser(), login.getLogin(), login.getRole());
// Return the token on the response
return Response.ok().header(AUTHORIZATION, "Bearer " + token).build();
} catch (Exception e){
System.out.println("Exception: " + e.toString());
return Response.status(Response.Status.UNAUTHORIZED).build();
}
}
private String generateToken(int id, String login, int role) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
//TODO generate key (or retrieve it from file/database?)
Key key;
String jwtToken = Jwts.builder()
.setSubject(login)
.setIssuer("my_company")
.setIssuedAt(now)
.setExpiration(new Date(nowMillis + EXPIRATION_TIME))
.claim("role", role)
.signWith(SignatureAlgorithm.HS512, key)
.compact();
return jwtToken;
}
JWTTokenFilter.java:
#Provider
#JWTTokenNeeded
#Priority(Priorities.AUTHENTICATION)
public class JWTTokenFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
String token = authorizationHeader.substring("Bearer".length()).trim();
try {
// TODO generate key (or retrieve it from file/database?)
Key key;
Jwts.parser().setSigningKey(key).parseClaimsJws(token);
} catch (Exception e) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
}
}
I've been doing some research, but I'm still not sure how to manage the key generation/validation. My doubts:
If I create the key when authenticating, how could I pass this same key to the filter? I've seen some code examples where key is generated both when authenticating and validating using random numbers, which have no sense to me as the key generated wouldn't be the same. What am I missing there?
Other option would be to store the key in filesystem, so both authenticating and validating processes would be able to access to the same key. What downsides (if any) would this bring to the implementation? Is there any good library or framework to manage the key generation and access in filesystem(or even database)?
Notice that I don't want to pass the key to the clients, so they had to authenticate once in a while in order to refresh the token as they won't have access to the expiration date. This topic doesn't fit to my case, and this is quite complete but doesn't bring any example
If you generate the symmetric key at runtime you could share it between the filter and the login class using spring injection or with a static variable
But consider that restarting server will invalidate all issued JWT. If this is not the desired behaviour you need to persist the key in a properties file or in the database
Using Jjwt, you can do:
//generate a random HMAC
Key key = MacProvider.generateKey(SignatureAlgorithm.HS256);
//Get the key data
byte keyData[]= key.getEncoded();
//Store data in a file...
//Build key
Key key = new SecretKeySpec(keyData, SignatureAlgorithm.HS256.getJcaName());

Categories