{"id":11401,"date":"2026-02-20T11:42:25","date_gmt":"2026-02-20T02:42:25","guid":{"rendered":"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=11401"},"modified":"2026-02-20T21:49:24","modified_gmt":"2026-02-20T12:49:24","slug":"refreshtoken-%ec%9d%84-%ec%95%88%ec%a0%84%ed%95%98%ea%b2%8c-%ec%83%9d%ec%84%b1","status":"publish","type":"post","link":"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=11401","title":{"rendered":"refreshToken \uc744 \uc548\uc804\ud558\uac8c \uc0dd\uc131"},"content":{"rendered":"<h1>refreshToken \uc744 \uc548\uc804\ud558\uac8c \uc0dd\uc131<\/h1>\n<p>refreshToken \uc740 \uc11c\ubc84\uc5d0\uc11c <code>Set-Cookie: HttpOnly; Secure; SameSite=Strict<\/code> \ub85c \ud074\ub77c\uc774\uc5b8\ud2b8 \uc804\uc1a1\ud558\ub294 \ubc29\ubc95\uc744 \uc124\uba85\ud569\ub2c8\ub2e4.<\/p>\n<h2>Cookie \uc0dd\uc131 \uc720\ud2f8\ub9ac\ud2f0 \ud074\ub798\uc2a4<\/h2>\n<pre><code class=\"language-java\">import jakarta.servlet.http.Cookie;\nimport org.springframework.http.ResponseCookie;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class CookieUtil {\n\n    public ResponseCookie createRefreshTokenCookie(String refreshToken, long maxAgeInSeconds) {\n        return ResponseCookie.from(&quot;refreshToken&quot;, refreshToken)\n                .httpOnly(true)    \/\/ JS \uc811\uadfc \ubc29\uc9c0\n                .secure(true)      \/\/ HTTPS \ud658\uacbd\uc5d0\uc11c\ub9cc \uc804\uc1a1\n                .path(&quot;\/&quot;)         \/\/ \ubaa8\ub4e0 \uacbd\ub85c\uc5d0\uc11c \ucfe0\ud0a4 \uc804\uc1a1\n                .maxAge(maxAgeInSeconds)\n                .sameSite(&quot;Strict&quot;) \/\/ CSRF \uacf5\uaca9 \ubc29\uc5b4\n                .build();\n    }\n}<\/code><\/pre>\n<h2>\ucee8\ud2b8\ub864\ub7ec\uc5d0\uc11c \ucfe0\ud0a4 \uc804\uc1a1\ud558\uae30<\/h2>\n<pre><code class=\"language-java\">@RestController\n@RequestMapping(&quot;\/api\/auth&quot;)\npublic class AuthController {\n\n    private final CookieUtil cookieUtil;\n\n    public AuthController(CookieUtil cookieUtil) {\n        this.cookieUtil = cookieUtil;\n    }\n\n    @PostMapping(&quot;\/login&quot;)\n    public ResponseEntity&lt;?&gt; login(@RequestBody LoginRequest loginRequest) {\n        \/\/ 1. \uc0ac\uc6a9\uc790 \uc778\uc99d \ub85c\uc9c1 (\uc0dd\ub7b5)\n        \/\/ 2. \ud1a0\ud070 \uc0dd\uc131\n        String accessToken = &quot;access-token-string&quot;;\n        String refreshToken = &quot;refresh-token-string&quot;;\n\n        \/\/ 3. Refresh Token\uc744 \ub2f4\uc740 \ucfe0\ud0a4 \uc0dd\uc131\n        ResponseCookie cookie = cookieUtil.createRefreshTokenCookie(refreshToken, 7 * 24 * 60 * 60);\n\n        \/\/ 4. Access Token\uc740 \ubc14\ub514\uc5d0, Refresh Token\uc740 \ud5e4\ub354(\ucfe0\ud0a4)\uc5d0 \uc124\uc815\n        return ResponseEntity.ok()\n                .header(HttpHeaders.SET_COOKIE, cookie.toString())\n                .body(new LoginResponse(accessToken)); \n    }\n}<\/code><\/pre>\n<h2>\ud074\ub77c\uc774\uc5b8\ud2b8\ub85c\ubd80\ud130 \ucfe0\ud0a4 \uc77d\uae30<\/h2>\n<pre><code class=\"language-java\">@PostMapping(&quot;\/refresh&quot;)\npublic ResponseEntity&lt;?&gt; refresh(\n    @CookieValue(name = &quot;refreshToken&quot;) String refreshToken) {\n\n    \/\/ 1. \uc720\ud6a8\uc131 \uac80\uc99d \ubc0f \uc0c8\ub85c\uc6b4 Access Token \uc0dd\uc131 \ub85c\uc9c1\n    if (isValid(refreshToken)) {\n        String newAccessToken = &quot;new-access-token&quot;;\n        return ResponseEntity.ok(new LoginResponse(newAccessToken));\n    }\n\n    return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();\n}<\/code><\/pre>\n<h2>application-dev.yml, application-prd.yml \uc5d0\uc11c Secure \ucc98\ub9ac<\/h2>\n<p>Secure(true) \uc124\uc815 \uc2dc, <a href=\"http:\/\/localhost\">http:\/\/localhost<\/a> \ud658\uacbd\uc5d0\uc11c\ub294 \ucfe0\ud0a4\uac00 \uc800\uc7a5\ub418\uc9c0 \uc54a\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<p>application-dev.yml<\/p>\n<pre><code class=\"language-yaml\">auth:\n  cookie:\n    secure: false\n    same-site: Lax # \ub85c\uceec \uac1c\ubc1c \uc2dc\uc5d0\ub294 Strict\ubcf4\ub2e4 Lax\uac00 \ud3b8\ub9ac\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/code><\/pre>\n<p>application-prd.yml<\/p>\n<pre><code class=\"language-yaml\">auth:\n  cookie:\n    secure: true\n    same-site: Strict<\/code><\/pre>\n<pre><code class=\"language-java\">@Component\npublic class CookieUtil {\n\n    @Value(&quot;${auth.cookie.secure}&quot;)\n    private boolean isSecure;\n\n    @Value(&quot;${auth.cookie.same-site}&quot;)\n    private String sameSite;\n\n    public ResponseCookie createRefreshTokenCookie(String refreshToken, long maxAgeInSeconds) {\n        return ResponseCookie.from(&quot;refreshToken&quot;, refreshToken)\n                .httpOnly(true)\n                .secure(isSecure)     \/\/ yml \uc124\uc815\uac12 \uc801\uc6a9\n                .path(&quot;\/&quot;)\n                .maxAge(maxAgeInSeconds)\n                .sameSite(sameSite)    \/\/ yml \uc124\uc815\uac12 \uc801\uc6a9\n                .build();\n    }\n}<\/code><\/pre>\n<h2>\uc8fc\uc758\uc0ac\ud56d<\/h2>\n<ul>\n<li>\n<p>CORS \uc124\uc815: \ud504\ub860\ud2b8\uc5d4\ub4dc\uc640 \ubc31\uc5d4\ub4dc \ub3c4\uba54\uc778\uc774 \ub2e4\ub97c \uacbd\uc6b0, \ubc31\uc5d4\ub4dc CORS \uc124\uc815\uc5d0\uc11c allowCredentials(true)\ub97c \ubc18\ub4dc\uc2dc \ud65c\uc131\ud654\ud574\uc57c \ucfe0\ud0a4\uac00 \uc8fc\uace0\ubc1b\uc544\uc9d1\ub2c8\ub2e4.<\/p>\n<\/li>\n<li>\n<p>Local \ud14c\uc2a4\ud2b8: Secure(true) \uc124\uc815 \uc2dc, <a href=\"http:\/\/localhost\">http:\/\/localhost<\/a> \ud658\uacbd\uc5d0\uc11c\ub294 \ucfe0\ud0a4\uac00 \uc800\uc7a5\ub418\uc9c0 \uc54a\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uac1c\ubc1c \ud658\uacbd\uc5d0\uc11c\ub294 Secure(false)\ub85c \uc6b4\uc601\ud558\uac70\ub098 HTTPS \ub300\ud589 \ud504\ub85d\uc2dc\ub97c \uc0ac\uc6a9\ud558\uc138\uc694.<\/p>\n<\/li>\n<li>\n<p>Set-Cookie \ud5e4\ub354 \ucc98\ub9ac: \ud06c\ub85c\uc2a4 \uc624\ub9ac\uc9c4 \ud658\uacbd\uc778 \uacbd\uc6b0, \ube0c\ub77c\uc6b0\uc800\uac00 Set-Cookie \ud5e4\ub354\ub97c \ucc98\ub9ac\ud558\ub824\uba74 \ud504\ub860\ud2b8\uc5d4\ub4dc\uc5d0\uc11c \uc694\uccad \uc2dc credentials: &#8216;include&#8217; (\ub610\ub294 axios\uc758 withCredentials:     true)\ub97c \uc124\uc815\ud574\uc57c \ud569\ub2c8\ub2e4.<\/p>\n<\/li>\n<\/ul>\n<h2>Refresh Token Rotation<\/h2>\n<p>\ud574\ucee4\uac00 \ube0c\ub77c\uc6b0\uc800\uc5d0 \uae4c\uc9c0\ub3c4 \uc811\uadfc \uac00\ub2a5\ud55c \uc0c1\ud669\uc774 \ub418\uc5c8\ub2e4\ub294 \uac00\uc815\ud558\uc5d0,<br \/>\nAccess Token \uc744 \uc7ac\ubc1c\ud560 \ud560\ub54c\ub9c8\ub2e4 Refresh Token \ub610\ud55c \uc7ac\ubc1c\uae09\ud558\ub294 \ubc29\ubc95\uc785\ub2c8\ub2e4.<\/p>\n<p>Refresh Token \uc744 \uc815\uc0c1\uc801\uc778 \uc0ac\uc6a9\uc790\uac00 \uc0ac\uc6a9\ud558\uc5ec Access Token \uc744 \uc7ac\ubc1c\uae09\ud558\uba74,<br \/>\n\ud0c8\ucde8\ub41c Refresh Token \uc740 \ubb34\ud6a8\ud654\ub429\ub2c8\ub2e4.<\/p>\n<p>\ubc18\ub300\ub85c \ud574\ucee4\uac00 \uba3c\uc800 \uc7ac\ubc1c\uae09\uc744 \ubc1b\uc558\ub2e4\uba74, \uc815\uc0c1\uc801\uc778 \uc0ac\uc6a9\uc790\uac00 \ub85c\uadf8\uc778 \uc2dc\ub3c4\ud558\uc600\uc744 \ub54c \ubaa8\ub4e0 \ud1a0\ud070\uc744 \ubb34\ud6a8\ud654 \ud568\uc73c\ub85c\uc11c \uc9c0\uc18d\uc801\uc778 \ud53c\ud574\ub97c \ucc28\ub2e8\uc2dc\ud0ac \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<p><code>\ub9c8\uc9c0\ub9c9 \uc720\ud6a8 \ud1a0\ud070 + \ub514\ubc14\uc774\uc2a4 ID<\/code> \ub9cc \ubcf4\uad00\ud568\uc73c\ub85c\uc368, \uc11c\ubc84 \uc800\uc7a5 \uacf5\uac04\uc744 \ud6a8\uc728\uc801\uc73c\ub85c \uc6b4\uc6a9\ud560 \uc218 \uc788\ub2e4.<\/p>\n<p>\ud55c\uac00\uc9c0 \uace0\ub824\ud560 \uac83\uc740 \ud55c\ubc88\uc5d0 3\uac1c\uc758 \ud638\ucd9c\uc774 \ubc1c\uc0dd\ud588\uc744 \ub54c, \ud638\ucd9c\ub3c4\uc911 \ud1a0\ud070 \uac31\uc2e0\uc774 \ubc1c\uc0dd\ud558\uba74 \uc624\ub958\uac00 \ubc1c\uc0dd\ud55c\ub2e4.<br \/>\n\ud1a0\ud070 \uac31\uc2e0\uc774\ud6c4\uc5d0\ub3c4 \ucd5c\uc18c 10\ucd08\uc815\ub3c4\ub294 \uc774\uc804 \ud1a0\ud070\uc744 \uc720\ud6a8\ud558\uac8c \ucc98\ub9ac\ud574 \uc918\uc57c \ud558\ub294 \ub85c\uc9c1\uc774 \ud544\uc694\ud558\ub2e4.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>refreshToken \uc744 \uc548\uc804\ud558\uac8c \uc0dd\uc131 refreshToken \uc740 \uc11c\ubc84\uc5d0\uc11c Set-Cookie: HttpOnly; Secure; SameSite=Strict \ub85c \ud074\ub77c\uc774\uc5b8\ud2b8 \uc804\uc1a1\ud558\ub294 \ubc29\ubc95\uc744 \uc124\uba85\ud569\ub2c8\ub2e4. Cookie \uc0dd\uc131 \uc720\ud2f8\ub9ac\ud2f0 \ud074\ub798\uc2a4 import jakarta.servlet.http.Cookie; import org.springframework.http.ResponseCookie; import org.springframework.stereotype.Component; @Component public class CookieUtil { public ResponseCookie createRefreshTokenCookie(String refreshToken, long maxAgeInSeconds) { return ResponseCookie.from(&quot;refreshToken&quot;, refreshToken) .httpOnly(true) \/\/ JS \uc811\uadfc \ubc29\uc9c0 .secure(true) \/\/ HTTPS \ud658\uacbd\uc5d0\uc11c\ub9cc \uc804\uc1a1 .path(&quot;\/&quot;) \/\/ \ubaa8\ub4e0 \uacbd\ub85c\uc5d0\uc11c\u2026 <span class=\"read-more\"><a href=\"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=11401\">Read More &raquo;<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-11401","post","type-post","status-publish","format-standard","hentry","category-spring-boot"],"_links":{"self":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/11401","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=11401"}],"version-history":[{"count":10,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/11401\/revisions"}],"predecessor-version":[{"id":11413,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/11401\/revisions\/11413"}],"wp:attachment":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=11401"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=11401"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=11401"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}