|
2 | 2 |
|
3 | 3 | import java.net.URI; |
4 | 4 | import java.text.SimpleDateFormat; |
5 | | -import java.time.Instant; |
6 | | -import java.time.temporal.ChronoUnit; |
7 | 5 | import java.util.ArrayList; |
8 | 6 | import java.util.Calendar; |
9 | 7 | import java.util.Collections; |
10 | 8 | import java.util.Date; |
11 | 9 | import java.util.List; |
12 | 10 | import java.util.Locale; |
13 | 11 | import java.util.Map; |
14 | | -import java.util.UUID; |
15 | | -import java.util.concurrent.TimeUnit; |
16 | 12 | import java.util.stream.Collectors; |
17 | 13 |
|
18 | 14 | import javax.servlet.http.HttpServletRequest; |
|
38 | 34 | import com.auth0.jwt.algorithms.Algorithm; |
39 | 35 | import com.auth0.jwt.interfaces.DecodedJWT; |
40 | 36 | import com.fasterxml.jackson.databind.ObjectMapper; |
41 | | -import com.hazelcast.map.IMap; |
42 | 37 |
|
43 | 38 | import it.eng.knowage.monitor.IKnowageMonitor; |
44 | 39 | import it.eng.knowage.monitor.KnowageMonitorFactory; |
|
88 | 83 | import it.eng.spagobi.tenant.TenantManager; |
89 | 84 | import it.eng.spagobi.tools.dataset.notifier.fiware.OAuth2Utils; |
90 | 85 | import it.eng.spagobi.utilities.exceptions.SpagoBIRuntimeException; |
91 | | -import it.eng.spagobi.utilities.locks.DistributedLockFactory; |
92 | 86 |
|
93 | 87 | @Path("/login") |
94 | 88 | public class LoginResource extends AbstractSpagoBIResource { |
@@ -477,128 +471,6 @@ public Response validateOIDCIdToken(@Context HttpServletRequest req, Map<String, |
477 | 471 | } |
478 | 472 | } |
479 | 473 |
|
480 | | - @POST |
481 | | - @Path("/refreshToken") |
482 | | - @PublicService |
483 | | - public Response refreshToken(@Context HttpServletRequest req, Map<String, String> payload) { |
484 | | - |
485 | | - try { |
486 | | - String refreshToken = payload.get("refreshToken"); |
487 | | - if (StringUtils.isBlank(refreshToken)) { |
488 | | - return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "Missing refreshToken")).build(); |
489 | | - } |
490 | | - |
491 | | - logger.debug("Refreshing access token using refreshToken"); |
492 | | - |
493 | | - // 1) Get the Hazelcast distributed map |
494 | | - IMap<String, String> tokenMap = DistributedLockFactory.getDistributedMap(SpagoBIConstants.DISTRIBUTED_MAP_INSTANCE_NAME, |
495 | | - SpagoBIConstants.DISTRIBUTED_MAP_FOR_REFRESH_TOKEN); |
496 | | - |
497 | | - // 2) Check if refresh token exists (revoked or never issued?) |
498 | | - String userIdFromCache = tokenMap.get(refreshToken); |
499 | | - if (userIdFromCache == null) { |
500 | | - logger.warn("Refresh token not found or revoked"); |
501 | | - return Response.status(Response.Status.UNAUTHORIZED).entity(Map.of("error", "Invalid or expired refreshToken")).build(); |
502 | | - } |
503 | | - |
504 | | - // 3) Verify JWT refresh token signature |
505 | | - Algorithm algorithm = ALGORITHM_FACTORY.getAlgorithm(); |
506 | | - DecodedJWT decoded; |
507 | | - |
508 | | - try { |
509 | | - decoded = JWT.require(algorithm).withIssuer(JWTSsoService.KNOWAGE_ISSUER).build().verify(refreshToken); |
510 | | - } catch (Exception e) { |
511 | | - logger.error("Invalid refresh token signature", e); |
512 | | - return Response.status(Response.Status.UNAUTHORIZED).entity(Map.of("error", "Invalid refreshToken")).build(); |
513 | | - } |
514 | | - |
515 | | - // 4) Extract data from refresh token |
516 | | - String userIdFromJWT = decoded.getClaim("user_id").asString(); |
517 | | - Date expiresAt = decoded.getExpiresAt(); |
518 | | - String deviceFromJWT = decoded.getClaim("device").asString(); |
519 | | - |
520 | | - // 5) Verify user ID consistency |
521 | | - if (!userIdFromJWT.equals(userIdFromCache)) { |
522 | | - logger.error("Refresh token mismatch: JWT user != Hazelcast user"); |
523 | | - return Response.status(Response.Status.UNAUTHORIZED).entity(Map.of("error", "Invalid refreshToken")).build(); |
524 | | - } |
525 | | - |
526 | | - // 6) Check token expiration |
527 | | - if (expiresAt.before(new Date())) { |
528 | | - logger.warn("Expired refresh token"); |
529 | | - return Response.status(Response.Status.UNAUTHORIZED).entity(Map.of("error", "Expired refreshToken")).build(); |
530 | | - } |
531 | | - |
532 | | - // 7) (Optional but recommended) Verify DEVICE / USER-AGENT |
533 | | - String currentUA = req.getHeader("User-Agent"); |
534 | | - if (deviceFromJWT != null && !deviceFromJWT.equals(currentUA)) { |
535 | | - logger.error("Refresh token used from different device"); |
536 | | - return Response.status(Response.Status.UNAUTHORIZED).entity(Map.of("error", "RefreshToken used from different device")).build(); |
537 | | - } |
538 | | - |
539 | | - // 8) At this point the refresh token is valid → generate new access token |
540 | | - String newAccessToken = JWTSsoService.userId2jwtToken(userIdFromJWT); |
541 | | - |
542 | | - // 9) (Optional) Generate a new refresh token and revoke the previous one |
543 | | - // - Remove old token |
544 | | - tokenMap.remove(refreshToken); |
545 | | - // - Generate and save new token |
546 | | - String newRefreshToken = storeTokenInHazelcast(userIdFromJWT, req); |
547 | | - |
548 | | - logger.info("Successfully refreshed access token for user: " + userIdFromJWT); |
549 | | - |
550 | | - return Response.ok(Map.of("token", newAccessToken, "refresh_token", newRefreshToken)).build(); |
551 | | - |
552 | | - } catch (Exception e) { |
553 | | - logger.error("Error during refresh token process", e); |
554 | | - return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(Map.of("error", "Internal server error")).build(); |
555 | | - } |
556 | | - } |
557 | | - |
558 | | - |
559 | | - /** |
560 | | - * Generates a new refresh token and stores it in Hazelcast distributed cache |
561 | | - * The refresh token includes JWT ID, user ID, issue date, expiration time, and device information |
562 | | - * Tokens are stored with TTL matching the configured refresh expiry period |
563 | | - * |
564 | | - * @param userId The unique identifier of the user |
565 | | - * @param req The HTTP servlet request (used to extract User-Agent header) |
566 | | - * @return The generated refresh token as a JWT string, or null if generation fails |
567 | | - */ |
568 | | - private String storeTokenInHazelcast(String userId, HttpServletRequest req) { |
569 | | - try { |
570 | | - logger.debug("Generating and storing refresh token for user: " + userId); |
571 | | - |
572 | | - long refreshExpiryHours = 10L; |
573 | | - |
574 | | - |
575 | | - Algorithm algorithm = ALGORITHM_FACTORY.getAlgorithm(); |
576 | | - String jti = UUID.randomUUID().toString(); |
577 | | - Instant now = Instant.now(); |
578 | | - Instant exp = now.plus(refreshExpiryHours, ChronoUnit.HOURS); |
579 | | - |
580 | | - String userAgent = (req != null && req.getHeader("User-Agent") != null) ? req.getHeader("User-Agent") : null; |
581 | | - |
582 | | - String refreshToken = JWT.create().withIssuer(JWTSsoService.KNOWAGE_ISSUER) |
583 | | - .withJWTId(jti).withClaim("user_id", userId).withIssuedAt(Date.from(now)).withExpiresAt(Date.from(exp)) |
584 | | - .withClaim("device", userAgent).sign(algorithm); |
585 | | - |
586 | | - IMap<String, String> tokenMap = DistributedLockFactory.getDistributedMap(SpagoBIConstants.DISTRIBUTED_MAP_INSTANCE_NAME, |
587 | | - SpagoBIConstants.DISTRIBUTED_MAP_FOR_REFRESH_TOKEN); |
588 | | - |
589 | | - tokenMap.put(refreshToken, userId, refreshExpiryHours, TimeUnit.HOURS); |
590 | | - |
591 | | - logger.info( |
592 | | - "Refresh token generated and stored in Hazelcast map : " + SpagoBIConstants.DISTRIBUTED_MAP_FOR_REFRESH_TOKEN + " for user : " + userId); |
593 | | - |
594 | | - return refreshToken; |
595 | | - |
596 | | - } catch (Exception e) { |
597 | | - logger.error("Error generating/storing refresh token in Hazelcast for user " + userId, e); |
598 | | - return null; |
599 | | - } |
600 | | - } |
601 | | - |
602 | 474 | /** |
603 | 475 | * Performs OAuth2 authentication logic |
604 | 476 | */ |
|
0 commit comments