Skip to content

Commit

Permalink
feat: refresh token 발급 및 access token 재발급 api 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
drunkenhw committed Sep 29, 2023
1 parent 6c81c25 commit f44fa29
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 5 deletions.
21 changes: 20 additions & 1 deletion backend/src/docs/asciidoc/auth.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
:toc: left
:toclevels: 3

== 충전소의 고장을 신고한다 (/oauth/{provider}/login-uri)
== 소셜 로그인 주소를 발급받는다 (/oauth/{provider}/login-uri)

=== Request

Expand All @@ -26,3 +26,22 @@ include::{snippets}/auth-controller-test/login/request-fields.adoc[]

include::{snippets}/auth-controller-test/login/http-response.adoc[]

== 새로운 access token을 발급받는다 (/renew)

=== Request

include::{snippets}/auth-controller-test/renew-access-token/http-request.adoc[]

=== Response

include::{snippets}/auth-controller-test/renew-access-token/http-response.adoc[]

== 로그아웃을 한다 (/logout)

=== Request

include::{snippets}/auth-controller-test/logout/http-request.adoc[]

=== Response

include::{snippets}/auth-controller-test/logout/http-response.adoc[]
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,32 @@

import com.carffeine.carffeine.auth.controller.dto.LoginUriResponse;
import com.carffeine.carffeine.auth.controller.dto.TokenResponse;
import com.carffeine.carffeine.auth.controller.support.AuthMember;
import com.carffeine.carffeine.auth.controller.support.RefreshTokenCookieGenerator;
import com.carffeine.carffeine.auth.domain.OAuthMember;
import com.carffeine.carffeine.auth.service.AuthService;
import com.carffeine.carffeine.auth.service.OAuthRequester;
import com.carffeine.carffeine.auth.service.dto.OAuthLoginRequest;
import com.carffeine.carffeine.auth.service.dto.Tokens;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import static org.springframework.http.HttpHeaders.SET_COOKIE;

