1. ๋ค์ด๊ฐ๊ธฐ์ ์์
๊ธ์ ์์ํ๊ธฐ ์ ์, ์ด๋ฒ ํฌ์คํ ์์ ๋ค๋ฃฐ ์์ ๋ก๊ทธ์ธ์ ์ธ์ฆ ํ๋ก์ฐ์ ๋ํด ๊ฐ๋ตํ๊ฒ ์ง์ด๋ณด์.
- ์ฌ์ฉ์๋ฅผ ๊ตฌ๊ธ ๋ก๊ทธ์ธ ์ฐฝ์ผ๋ก ๋๊ฒจ์ค๋ค.
- ์ฌ์ฉ์๊ฐ ๊ตฌ๊ธ ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ๋ฉด, ๊ตฌ๊ธ ์๋ฒ๋ ์ฐ๋ฆฌ์๊ฒ ์น์ธ ์ฝ๋(Authorization code)๋ฅผ ๋๊ฒจ์ค๋ค.
- (2)์ ์น์ธ ์ฝ๋๋ฅผ ๊ตฌ๊ธ ์๋ฒ์๊ฒ ๋๊ธฐ๊ณ , ๊ตฌ๊ธ ์ก์ธ์ค ํ ํฐ์ ๋ฐ์์จ๋ค.
- ๊ตฌ๊ธ ์ก์ธ์ค ํ ํฐ์ ์ด์ฉํด ์ฌ์ฉ์ ๋์ ๊ตฌ๊ธ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ฐ์์จ๋ค.
- ์ฌ์ฉ์ ์ ๋ณด์ ์ ์ ์๋ณ์๋ฅผ ํตํด ๊ฐ์ ์ฌ๋ถ๋ฅผ ํ์ธํ๋ค. ์ ํ์์ด๋ฉด DB์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ฑ๋กํ ๋ค, ๋ก๊ทธ์ธ์ ์งํํ๋ค.
๋น์ ๋ฅผ ๋ค์ด๋ณด์๋ฉด... ์ด๋ฐ ๋๋์ด๋ค.
- ์ฌ์ฉ์ ์ ๋ณด: ๊ธ๊ณ ์ ๋ณด๊ด ์ค
- ์ก์ธ์ค ํ ํฐ: ๊ธ๊ณ ์ ์ด์ ์ ํด๋น. ์๋์ ๋ณด๊ด ์ค
- ์น์ธ ์ฝ๋: ์๋์ ์๋ฌผ์ ๋น๋ฐ๋ฒํธ
์ก์ธ์ค ํ ํฐ์ ๋์ถฉ ๋ณด๊ดํ๋ฉด ๊ฐ๋์๋ ์ฌ์ฉ์ ์ ๋ณด์ ์ ๊ทผ ๊ฐ๋ฅํ๋ค. ๊ทธ๋์ ๊ตฌ๊ธ์ ์ก์ธ์ค ํ ํฐ์ ์๋์ ๋ฃ๊ณ ์๋ฌผ์ ๋ฅผ ์ฑ์ด ๋ค, ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ ์ฌ๋์๊ฒ๋ง ์๋ ๋น๋ฐ๋ฒํธ๋ฅผ ์๋ ค์ค๋ค. ์ฌ์ฉ์๊ฐ ์ฐ๋ฆฌ์๊ฒ ์๋ ๋น๋ฐ๋ฒํธ(์น์ธ ์ฝ๋)๋ฅผ ์๋ ค์ฃผ๋ฉด, ์ฐ๋ฆฌ๋ ์ฌ์ฉ์ ๋์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ป์ด์ค๋ ๊ฑฐ๋ค.
2. ์ด๋๊น์ง ํ๋ก ํธ์๊ฒ ๋งก๊ธธ๊น?
์ฌ์ค ํ๋ก ํธ์์๋ ๋ฐ๋ก ์ก์ธ์ค ํ ํฐ์ ๋ฐ์์ฌ ์ ์๋ค. ์ด๋ ๊ฒ ๋๋ฉด ๋ฐฑ์์๋ ๊ตฌ๊ธ ์๋ฒ์ ์์ฒญ์ ํ๋ฒ๋ง ๋ณด๋ด๋ ๋๋ค๋ ์ฅ์ ์ด ์๋ค.
ํ์ง๋ง ์ก์ธ์ค ํ ํฐ์ ํ๋ก ํธ์์ ์ง์ ๊ด๋ฆฌํ๋ ๊ฒ์ ์ํํ ์ ์๋ค. ์ก์ธ์ค ํ ํฐ์ด ์ ์ถ๋๋ฉด ๋๊ตฌ๋ ์ฌ์ฉ์ ์ ๋ณด์ ์ ๊ทผ ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ด๋ค. (์ผ๋ฐ์ ์ผ๋ก ์ก์ธ์ค ํ ํฐ์ ์ ํจ๊ธฐ๊ฐ์ 30๋ถ~1์๊ฐ์ด๋ค.) ๋ฐ๋ฉด ์ธ์ฆ ์ฝ๋๋ ์ผํ์ฑ์ด๊ณ ์ ํจ๊ธฐ๊ฐ์ด ์ก์ธ์ค ํ ํฐ๋ณด๋ค ๋ ์งง๊ธฐ ๋๋ฌธ์, ์ ์ถ ์ ์ํ๋๊ฐ ๋ ๋ฎ๋ค. ๋๋ฌธ์ ์ฐ๋ฆฌ ํ์์๋ 2๋ฒ๊น์ง ํ๋ก ํธ์์ ์งํํ๊ณ , 3๋ฒ๋ถํฐ๋ ๋ฐฑ์์ ์งํํ๊ธฐ๋ก ํ๋ค. ์น์ธ ์ฝ๋๋ POST๋ฅผ ์ด์ฉํด RequestBody์ ๋ด์ ๋ณด๋ด์ฃผ๊ธฐ๋ก!
์ด์ ๋ฐฑ์์ ๊ตฌํํด์ผ ํ ๋ถ๋ถ์ด ์ ํด์ก๋ค.
- ํ๋ก ํธ๋ก๋ถํฐ ๋ฐ์์จ ์ธ์ฆ ์ฝ๋๋ก, ๊ตฌ๊ธ์ ์ก์ธ์ค ํ ํฐ ์์ฒญ
- ๊ตฌ๊ธ ์ก์ธ์ค ํ ํฐ์ผ๋ก, ๊ตฌ๊ธ์ ์ฌ์ฉ์ ์ ๋ณด ์์ฒญ
- ์ป์ด์จ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ํตํด, ๋ฏธ๊ฐ์ ํ์์ด๋ฉด ํ์์ ๋ณด๋ฅผ DB์ ์ ์ฅ
๋ง์ง๋ง 10๋ฒ์ ์์ ๋ก๊ทธ์ธ์ด๊ธฐ ๋ณด๋ค๋, ์ผ๋ฐ ๋ก๊ทธ์ธ์ ํด๋นํ๋ฏ๋ก ์ฐธ๊ณ ๋ก๋ง ๋ณด์.
3. properties ๋๋ yml ํ์ผ์ ์ค์ ๊ฐ ์ถ๊ฐํ๊ธฐ
๋จผ์ application.properties ํ์ผ ๋๋ application.yml ํ์ผ์ ์๋์ ๊ฐ์ ๊ฐ์ ์ค์ ํด์ค๋ค. ๊ฐ์ข URI ์๋ํฌ์ธํธ๋ ๊ตฌ๊ธ ๊ณต์๋ฌธ์์์ ํ์ธํ ์ ์๋ค.
- token-base-uri: ํด๋น URI๋ก ๊ตฌ๊ธ ์ก์ธ์ค ํ ํฐ์ ์์ฒญํ๋ค.
- member-info-base-uri: ํด๋น URI๋ก ๊ตฌ๊ธ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์์ฒญํ๋ค.
- grant_type: ์ธ์ฆ ์ฝ๋๋ก ์ก์ธ์ค ํ ํฐ์ ๋ฐ์์ฌ ๊ฑฐ๋๊น authorization_code๋ก ์ค์ ํด์ฃผ์๋ค.
// application.yml
oauth2:
google:
client-id: {ํด๋ผ์ด์ธํธ ID}
client-secret: {ํด๋ผ์ด์ธํธ ์ํฌ๋ฆฟ}
redirect-uri: {๋ฆฌ๋ค์ด๋ ํธ URI}
token-base-uri: https://oauth2.googleapis.com
member-info-base-uri: https://www.googleapis.com
grant-type: authorization_code
ํด๋ผ์ด์ธํธ ID์ ์ํฌ๋ฆฟ, ๋ฆฌ๋ค์ด๋ ํธ URI๋ ๊ตฌ๊ธ ํด๋ผ์ฐ๋ ์ฝ์์์ ํ์ธํ ์ ์๋ค. ๋ฐ๋์ ์ฝ์์์ ํ์ธํ ๊ฐ๊ณผ ๋์ผํ ๊ฐ์ ๋ฃ์ด์ฃผ์ด์ผ ํ๋ค.
์ง๋ ํฌ์คํ ์์ ๋ฐ์ค์น ๋ถ๋ถ์ ์ ์๊น๊ฒ ๋ณด๋ผ๊ณ ํ ๊ฒ๋ ์ฌ๊ธฐ์ ์ ๋ ฅํด์ผ ํ๊ธฐ ๋๋ฌธ์ด๋ค.
์ด๊ฑธ ๊ตณ์ด yml ํ์ผ์ ์ถ๊ฐํ๋ ์ด์ ๋ ํ ๊ณณ์์ ๊ด๋ฆฌํ๋ ค๋ ์ด์ ๋ ์์ง๋ง, client secret์ ๋ ธ์ถ๋๋ฉด ์๋๋ ๊ฐ์ด๊ธฐ ๋๋ฌธ์ด๋ค. ๋น์ฐํ ๊น์ ์ฌ๋ ค๋ ์๋๋ค. ๊ทธ๋์ ์ฝ๋์ ์ง์ ์ ์ผ๋ก ๋ช ์ํ์ง ์๊ณ , @Value ์ด๋ ธํ ์ด์ ์ ์ด์ฉํด yml ํ์ผ์์ ๊ฐ์ ๋ฐ์์ฌ ๊ฒ์ด๋ค.
4. OpenFeign์ ์ด์ฉํด ๊ตฌ๊ธ์ ์ก์ธ์ค ํ ํฐ ์์ฒญํ๊ธฐ
์ด ๋ถ๋ถ์ด ์ ๋ง ์ด๋ ค์ ๋ค. ๋งจ๋ ํด๋ผ์ด์ธํธ ์์ฒญ์ ๋ฐ๋ผ ์๋ต๋ง ๋์ ธ์คฌ๋๋ฐ, ์ด์ ๋ด๊ฐ ์์ฒญ์ ๋ณด๋ด์ผ ํ๋ค๋๐ฅน
์๋ฒ์์ ๋ค๋ฅธ ์๋ฒ๋ก HTTP ์์ฒญ์ ๋ณด๋ผ ๋ ์ด์ฉํ ์ ์๋ ๊ธฐ์ ๋ก๋ RestTemplate, WebClient, OpenFeign ๋ฑ์ด ์๋ค. ์ฌ๊ธฐ์๋ OpenFeign์ ์ด์ฉํด ์งํํด๋ณด๊ฒ ๋ค. OpenFeign์ JPA์ฒ๋ผ ์ธํฐํ์ด์ค๋ง ๊ตฌํํ๋ฉด ์์ฒญ์ ๋ณด๋ผ ์ ์์ด์ ๋งค์ฐ ์ฝ๊ณ ๊ฐํธํ๋ค. ์์ด์ค๋ ฅ์ด ๋๋ ๋ถ์ด๋ผ๋ฉด OpenFeign ๊ณต์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ๋ ๊ฑธ ์ถ์ฒ!
(1) ์์กด์ฑ ๋ฐ ์ด๋ ธํ ์ด์ ์ถ๊ฐ
implementation "org.springframework.cloud:spring-cloud-dependencies:2023.0.0"
implementation "org.springframework.cloud:spring-cloud-starter-openfeign:4.1.0"
OpenFeign์ ์ด์ฉํ๊ธฐ ์ํด์๋ build.gradle ํ์ผ์ ์์กด์ฑ์ ์ถ๊ฐํด์ฃผ์ด์ผ ํ๋ค. ์คํ๋ง ํด๋ผ์ฐ๋ ๋ฒ์ ๊ณผ OpenFeign ๋ฒ์ ์ ๊ผญ ํ์ธํ๊ณ ์ ์ด์ฃผ์. ๋๋ ์คํ๋ง๋ถํธ 3.2.0์ ์ฌ์ฉ์ค์ด๋ผ ๊ทธ์ ๋ง์ถฐ ์ ์๋ค.
@EnableFeignClients
@SpringBootApplication
public class SiltaraeBeApplication {
public static void main(String[] args) {
SpringApplication.run(SiltaraeBeApplication.class, args);
}
}
๊ทธ๋ฆฌ๊ณ ๋ฉ์ธํจ์๊ฐ ์๋ Application ํด๋์ค์ @EnableFeignClients ์ด๋ ธํ ์ด์ ์ ์ถ๊ฐํด์ค๋ค. ์ด์ OpenFeign์ ์ฌ์ฉํ ์ค๋น๊ฐ ๋๋ฌ๋ค.
(2) ๊ตฌ๊ธ API ์์์ ๋ง๋ DTO ์์ฑํ๊ธฐ
์์ฒญ์ ๋ณด๋ด๊ธฐ ์ ์, ๊ตฌ๊ธ์๊ฒ ๋ณด๋ผ ์์ฒญ ๋ฐ์ดํฐ์ ์๋ต ๋ฐ์ดํฐ๋ฅผ ๋ด์ DTO ๊ฐ์ฒด๋ฅผ ์์ฑํด๋ณด์.
POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded
code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&
client_id=your_client_id&
client_secret=your_client_secret&
redirect_uri=https%3A//oauth2.example.com/code&
grant_type=authorization_code
๋จผ์ ๊ตฌ๊ธ ๊ณต์๋ฌธ์์ ๋ฐ๋ฅด๋ฉด, ์ก์ธ์ค ํ ํฐ์ ์์ฒญํ ๋๋ ์์ ๊ฐ์ ์์์ผ๋ก HTTP Request๋ฅผ ๋ณด๋ด์ผ ํ๋ค. RequestBody์๋ ์ธ์ฆ ์ฝ๋, ํด๋ผ์ด์ธํธ ID, ํด๋ผ์ด์ธํธ ์ํฌ๋ฆฟ, ๋ฆฌ๋ค์ด๋ ํธ URI, ์น์ธ ํ์ ์ด ๋ด๊ฒจ์ผ ํ๋ค. ์์ฒญ์ ๋ณด๋ผ ๋ ์ฌ์ฉํ RequestDTO๋ฅผ ํ๋ ์์ฑํด๋ณด์.
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class OAuthAccessTokenRequest {
private String clientId;
private String clientSecret;
private String redirectUri;
private String grantType;
private String code;
@Builder
public OAuthAccessTokenRequest(String clientId, String clientSecret, String redirectUri, String grantType, String code) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.redirectUri = redirectUri;
this.grantType = grantType;
this.code = code;
}
}
{
"access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
"expires_in": 3920,
"token_type": "Bearer",
"scope": "https://www.googleapis.com/auth/drive.metadata.readonly",
"refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
}
๊ทธ๋ฌ๋ฉด ๊ตฌ๊ธ ์๋ฒ๋ ์์ ๊ฐ์ ์์์ผ๋ก ์๋ต์ ๋ณด๋ด์ค๋ค. ์ก์ธ์ค ํ ํฐ ์ธ์๋ ์ก์ธ์ค ํ ํฐ์ ๋ง๋ฃ์๊ฐ, ํ ํฐ ํ์ , ๋ฆฌํ๋ ์ ํ ํฐ, ํด๋น ์ก์ธ์ค ํ ํฐ์ผ๋ก ์ด๋ scope์ ์ ๊ทผ ๊ฐ๋ฅํ์ง ๋ฑ์ด ์ ํ์๋ค. ์ด JSON ๋ฐ์ดํฐ๋ฅผ ๋งคํํ ResponseDTO๋ ํ๋ ๋ง๋ค์ด๋ณด์.
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class OAuthAccessTokenResponse {
@JsonProperty("access_token")
private String accessToken;
@JsonProperty("expires_in")
private Integer expiresIn;
@JsonProperty("scope")
private String scope;
@JsonProperty("id_token")
private String idToken;
}
@JsonProperty ์ด๋ ธํ ์ด์ ์ ์ด์ฉํด, JSON ๋ฐ์ดํฐ๊ฐ ์๋ฐ ๊ฐ์ฒด์ ๋งคํ๋ ๋ ๊ฐ key๋ณ๋ก ๋งคํ๋ ํ๋๋ฅผ ๋ช ์ํด์ฃผ์๋ค.
(3) GoogleAuthClient ์์ฑํ๊ธฐ
์ด์ FeignClient ์ธํฐํ์ด์ค๋ฅผ ์ ์ธํ ์ฐจ๋ก์ด๋ค. OpenFeign์ JPA์ฒ๋ผ ์ธํฐํ์ด์ค๋ฅผ ์ ์ธํ๋ ๊ฒ๋ง์ผ๋ก ๋ค๋ฅธ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ผ ์ ์๋ค. ๊ตฌ๊ธ์ API ์์์ ๋ง์ถฐ FeignClient๋ฅผ ๋ง๋ค์ด๋ณด์.
package weavers.siltarae.login.domain;
import feign.Headers;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import weavers.siltarae.login.dto.request.OAuthAccessTokenRequest;
import weavers.siltarae.login.dto.response.OAuthAccessTokenResponse;
@FeignClient(name = "googleAuthClient", url = "${oauth2.google.token-base-uri}")
public interface GoogleAuthClient {
@PostMapping("/token")
@Headers("Content-Type: application/x-www-form-urlencoded")
ResponseEntity<OAuthAccessTokenResponse> getAccessToken(@RequestBody OAuthAccessTokenRequest request);
}
- @FeignClient: Feign ํด๋ผ์ด์ธํธ๋ฅผ ์ ์ํ ๋ ์ฌ์ฉํ๋ค. name ์์ฑ์ ํตํด ํด๋ผ์ด์ธํธ์ ์ด๋ฆ์ ์ ์ํ๊ณ , url์ ํตํด ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญ์ ๋ณด๋ผ ์๋ฒ์ ๊ธฐ๋ณธ URL์ ์ ์ํ๋ค.
- @PostMapping("/token"): /token ๊ฒฝ๋ก๋ก POST ์์ฒญ์ ๋ณด๋ผ ๊ฒ์์ ๋ช ์ํ๋ค.
- @Headers: HTTP Request Header๋ฅผ ์ง์ ํ ์ ์๋ค.
- @RequestBody: ๋ค์ ์ค๋ ํ๋ผ๋ฏธํฐ๋ฅผ HTTP Request Body์ ๋ฃ์ด ๋ณด๋ธ๋ค.
์ฌ๊ธฐ์๋ OAuthAccessTokenRequest์ ๋ด๊ธด ๊ฐ๋ค์ Request Body์ ๋ฃ์ด ๋ณด๋ด๊ณ , Response Body์ ๋ด๊ธด ์๋ต ๋ฐ์ดํฐ๋ฅผ OAuthAccessTokenResponse ๊ฐ์ฒด์ ๋งคํํ๋ค.
์คํ๋ง ํด๋ผ์ฐ๋๋ FeignClientConfiguration์ ์ด์ฉํด, ์ฌ๊ธฐ ์ค์ ๋ ์ด๋ฆ๋๋ก ApplicationContext์ ํด๋ผ์ด์ธํธ๋ฅผ ๋ฑ๋กํด์ค๋ค. ๋ฐ๋ผ์ ๋ฐ๋ก ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กํ์ง ์์๋ ์๋ ์ฃผ์ ์ด ๊ฐ๋ฅํ๋ค.
(4) GoogleProvider ์์ฑํ๊ธฐ
package weavers.siltarae.login.domain;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import weavers.siltarae.global.exception.AuthException;
import weavers.siltarae.global.exception.ExceptionCode;
import weavers.siltarae.login.dto.request.OAuthAccessTokenRequest;
import weavers.siltarae.login.dto.response.OAuthAccessTokenResponse;
import weavers.siltarae.member.domain.SocialType;
@Component
@RequiredArgsConstructor
public class GoogleProvider {
private final GoogleAuthClient googleAuthClient;
private final String SOCIAL_TYPE = "GOOGLE";
@Value("${oauth2.google.client-id}")
private String CLIENT_ID;
@Value("${oauth2.google.client-secret}")
private String CLIENT_SECRET;
@Value("${oauth2.google.redirect-uri}")
private String REDIRECT_URI;
@Value("${oauth2.google.grant-type}")
private String GRANT_TYPE;
@Value("${oauth2.google.member-info-base-uri}")
private String MEMBER_INFO_BASE_URI;
public String requestAccessToken(final String code) {
OAuthAccessTokenRequest request = OAuthAccessTokenRequest.builder()
.clientId(CLIENT_ID)
.clientSecret(CLIENT_SECRET)
.redirectUri(REDIRECT_URI)
.grantType(GRANT_TYPE)
.code(code).build();
ResponseEntity<OAuthAccessTokenResponse> response = googleAuthClient.getAccessToken(request);
// ๋ง์ฝ ์์ฒญ์ด ์ ์ ์ํ๋์ง ์์ผ๋ฉด, Exception ๋ฐ์
if(!response.getStatusCode().is2xxSuccessful() || !response.hasBody()) {
throw new AuthException(ExceptionCode.INVALID_AUTHORIZATION_CODE);
}
// ResponseBody์์ AccessToken ์ถ์ถ
return response.getBody().getAccessToken();
}
์ด์ FeignClient๋ฅผ ์ฌ์ฉํ GoogleProvider ํด๋์ค๋ฅผ ์์ฑํด๋ณด์.
GoogleProvider๋ GoogleAuthClient๋ฅผ ํธ์ถํ์ฌ ๊ตฌ๊ธ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด๊ณ , ์๋ต์์ ์ก์ธ์ค ํ ํฐ์ ์ถ์ถํ์ฌ ๋ฐํํด์ค๋ค. ๋ง์ฝ ์๋ต์ ์ํ์ฝ๋๊ฐ 200๋ฒ๋๊ฐ ์๋๊ฑฐ๋, ResponseBody๊ฐ ์์ผ๋ฉด ์์ฒญ์ด ์ ์ ์ฒ๋ฆฌ๋ ๊ฒ ์๋๋ฏ๋ก Exception์ ๋ฐ์์ํค๋๋ก ํ๋ค. (AuthException์ ์ปค์คํ ์์ธ์ด๋ค.)
๊น์ ์ฌ๋ผ๊ฐ๋ ์์ ํ๋๋ก ํ์ํ ๊ฐ๋ค์ ์ ๋ถ application.yml ํ์ผ์์ ๋ฐ์์ค๋๋ก ํ๋ค.
(5) ์ปจํธ๋กค๋ฌ, ์๋น์ค ์์ฑํ๊ธฐ
๋ง์ง๋ง์ผ๋ก ์ปจํธ๋กค๋ฌ์ ์๋น์ค ์ฝ๋๋ฅผ ์์ฑํด์ฃผ๋ฉด, ๊ตฌ๊ธ์์ ์ก์ธ์ค ํ ํฐ์ ๋ฐ์์ฌ ์ค๋น๊ฐ ๋๋๋ค.
ํ๋ก ํธ๊ฐ /api/v1/login/google URI๋ก POST ์์ฒญ์ ๋ณด๋ด๋ฉด, ์ปจํธ๋กค๋ฌ -> ์๋น์ค -> GoogleProvider -> GoogleAuthClient ์์๋๋ก ํธ์ถํ ๊ฒ์ด๋ค.
package weavers.siltarae.login.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import weavers.siltarae.login.domain.GoogleProvider;
@Service
@Transactional
@RequiredArgsConstructor
public class LoginService {
private final GoogleProvider googleProvider;
public void login(String code) {
String authAccessToken = googleProvider.requestAccessToken(code);
}
}
์น์ธ ์ฝ๋(code)๋ฅผ GoogleProvider์ ๋๊ธฐ๊ณ , ๊ตฌ๊ธ ์ก์ธ์ค ํ ํฐ์ ๋ฐ์์ค๋ login() ๋ฉ์๋๋ฅผ ๋ง๋ค์ด์ฃผ์๋ค.
package weavers.siltarae.login.dto.request;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class LoginRequest {
private String authCode;
@Builder
public LoginRequest(String authCode) {
this.authCode = authCode;
}
}
package weavers.siltarae.login.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import weavers.siltarae.login.dto.request.LoginRequest;
import weavers.siltarae.login.service.LoginService;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1")
public class LoginController {
private final LoginService loginService;
@PostMapping("/login/google")
public ResponseEntity<AccessTokenResponse> login(@RequestBody final LoginRequest request) {
loginService.login(request.getAuthCode());
}
ํ๋ก ํธ์์ ๋๊ฒจ์ค ์ธ์ฆ ์ฝ๋๋ฅผ ๋งคํํ DTO LoginRequest ํด๋์ค๋ฅผ ์์ฑํ ๋ค์, ํ๋ก ํธ์ ์์ฒญ์ ๋ฐ์์ค LoginController๋ฅผ ๋ง๋ค์๋ค.
5. ํ ์คํธ๋ ์ด๋ป๊ฒ ํ์ง?
์ด๋ถ๋ถ์ ์ข ๋ฌด์ํ ๋ฐฉ๋ฒ์ผ๋ก ์งํํ๋ค. ํน์ ๋ ์ ์ํ ๋ฐฉ๋ฒ ์๋ ๋ถ๋ค์ ์๋ ค์ฃผ์ธ์ ๐คง
์ผ๋จ ๊ตฌ๊ธ ํด๋ผ์ฐ๋ ์ฝ์์ ๊ฐ์ ๋ฆฌ๋๋ ์ URI์ ๋ฐฑ ์ฃผ์๋ฅผ ์ถ๊ฐํด์ค๋ค. ๋๋ http://localhost:8761/api/v1/login/google๋ก ์ค์ ํ๋ค. (์คํ๋ง๋ถํธ๊ฐ 8761๋ฒ ํฌํธ์์ ๋๊ณ ์๋ค) application.properties๋ application.yml ํ์ผ์ ์ค์ ๋ ๊ฐ๋ ๋ฐ๊ฟ์ฃผ์ด์ผ ํ๋ค.
@GetMapping("/login/google")
public String getAuthCode(@RequestParam final String code) {
return code;
}
๊ทธ ๋ค์์, ์ธ์ฆ์ฝ๋๋ฅผ ๋ฐฑ์ผ๋ก ๋ฐ์์ฌ ์ ์๋๋ก LoginController์ ๋ฉ์๋๋ฅผ ํ๋ ์ถ๊ฐํด์ค๋ค.
๊ตฌ๊ธ์์๋ ๋ฐฑ์ http://localhost:8761/api/v1/login/google?code=4/0xiruqkvfaew... ๊ฐ์ ์์ผ๋ก ์์ฒญ์ ๋ณด๋ด๊ณ , ๋๋ ๊ทธ๊ฑธ ๋ฐ์์ค๋ฉด ๋๋ ์์ด๋ค.
๊ทธ๋ฆฌ๊ณ ์๋ ์์์ ์ค์ ๊ฐ์ ๋ง๊ฒ ์ฑ์ด ๋ค, ๋ธ๋ผ์ฐ์ ์ฃผ์์ฐฝ์ ๋ถ์ฌ๋ฃ๊ธฐ ํด์ค๋ค.
https://accounts.google.com/o/oauth2/v2/auth?
client_id={ํด๋ผ์ด์ธํธ ID}
&redirect_uri={๋ฆฌ๋ค์ด๋ ํธ URI}
&scope=https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile openid
&response_type=code
๊ทธ๋ฆฌ๊ณ ๋ธ๋ผ์ฐ์ ์ฃผ์์ฐฝ์ ๋ถ์ฌ๋ฃ๊ธฐ๋ฅผ ํด์ฃผ๋ฉด ์ก์ธ์ค ํ ํฐ๊ฐ์ด ์ฐฝ์ ๋ํ๋๋ค.
์ด๊ฒ ์ต์ ์ผ๊น? ์ถ์๋ฐ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ ์ฐพ์ง ๋ชปํด์๐ฅน ๋๋ ์ด๋ ๊ฒ ๋ฌด์ํ ๋ฐฉ๋ฒ์ผ๋ก ํ ์คํธํ๊ณ ์ถ์ง ์์๋ค...
์ดํ๋ ํฌ์คํธ๋งจ์ ์ฌ์ฉํ๋ , ์ธํ ๋ฆฌ์ ์ด HttpClient๋ฅผ ์ฌ์ฉํ๋ ํธํ๋๋ก ํ๋ฉด ๋๋ค.
'๐๐ฉ๐ซ๐ข๐ง๐ ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring์์ ๊ตฌ๊ธ ์์ ๋ก๊ทธ์ธ ๊ตฌํํ๊ธฐ (1) - ํด๋ผ์ด์ธํธ ๋ฑ๋ก (1) | 2023.12.24 |
---|---|
Mockito๋ฅผ ์ด์ฉํ ๋จ์ ํ ์คํธ ์์ฑ (0) | 2023.12.10 |