{"id":31,"date":"2024-09-14T19:47:59","date_gmt":"2024-09-14T19:47:59","guid":{"rendered":"https:\/\/marcinzygmunt.pl\/blog\/?p=31"},"modified":"2025-09-24T18:44:53","modified_gmt":"2025-09-24T18:44:53","slug":"extending-simple-jwt-authentication-with-refresh-tokens-with-spring-boot-hazelcast-cache-cluster-version","status":"publish","type":"post","link":"https:\/\/marcinzygmunt.pl\/blog\/extending-simple-jwt-authentication-with-refresh-tokens-with-spring-boot-hazelcast-cache-cluster-version\/","title":{"rendered":"Extending Simple JWT Authentication with Refresh Tokens with Spring Boot (Hazelcast cache cluster version)\u00a0"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">In the previous examples, we covered JWT authentication with refresh tokens stored in a database and in local cache. Now, we present a solution using a <strong>Hazelcast cluster<\/strong>, which allows <strong>refresh tokens to be shared across multiple application instances<\/strong>. This approach ensures <strong>consistency and scalability<\/strong> in multi-node environments.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Benefits of using a Hazelcast cluster:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Shared state:<\/strong> Refresh tokens are stored in a distributed cluster, making them accessible from any instance.<\/li>\n\n\n\n<li><strong>Scalability:<\/strong> Adding new application instances requires no additional cache configuration, as Hazelcast automatically synchronizes data across nodes.<\/li>\n\n\n\n<li><strong>Performance:<\/strong> With in-memory storage, token access is fast and database load is reduced.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Considerations \/ Trade-offs:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Configuration complexity:<\/strong> Integrating Hazelcast requires additional setup and is more complex than using local cache.<\/li>\n\n\n\n<li><strong>Cluster management:<\/strong> Monitoring and maintaining the cluster is required to ensure availability and performance.<\/li>\n<\/ul>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;6aac8d2c7164e&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"6aac8d2c7164e\" class=\"wp-block-image size-large wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"879\" height=\"1024\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/marcinzygmunt.pl\/blog\/wp-content\/uploads\/2025\/09\/jwt_4-879x1024.png\" alt=\"\" class=\"wp-image-32\" srcset=\"https:\/\/marcinzygmunt.pl\/blog\/wp-content\/uploads\/2025\/09\/jwt_4-879x1024.png 879w, https:\/\/marcinzygmunt.pl\/blog\/wp-content\/uploads\/2025\/09\/jwt_4-258x300.png 258w, https:\/\/marcinzygmunt.pl\/blog\/wp-content\/uploads\/2025\/09\/jwt_4-768x894.png 768w, https:\/\/marcinzygmunt.pl\/blog\/wp-content\/uploads\/2025\/09\/jwt_4.png 1052w\" sizes=\"auto, (max-width: 879px) 100vw, 879px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">What You Get<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Basic Auth + JWT:<\/strong> Users log in with credentials and receive a JWT token.<\/li>\n\n\n\n<li><strong>Refresh Tokens in Hazelcast:<\/strong> Tokens are stored in a distributed Hazelcast cluster, accessible to all application instances.<\/li>\n\n\n\n<li><strong>Swagger UI:<\/strong> Explore and test endpoints at <code>http:\/\/localhost:8088\/swagger-ui\/index.html<\/code>.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Setup in 4 Steps<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Clone the repository<\/strong><\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>git clone https:\/\/github.com\/marcinzygmunt-pl\/spring-boot-jwt-with-refresh-cache-hazelcast.git\ncd spring-boot-jwt-with-refresh-cache-hazelcast\n<\/code><\/pre>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li><strong>Configure PostgreSQL and Hazelcast<\/strong><br>Edit <code>application.yml<\/code> with your database credentials and Hazelcast cluster settings.<\/li>\n\n\n\n<li><strong>Run the application<\/strong><\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/mvnw spring-boot:run\n<\/code><\/pre>\n\n\n\n<ol start=\"4\" class=\"wp-block-list\">\n<li><strong>Test endpoints in Swagger UI<\/strong><br>Open <code>http:\/\/localhost:8088\/swagger-ui\/index.html<\/code> and test login, token refresh, and protected endpoints.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">How to see it in ACTION ?<a href=\"https:\/\/github.com\/marcinzygmunt-pl\/spring-boot-jwt-with-refresh-cache-hazelcast#how-to-see-it-in-action-\"><\/a><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Run 2 different instances of SpringBoot with different hazelcast configurations (see&nbsp;<code>application.yml<\/code>&nbsp;comments) Login and generate new JWT with refresh send to second instance of application.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Monitor Hazelcast with&nbsp;<a href=\"https:\/\/docs.hazelcast.com\/management-center\/5.3\/getting-started\/get-started\">Hazelcast Managment Center in Docker container<\/a><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>docker run --rm -p 8080:8080 hazelcast\/management-center:$MC_VERSION<\/code><\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Final Note<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>spring-boot-jwt-with-refresh-cache-hazelcast<\/strong> is the most advanced version in the series: it builds on refresh tokens with caching and <strong>adds a shared Hazelcast cluster<\/strong> to support multi-instance deployments safely and efficiently.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the previous examples, we covered JWT authentication with refresh tokens stored in a database and in local cache. Now, we present a solution using a Hazelcast cluster,\u2026<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[1],"tags":[9,5],"class_list":["post-31","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-jwt","tag-springboot"],"jetpack_likes_enabled":true,"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/marcinzygmunt.pl\/blog\/wp-json\/wp\/v2\/posts\/31","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/marcinzygmunt.pl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/marcinzygmunt.pl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/marcinzygmunt.pl\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/marcinzygmunt.pl\/blog\/wp-json\/wp\/v2\/comments?post=31"}],"version-history":[{"count":5,"href":"https:\/\/marcinzygmunt.pl\/blog\/wp-json\/wp\/v2\/posts\/31\/revisions"}],"predecessor-version":[{"id":156,"href":"https:\/\/marcinzygmunt.pl\/blog\/wp-json\/wp\/v2\/posts\/31\/revisions\/156"}],"wp:attachment":[{"href":"https:\/\/marcinzygmunt.pl\/blog\/wp-json\/wp\/v2\/media?parent=31"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/marcinzygmunt.pl\/blog\/wp-json\/wp\/v2\/categories?post=31"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/marcinzygmunt.pl\/blog\/wp-json\/wp\/v2\/tags?post=31"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}