@RestController
@RequiredArgsConstructor
public class AuthController {

private final AuthService authService;
private final OAuthRequester oAuthRequester;
private final RefreshTokenCookieGenerator refreshTokenCookieGenerator;

@GetMapping("/oauth/{provider}/login-uri")
public ResponseEntity<LoginUriResponse> getRedirectUri(
Expand All @@ -37,7 +44,24 @@ public ResponseEntity<TokenResponse> login(
@PathVariable String provider
) {
OAuthMember oAuthMember = oAuthRequester.login(request, provider);
String token = authService.generateToken(oAuthMember);
return ResponseEntity.ok(new TokenResponse(token));
Tokens tokens = authService.generateTokens(oAuthMember);
return ResponseEntity.ok()
.header(SET_COOKIE, refreshTokenCookieGenerator.createCookie(tokens.refreshToken()).toString())
.body(new TokenResponse(tokens.accessToken()));
}

@PostMapping("/renew")
public ResponseEntity<TokenResponse> renewAccessToken(@CookieValue(value = "refresh-token") String refreshToken) {
String accessToken = authService.renewAccessToken(refreshToken);
return ResponseEntity.ok(new TokenResponse(accessToken));
}

@PostMapping("/logout")
public ResponseEntity<Void> logOut(@AuthMember Long loginMember) {
authService.logout(loginMember);
return ResponseEntity.ok()
.header(SET_COOKIE, refreshTokenCookieGenerator.createLogoutCookie().toString())
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.carffeine.carffeine.auth.controller.support;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Component;

import java.time.Duration;

@Component
public class RefreshTokenCookieGenerator {

private static final String REFRESH_TOKEN = "refreshToken";
private static final String VALID_COOKIE_PATH = "/";
private static final String LOGOUT_COOKIE_VALUE = "";
private static final int LOGOUT_COOKIE_AGE = 0;

@Value("${jwt.refresh-token-expiration-period}")
private long expireLength;

public ResponseCookie createCookie(String refreshToken) {
return ResponseCookie.from(REFRESH_TOKEN, refreshToken)
.maxAge(Duration.ofMillis(expireLength))
.path(VALID_COOKIE_PATH)
.secure(true)
.httpOnly(true)
.build();
}

public ResponseCookie createLogoutCookie() {
return ResponseCookie.from(REFRESH_TOKEN, LOGOUT_COOKIE_VALUE)
.maxAge(LOGOUT_COOKIE_AGE)
.build();
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.carffeine.carffeine.auth.controller;

import com.carffeine.carffeine.auth.service.dto.OAuthLoginRequest;
import com.carffeine.carffeine.auth.service.dto.Tokens;
import com.carffeine.carffeine.helper.MockBeanInjection;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayNameGeneration;
Expand All @@ -9,12 +10,20 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseCookie;
import org.springframework.test.web.servlet.MockMvc;

import javax.servlet.http.Cookie;
import java.time.Duration;

import static com.carffeine.carffeine.helper.RestDocsHelper.customDocument;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
Expand Down Expand Up @@ -61,8 +70,15 @@ public class AuthControllerTest extends MockBeanInjection {
String provider = "google";

// when
when(authService.generateToken(any()))
.thenReturn("access token");
when(authService.generateTokens(any()))
.thenReturn(new Tokens("access token", "refreshToken"));
when(refreshTokenCookieGenerator.createCookie(anyString()))
.thenReturn(ResponseCookie.from("refresh-token", "refreshToken")
.maxAge(Duration.ofMillis(10000))
.path("/")
.secure(true)
.httpOnly(true)
.build());

// then
mockMvc.perform(post("/oauth/{provider}/login", provider)
Expand All @@ -77,9 +93,50 @@ public class AuthControllerTest extends MockBeanInjection {
),
responseFields(
fieldWithPath("token").description("Access token")
),
responseHeaders( //응답 헤더 문서화
headerWithName(HttpHeaders.SET_COOKIE).description("refresh token")
)
));
}

@Test
void refresh_token으로_access_token을_반환한다() throws Exception {
// given
String refreshToken = "your-refresh-token";
String accessToken = "new-access-token";
when(authService.renewAccessToken(refreshToken)).thenReturn(accessToken);

// when & then
mockMvc.perform(post("/renew")
.cookie(new Cookie("refresh-token", refreshToken)))
.andExpect(status().isOk())
.andDo(customDocument("renew-access-token",
responseFields(
fieldWithPath("token").description("Renewed access token")
)
));
}

@Test
public void logout을_한다() throws Exception {
// given
when(refreshTokenCookieGenerator.createLogoutCookie())
.thenReturn(ResponseCookie.from("refreshToke", "")
.maxAge(0)
.build());

// when & then
mockMvc.perform(post("/logout")
.contentType(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, "Bearer token~~"))
.andExpect(status().isOk())
.andDo(customDocument("logout",
responseHeaders(
headerWithName("Set-Cookie")
.description("Logout cookie containing refreshed token")
)));
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.carffeine.carffeine.admin.service.AdminStationService;
import com.carffeine.carffeine.auth.controller.AuthArgumentResolver;
import com.carffeine.carffeine.auth.controller.support.AuthenticationContext;
import com.carffeine.carffeine.auth.controller.support.RefreshTokenCookieGenerator;
import com.carffeine.carffeine.auth.domain.TokenProvider;
import com.carffeine.carffeine.auth.service.AuthService;
import com.carffeine.carffeine.auth.service.OAuthRequester;
Expand Down Expand Up @@ -75,4 +76,6 @@ public class MockBeanInjection {
protected FilterQueryService filterQueryService;
@MockBean
protected StationQueryService stationQueryService;
@MockBean
protected RefreshTokenCookieGenerator refreshTokenCookieGenerator;
}

0 comments on commit f44fa29

Please sign in to comment.