๐Goal : Spring boot์์ ๊ตฌ๊ธ ์์ ๋ก๊ทธ์ธ์ ๋ฐฑ์๋ ์ฒ๋ฆฌ ํ๋ก์ธ์ค๋ฅผ REST API ๋ฐฉ์์ผ๋ก ๊ตฌํํ๋ค.
1. ๊ตฌ๊ธ OAuth API ํ๋ก์ ํธ ํ๊ฒฝ ๊ตฌ์ฑ
โฌ๏ธ ๊ตฌ๊ธ API๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ์ฐ์ ํ๋จ์ ์ฌ์ดํธ์์ ์ผ๋ จ์ ๊ตฌ์ฑ๋ฐ ํ๊ฐ ๊ณผ์ ์ ๊ฑฐ์ณ์ผ ํ๋ค.
https://console.cloud.google.com/apis/dashboard
1) ์ฐ์ ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ง Oauth ๋์ ํ๋ฉด์ ๊ตฌ์ฑํ๋ค.
2) ๊ตฌ๊ธ์ ์ด๋ค ์ฌ์ฉ์ ๋ฐ์ดํฐ๊น์ง ์ ๊ทผํ ๊ฒ์ธ์ง ๋ฒ์๋ฅผ ์ง์ ํ๋ค.
3) ํ ์คํธ ์ฌ์ฉ์ ์ถ๊ฐ๋ ์ฐ์ ์๋ตํ๊ณ , ์๋ฃํ๋ฉด ์ฌ์ฉ์ Oauth ๋์ ํ๋ฉด์ด ๊ตฌ์ฑ๋๋ค.
4) OAuth api์ ์ก์ธ์คํ๊ธฐ ์ํด ์ฌ์ฉ์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๋ฐ๊ธํด์ผ ํ๋ค.
- ์ฌ๊ธฐ์ ์ค์ํ ๊ฒ์ ์น์ธ๋ ๋ฆฌ๋๋ ์ URI์ธ๋ฐ, ๊ตฌ๊ธ ๋ก๊ทธ์ธ์ด ๋๋๊ณ ๋ ๋ค์ ํด๋น api๋ก ๋ฆฌ๋๋ ์ ๋์ด ์ก์ธ์ค ํ ํฐ ๋ฑ์ ์ฒ๋ฆฌํ๋ฏ๋ก path๋ฅผ ํ์ธํ์ฌ ์ ์ง์ ํด๋ฌ์ผ ํ๋ค.
5) ํด๋น ๊ณผ์ ์ ๊ฑฐ์น๋ฉด ์ฌ์ฉ์ ์ธ์ฆ ์ ๋ณด๊ฐ ๋ฐ๊ธ๋๋ค. ์ถํ์ ์ฌ์ฉํด์ผ ํ๋ฏ๋ก ์ ํ์ธํด๋์.
2. REST API ๊ตฌํ
- SNS ์์
๋ก๊ทธ์ธ ํ๋ก์ธ์ค
- ๋ก๊ทธ์ธ ์ต์ด ์์ฒญ ์ฒ๋ฆฌ (”/app/accounts/auth/{SocialLoginType}”)
- ์ฒซ๋ฒ์งธ๋ก ์ฌ์ฉ์๊ฐ ์น์ฌ์ดํธ์ ๋ก๊ทธ์ธ ํ๋ฉด์์ ํน์ ํ ์์ ๋ก๊ทธ์ธ ๋ฒํผ์ ํด๋ฆญํ๊ฒ ๋๋๋ฐ, ๋จผ์ ์ด ์์ฒญ์ ์ฒ๋ฆฌํด์ผ ํ๋ค.
- ์ด ์์ฒญ์ ์ ํด์ง ํ์์ผ๋ก URL์ ๊ฐ์ถฐ( ๊ฐ api ๊ฐ ์์ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธํด์ค์ผ๋ก์จ ์ฒ๋ฆฌํ๋ค.
- ์์
๋ก๊ทธ์ธ ํ์ด์ง์์ ๋ก๊ทธ์ธํ ์ดํ ์น์ธ๋ ๋ฆฌ๋๋ ์
URI๋ก ๋ฆฌ๋ค์ด๋ ํธ๋๋ค.(”/app/accounts/auth/{SocialLoginType}/callback”)
- ์ด๋ ํด๋น API ์๋ฒ๋ก๋ถํฐ 1ํ์ฉ access code๋ฅผ ๋ฐ๊ฒ ๋๋๋ฐ, ์ด ์ฝ๋๋ฅผ ์ด์ฉํด api ์๋ฒ๋ก๋ถํฐ access token๊ณผ refresh token์ ๋ฐ๊ฒ ๋๋ค.
- ์ด access token์ ์ด์ฉํด์ ์ธ๊ฐ ์ฒ๋ฆฌ๋ฅผ ์คํํ๋ฉฐ, ์์ ์๋ฒ๋ก๋ถํฐ ์ฌ์ฉ์์ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์์ฒญํ ๋๋ ์ด access token์ ์ฌ์ฉํ์ฌ ์ ๋ณด๋ฅผ ๋ฐ์์ฌ ์ ์๋ค.
- ๋ก๊ทธ์ธ ์ต์ด ์์ฒญ ์ฒ๋ฆฌ (”/app/accounts/auth/{SocialLoginType}”)
- ์์
๋ก๊ทธ์ธ์ ๊ตฌํํ๊ธฐ ์ํด์๋ ํด๋น ์๋ํํฐ์ ๋ฐ๋์ ์ ํด์ง ํ์์ ๋ง์ถฐ์ response/request๋ฅผ ์งํํด์ผ ํ๋ค.
- ๋๊ฐ์ ๊ฒฝ์ฐ๋ REST API๋ฅผ ๊ตฌํํ ์์ ์ด๋ฏ๋ก HTTP/REST ๊ท์ฝ ๋ถ๋ถ์ ์ฐธ๊ณ ํด์ ๊ฐ๋ฐํ์๋ค.
โฌ๏ธ ํ๋จ ์ฌ์ดํธ๋ฅผ ๋ฐ๋์ ์ฐธ๊ณ ํ๋ฉด์ ๊ตฌํํ ๊ฒ.
https://developers.google.com/identity/protocols/oauth2/web-server#libraries
1) ๋ก๊ทธ์ธ ์ต์ด ์์ฒญ ์ฒ๋ฆฌ
1. ์์ ๋ก๊ทธ์ธ ํ์ ์ ๊ตฌ๋ณํ๊ธฐ ์ํด์ enum ํ์์ constant๋ก ๋ง๋ค์ด์ค๋ค.
package com.example.demo.config;
public class Constant {
public enum SocialLoginType{
GOOGLE,
KAKAO,
NAVER
}
}
2. ๊ฐ ์์ ๋ก๊ทธ์ธ ํด๋์ค๊ฐ ์์๋ฐ์ ์ฌ์ฉํ ์ ์๋๋ก interface๋ฅผ ํ๋ ์์ฑํ๋ค.
package com.example.demo.src.account.social;
public interface SocialOauth {
/**
* ๊ฐ ์์
๋ก๊ทธ์ธ ํ์ด์ง๋ก redirectํ URL build
* ์ฌ์ฉ์๋ก๋ถํฐ ๋ก๊ทธ์ธ ์์ฒญ์ ๋ฐ์ ์์
๋ก๊ทธ์ธ ์๋ฒ ์ธ์ฆ์ฉ ์ฝ๋ ์์ฒญ
*/
String getOauthRedirectURL();
}
3. ์์ ๋ก๊ทธ์ธ ํ์ ๋ณ ํด๋์ค๋ฅผ ์์ฑํ๋ค.
- ์ค๋์ ๊ตฌ๊ธ ๋ก๊ทธ์ธ์ ๊ตฌํํ ์์ ์ด๋ฏ๋ก ๊ตฌ๊ธ ํด๋์ค๋ง ์์ฑํด๋๋๋ก ํ๊ฒ ๋ค.
- ์ฐจํ์ ๋ค์ํ ๋ก๊ทธ์ธ์ ๊ตฌํํ ์ ์์ผ๋ฏ๋ก ์ธํฐํ์ด์ค๋ฅผ ๋ง๋ค๊ณ ์ธ๋ถ ํด๋์ค๋ฅผ ๊ตฌํํ๋๊ฒ ์ ์ง๋ณด์์ ์ข๋ค.
@Component
@RequiredArgsConstructor
public class GoogleOauth implements SocialOauth {
@Override
public String getOauthRedirectURL(){
return "";}
}
4. ๋ก๊ทธ์ธ ๋ฐฉ์์ ๋ฐ๋ผ์ ํด๋น ํด๋์ค๋ฅผ ํธ์ถํด์ค Service class๋ฅผ ํ๋ ์์ฑํ๋ค.
@Service
@RequiredArgsConstructor
public class OAuthService {
private final GoogleOauth googleOauth;
private final HttpServletResponse response;
public void request(Constant.SocialLoginType socialLoginType) throws IOException {
String redirectURL;
switch (socialLoginType){
case GOOGLE:{
//๊ฐ ์์
๋ก๊ทธ์ธ์ ์์ฒญํ๋ฉด ์์
๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ ํด์ฃผ๋ ํ๋ก์ธ์ค์ด๋ค.
redirectURL= googleOauth.getOauthRedirectURL();
}break;
default:{
throw new IllegalArgumentException("์ ์ ์๋ ์์
๋ก๊ทธ์ธ ํ์์
๋๋ค.");
}
}
response.sendRedirect(redirectURL);
}
}
5. ๋ก๊ทธ์ธ์ ์ฃผ๊ดํ๋ ์ปจํธ๋กค๋ฌ ํด๋์ค(AccountController)์์ ์์ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ ํด์ค API๋ฅผ ๊ตฌํํ๋ค.
- path๋ก ์ด๋ค ๊ฐ์ด ๋ค์ด์ค๋๋์ ๋ฐ๋ผ์ ์ด์ service ๊ฐ์ฒด์์ switch๋ฌธ์ผ๋ก ๋ค๋ฅด๊ฒ ์ฒ๋ฆฌํ ๊ฒ์ด๋ค.
- localhost:9000/app/acoounts/auth/google๋ก ์์ฒญ์ด ๋ค์ด์จ๋ค๋ฉด, googleOauth๋ฅผ ํธ์ถํ๊ฒ ๋ ๊ฒ์ด๋ค.
/**
* ์ ์ ์์
๋ก๊ทธ์ธ์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธ ํด์ฃผ๋ url
* [GET] /accounts/auth
* @return void
*/
@NoAuth
@GetMapping("/auth/{socialLoginType}") //GOOGLE์ด ๋ค์ด์ฌ ๊ฒ์ด๋ค.
public void socialLoginRedirect(@PathVariable(name="socialLoginType") String SocialLoginPath) throws IOException {
SocialLoginType socialLoginType= SocialLoginType.valueOf(SocialLoginPath.toUpperCase());
oAuthService.request(socialLoginType);
}
6. GoogleOauth ํด๋์ค ์์ธ๊ตฌํ
1) resource ํด๋์ ์กด์ฌํ๋ applications.yml ์ ์๊น ๋ฐ๊ธ๋ฐ์ client id, client secret key๋ฑ์ ๋ฑ๋กํด์ค๋ค. ์ด๋ ๋ ธ์ถ๋๋ฉด ์๋๋ฏ๋ก ์ฝ๋์ ์ง์ ์ถ๊ฐํ์ง ์๊ณ yml ํ์ผ์์ ๋ถ๋ฌ์์ ์ฌ์ฉํ ๊ฒ์ด๋ค.
2) ๊ตฌ๊ธ api ๋ฌธ์๋ฅผ ํ์ธํ์ฌ ๊ตฌ๊ธ ์์ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์ ํ๋ ค๋ฉด ์ด๋ป๊ฒ URL์ ๊ตฌ์ฑํด์ผ ํ๋์ง ํ์ธํ๋ค.
- ํ์ฌ response.redirect(URL); ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ๊ณง๋ฐ๋ก redirect๋ฅผ ์ํค๊ณ ์์ผ๋ฉฐ url์ controller๋ก ๋ค์ ๋๊ฒจ์ฃผ๊ธฐ๋ง ํ๋ฉด ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์ ํ ์ ์๋ค.
3) URL์ ๊ตฌ์ฑํ๊ธฐ ์ํด GoogleOauth class์ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ค.
@Component
@RequiredArgsConstructor
public class GoogleOauth implements SocialOauth {
//applications.yml ์์ value annotation์ ํตํด์ ๊ฐ์ ๋ฐ์์จ๋ค.
@Value("${spring.OAuth2.google.url}")
private String GOOGLE_SNS_LOGIN_URL;
@Value("${spring.OAuth2.google.client-id}")
private String GOOGLE_SNS_CLIENT_ID;
@Value("${spring.OAuth2.google.callback-url}")
private String GOOGLE_SNS_CALLBACK_URL;
@Value("${spring.OAuth2.google.client-secret}")
private String GOOGLE_SNS_CLIENT_SECRET;
@Value("${spring.OAuth2.google.scope}")
private String GOOGLE_DATA_ACCESS_SCOPE;
private final ObjectMapper objectMapper;
@Override
public String getOauthRedirectURL(){
Map<String,Object> params=new HashMap<>();
params.put("scope",GOOGLE_DATA_ACCESS_SCOPE);
params.put("response_type","code");
params.put("client_id",GOOGLE_SNS_CLIENT_ID);
params.put("redirect_uri",GOOGLE_SNS_CALLBACK_URL);
//parameter๋ฅผ ํ์์ ๋ง์ถฐ ๊ตฌ์ฑํด์ฃผ๋ ํจ์
String parameterString=params.entrySet().stream()
.map(x->x.getKey()+"="+x.getValue())
.collect(Collectors.joining("&"));
String redirectURL=GOOGLE_SNS_LOGIN_URL+"?"+parameterString;
System.out.println("redirectURL = " + redirectURL);
return redirectURL;
/*
* https://accounts.google.com/o/oauth2/v2/auth?scope=profile&response_type=code
* &client_id="ํ ๋น๋ฐ์ id"&redirect_uri="access token ์ฒ๋ฆฌ")
* ๋ก Redirect URL์ ์์ฑํ๋ ๋ก์ง์ ๊ตฌ์ฑ
* */
}
- ์ด์ ํด๋น ๋ฉ์๋์์ url์ ๊ตฌ์ฑํด service๋ก ๋๊ฒจ์ฃผ๋ฉด service->controller๋ฅผ ๊ฑฐ์ณ ํด๋น url์ ํตํด์ ์์ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ๋จ.
7. Postman์ผ๋ก ํ ์คํธ
- localhost:9000/app/accounts/auth/google ๋ก requestํ๋ฉด ์ผ๋ จ์ ๊ณผ์ ์ ๊ฑฐ์ณ ์์ ๋ก๊ทธ์ธ ํ์ด์ง๊ฐ ๋ ๋๋ง๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
- ๊ตฌ๊ธ์ ๋ฏธ๋ฆฌ ๋ก๊ทธ์ธ๋์ด ์๋ ํฌ๋กฌ ํ๊ฒฝ์์ ์์ url๋ก ์ ์ํด๋ณด์๋ค.
- ๊ณง๋ฐ๋ก ์์ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ๋์ด ์ ์ฅ๋์ด์๋ ํ๋กํ ํ์ด์ง๊ฐ ๋ ๋๋ง๋๋ ๊ฒ์ ํ์ธํ ์ ์์๋ค.
- ํ๋กํ์ ์ ํํ๊ณ ๋๋ฉด ์ด์ ์ ๋จ์ ๋ก๊ทธ์ธ ์ฒ๋ฆฌ๋ฅผ ์งํํ redirect_uri๋ก ์ง์ ํ๋ url๋ก ๋ฆฌ๋ค์ด๋ ํธ๋๋ค.
2) ์์ ๋ก๊ทธ์ธ ์ดํ ์์ฒญ ์ฒ๋ฆฌ
1. ์ด์ ์ ๊ตฌ๊ธ์ ๋ฑ๋กํด๋๋ callback api๋ฅผ controller์์ ๊ฐ๋ฐํ๋ค.
- ์์ ๋ก๊ทธ์ธ๊ณผ ๊ด๋ จ๋ ์๋น์ค๋ฅผ ์ฒ๋ฆฌํ๋ oAuthService ์ผ๋ก ์์ ๋ก๊ทธ์ธ ๊ฒฐ๊ณผ๋ก ๋ฐ์์จ ์ผํ์ฑ ์ฝ๋๋ฅผ ๋ณด๋ด์ ์๋ํํฐ๋ก๋ถํฐ ์ก์ธ์ค ํ ํฐ์ ๋ฐ์์ค๊ณ , ๊ทธ ์ก์ธ์ค ํ ํฐ์ ๋ค์ ๋ณด๋ด ์๋ํํฐ์ ์ ์ฅ๋ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ฐ์์ค๋ ์ผ๋ จ์ ๊ณผ์ ์ ๊ฑฐ์น ๊ฒ์ด๋ค.
- ๊ทธ ๊ณผ์ ์ ๊ฒฐ๊ณผ๋ก ๋ค์ ์๋ฒ์ ์ ๋ณด๋ฅผ ์์ฒญํ ๋ ํ์ํ access_token, ์ฐ๋ฆฌ ์๋ฒ์์ ํ์ ์ธ๊ฐ์ฒ๋ฆฌ๋ฅผ ํ jwt_token, ๊ทธ๋ฆฌ๊ณ ์ถํ์ ์กฐํ๋ฑ์ ํ์ํ user_num๋ฑ์ ์ ๋ณด๋ฅผ ๋ฐ์์ฌ ๊ฒ์ด๋ค.
/**
* Social Login API Server ์์ฒญ์ ์ํ callback ์ ์ฒ๋ฆฌ
* @param socialLoginPath (GOOGLE, FACEBOOK, NAVER, KAKAO)
* @param code API Server ๋ก๋ถํฐ ๋์ด์ค๋ code
* @return SNS Login ์์ฒญ ๊ฒฐ๊ณผ๋ก ๋ฐ์ Json ํํ์ java ๊ฐ์ฒด (access_token, jwt_token, user_num ๋ฑ)
*/
@NoAuth
@ResponseBody
@GetMapping(value = "/auth/{socialLoginType}/callback")
public BaseResponse<GetSocialOAuthRes> callback (
@PathVariable(name = "socialLoginType") String socialLoginPath,
@RequestParam(name = "code") String code)throws IOException,BaseException{
System.out.println(">> ์์
๋ก๊ทธ์ธ API ์๋ฒ๋ก๋ถํฐ ๋ฐ์ code :"+ code);
SocialLoginType socialLoginType= SocialLoginType.valueOf(socialLoginPath.toUpperCase());
GetSocialOAuthRes getSocialOAuthRes=oAuthService.oAuthLogin(socialLoginType,code);
return new BaseResponse<>(getSocialOAuthRes);
}
2. ์ฐ์ model์ ๋ชจ์๋ ํจํค์ง์ ์๋ฒ์ ํต์ ํ๊ธฐ ์ํ ์ฌ๋ฌ ์๋ฐ ํด๋์ค๋ฅผ ๋ง๋ ๋ค.
//๊ตฌ๊ธ์ ์ผํ์ฑ ์ฝ๋๋ฅผ ๋ค์ ๋ณด๋ด ๋ฐ์์ฌ ์ก์ธ์ค ํ ํฐ์ ํฌํจํ JSON ๋ฌธ์์ด์ ๋ด์ ํด๋์ค
@AllArgsConstructor
@Getter
@Setter
public class GoogleOAuthToken {
private String access_token;
private int expires_in;
private String scope;
private String token_type;
private String id_token;
}
//๊ตฌ๊ธ(์๋ํํฐ)๋ก ์ก์ธ์ค ํ ํฐ์ ๋ณด๋ด ๋ฐ์์ฌ ๊ตฌ๊ธ์ ๋ฑ๋ก๋ ์ฌ์ฉ์ ์ ๋ณด
@AllArgsConstructor
@Getter
@Setter
public class GoogleUser {
public String id;
public String email;
public Boolean verifiedEmail;
public String name;
public String givenName;
public String familyName;
public String picture;
public String locale;
}
//ํด๋ผ์ด์ธํธ๋ก ๋ณด๋ผ jwtToken, accessToken๋ฑ์ด ๋ด๊ธด ๊ฐ์ฒด
@Getter
@Setter
@AllArgsConstructor
public class GetSocialOAuthRes {
private String jwtToken;
private int user_num;
private String accessToken;
private String tokenType;
}
3. ์ด์ oAuthService ํด๋์ค์ ์ ๋ฐ์ ์ธ ๊ตฌ๊ธ๊ณผ์ ํต์ ํ๋ก์ธ์ค๋ฅผ ์ฒ๋ฆฌํ oAuthLogin ๋ฉ์๋๋ฅผ ๋ง๋ค์ด์ฃผ๋๋ก ํ๊ฒ ๋ค.
public GetSocialOAuthRes oAuthLogin(Constant.SocialLoginType socialLoginType, String code) throws IOException {
switch (socialLoginType){
case GOOGLE:{
//๊ตฌ๊ธ๋ก ์ผํ์ฑ ์ฝ๋๋ฅผ ๋ณด๋ด ์ก์ธ์ค ํ ํฐ์ด ๋ด๊ธด ์๋ต๊ฐ์ฒด๋ฅผ ๋ฐ์์ด
ResponseEntity<String> accessTokenResponse= googleOauth.requestAccessToken(code);
//์๋ต ๊ฐ์ฒด๊ฐ JSONํ์์ผ๋ก ๋์ด ์์ผ๋ฏ๋ก, ์ด๋ฅผ deserializationํด์ ์๋ฐ ๊ฐ์ฒด์ ๋ด์ ๊ฒ์ด๋ค.
GoogleOAuthToken oAuthToken=googleOauth.getAccessToken(accessTokenResponse);
//์ก์ธ์ค ํ ํฐ์ ๋ค์ ๊ตฌ๊ธ๋ก ๋ณด๋ด ๊ตฌ๊ธ์ ์ ์ฅ๋ ์ฌ์ฉ์ ์ ๋ณด๊ฐ ๋ด๊ธด ์๋ต ๊ฐ์ฒด๋ฅผ ๋ฐ์์จ๋ค.
ResponseEntity<String> userInfoResponse=googleOauth.requestUserInfo(oAuthToken);
//๋ค์ JSON ํ์์ ์๋ต ๊ฐ์ฒด๋ฅผ ์๋ฐ ๊ฐ์ฒด๋ก ์ญ์ง๋ ฌํํ๋ค.
GoogleUser googleUser= googleOauth.getUserInfo(userInfoResponse);
}
}
default:{
throw new IllegalArgumentException("์ ์ ์๋ ์์
๋ก๊ทธ์ธ ํ์์
๋๋ค.");
}
}
4. ๊ตฌ๊ธ๊ณผ ํต์ ํ๊ธฐ ์ํ ์ฌ๋ฌ ๋ฉ์๋๋ฅผ GoogleOAuth ํด๋์ค์ ๊ฐ๋ฐํ๋ค.
1) ๋จผ์ ์ผํ์ฉ ์ฝ๋๋ฅผ ๋ค์ ๊ตฌ๊ธ๋ก ๋ณด๋ด ์ก์ธ์ค ํ ํฐ์ ํฌํจํ JSON String์ด ๋ด๊ธด ResponseEntity๋ฅผ ๋ฐ์์จ๋ค.
- ์ก์ธ์ค ํ ํฐ์ ๋ฐ์์ค๊ธฐ ์ํด ํ์ํ ํ๋ผ๋ฏธํฐ๋ค์ ๊ตฌ๊ธ ๊ณต์ ๋ฌธ์์์ ํ์ธํ ์ ์๋ค.
public ResponseEntity<String> requestAccessToken(String code) {
String GOOGLE_TOKEN_REQUEST_URL="https://oauth2.googleapis.com/token";
RestTemplate restTemplate=new RestTemplate();
Map<String, Object> params = new HashMap<>();
params.put("code", code);
params.put("client_id", GOOGLE_SNS_CLIENT_ID);
params.put("client_secret", GOOGLE_SNS_CLIENT_SECRET);
params.put("redirect_uri", GOOGLE_SNS_CALLBACK_URL);
params.put("grant_type", "authorization_code");
ResponseEntity<String> responseEntity=restTemplate.postForEntity(GOOGLE_TOKEN_REQUEST_URL,
params,String.class);
if(responseEntity.getStatusCode()== HttpStatus.OK){
return responseEntity;
}
return null;
}
+) RestTemplate
- Spring boot์์๋ ๋ค๋ฅธ ์๋ฒ์ API endpoint๋ฅผ ํธ์ถํ ๋ RestTemplate๋ฅผ ๋ง์ด ์ด๋ค.
- HTTP ์๋ฒ์์ ํต์ ์ ๋จ์ํํ๊ณ RESTful ์์น์ ์งํจ๋ค.(json, xml์ ์ฝ๊ฒ ์๋ต ๋ฐ๋๋ค.
//์ฐ์ RestTemplate๋ฅผ ๋งค๋ฒ ์๋ก ์์ฑํ๊ธฐ ๋ณด๋ค ๋น์ผ๋ก ๋ง๋ค์ด์ ์์กด์ฑ์ ์ฃผ์
๋ฐ๋๋ก ํ๋ค.
@Configuration
public class RestTemplateConfig {
//HTTP get,post ์์ฒญ์ ๋ ๋ฆด๋ ์ผ์ ํ ํ์์ ๋ง์ถฐ์ฃผ๋ template
@Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder
.requestFactory(() -> new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()))
.additionalMessageConverters(new StringHttpMessageConverter(Charset.forName("UTF-8")))
.build();
}
}
//ํ์ฉ ์์
restTemplate.exchange(url, httpMethod, new HttpEntity<>(body, httpHeaders), class);
restTemplate.postForEntity(url, parameter,class);
2) responseEntity์ ๋ด๊ธด JSON String์ ์ญ์ง๋ ฌํํด ์๋ฐ ๊ฐ์ฒด์ ๋ด๋๋ค.
public GoogleOAuthToken getAccessToken(ResponseEntity<String> response) throws JsonProcessingException {
System.out.println("response.getBody() = " + response.getBody());
GoogleOAuthToken googleOAuthToken= objectMapper.readValue(response.getBody(),GoogleOAuthToken.class);
return googleOAuthToken;
}
3) ๋ค์ ๊ตฌ๊ธ๋ก ์ก์ธ์ค ํ ํฐ์ ๋ณด๋ด ๊ตฌ๊ธ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ฐ์์จ๋ค.
- ์ญ์ ๊ตฌ๊ธ api ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด request ํ์์ ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌ์ฑํด์ผ ํ๋ค.
- ์ด์ ์ ๊ณ์ ํ๋ผ๋ฏธํฐ๋ก request๋ฅผ ๋ณด๋์ผ๋, ์ด๋ฒ์ ํค๋์ access token์ ๋ด๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํด ๋ณด์๋ค.
public ResponseEntity<String> requestUserInfo(GoogleOAuthToken oAuthToken) {
String GOOGLE_USERINFO_REQUEST_URL="https://www.googleapis.com/oauth2/v1/userinfo";
//header์ accessToken์ ๋ด๋๋ค.
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization","Bearer "+oAuthToken.getAccess_token());
//HttpEntity๋ฅผ ํ๋ ์์ฑํด ํค๋๋ฅผ ๋ด์์ restTemplate์ผ๋ก ๊ตฌ๊ธ๊ณผ ํต์ ํ๊ฒ ๋๋ค.
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity(headers);
ResponseEntity<String> response=restTemplate.exchange(GOOGLE_USERINFO_REQUEST_URL, HttpMethod.GET,request,String.class);
System.out.println("response.getBody() = " + response.getBody());
return response;
}
< ๊ธฐ๋ณธ ๊ฐ๋ ์ ๋ฆฌ>
- HttpHeaders
- Header์ ์ํ๋ ๋ฐฉ์์ผ๋ก key-value๊ฐ์ ์ค์ ํด์ ๋ณด๋ผ ์ ์๋ ๊ฐ์ฒด์ด๋ค.
- Spring.io์ ์ค๋ช ์ ๋ค์๊ณผ ๊ฐ๋ค.
- HTTPEntity<T>
- HTTPEntity๋ http ์์ฒญ/์๋ต์ ํด๋นํ๋ HTTPHeader์ HTTPBody๋ฅผ ํฌํจํ๋ ๊ฐ์ฒด์ด๋ค.
- ๋ฐ๋ผ์ HttpEntity๋ฅผ ์์ฑํ ๋ header๋ฅผ ์์ฑ์ ํ๋ผ๋ฏธํฐ๋ก ์ถ๊ฐํ ์ ์๋ค.
public class HttpEntity<T> {
public static final HttpEntity<?> EMPTY = new HttpEntity();
private final HttpHeaders headers;
private final T body;
...
}
- ResponseEntity
- ์ผ๋ฐ์ ์ธ API๋ ๋ฐํํ๋ ๋ฆฌ์์ค์ Value๋ง ์์ง ์์ผ๋ฉฐ, ์ํ ์ฝ๋, ์๋ต ๋ฉ์ธ์ง ๋ฑ์ด ํฌํจ๋ ์ ์๋ค.
- ๋ฐ๋ผ์ ResponseEntity๋ client๊ฐ ๋ณด๋ด๋ ์ฌ๋ฌ๊ฐ์ง ์๋ต ๋ด์ฉ์ ๊ท๊ฒฉ์ ๋ง๊ฒ ํ๋ฒ ๊ฐ์ธ์ฃผ๋ ์ญํ ์ ํ๋ค.
- ๊ฐ์ ์ญํ ๋ก๋ @ResponseBody ์ด๋ ธํ ์ด์ ์ด ์๋ค.
- HttpEntity๋ฅผ ์์๋ฐ๊ณ ์๋ ํด๋์ค์ด๋ค.
- MultiValueMap
- MultiValueMap์ ํค ํ๋์ ์ฌ๋ฌ๊ฐ์ value๋ฅผ ๊ฐ์ง๊ฒ ํด์ฃผ๋ map์ด๋ค. key-value ๊ฐ๊ฐ ํ๋์ฉ์ด ์์ผ๋ก ์ด๋ค์ง๋ map๊ณผ๋ ๋ค๋ฅด๋ค.
- ์ค์ํ ํน์ง์ค์ ํ๋๋ RestTemplate์ exchange ๋ฉ์๋์ ์ธ๋ฒ์งธ ํ๋ผ๋ฏธํฐ request์๋ Hashmap์ ์ง์ํ์ง ์์ ๋ฌด์กฐ๊ฑด MultiValueMap์ ์ฌ์ฉํด์ผ ํ๋ค๋ ๊ฒ์ด๋ค.
- HTTP Request์์ URL์ ์ ์กํ ๋ name="jennie"&name="lisa"&name="peter" ์ ๊ฐ์ด ๊ฐ์ ํ๋ผ๋ฏธํฐ์ ์ฌ๋ฌ๊ฐ์ง ๊ฐ์ด ์ ์ก๋๋ ์ฌ๋ก๊ฐ ์๊ธฐ ๋๋ฌธ์ด๋ค.
4) ๋ง์ง๋ง์ผ๋ก, ์ด ๊ตฌ๊ธ ์ ์ ์ ๋ณด๊ฐ ๋ด๊ธด JSON ๋ฌธ์์ด์ ํ์ฑํ์ฌ GoogleUser ๊ฐ์ฒด์ ๋ด์์ฃผ๋ฉด ๋๋ค.
public GoogleUser getUserInfo(ResponseEntity<String> userInfoRes) throws JsonProcessingException{
GoogleUser googleUser=objectMapper.readValue(userInfoRes.getBody(),GoogleUser.class);
return googleUser;
}
5. ์ด์ ๋ง์ง๋ง์ผ๋ก ๊ตฌ๊ธ์์ ๋ฐ์์จ ์ ์ ์ ๋ณด๋ฅผ ์ด์ฉํด ์ผ๋ จ์ ์ ๋ณด๊ฐ ๋ด๊ธด ๊ฐ์ฒด๋ฅผ ์ปจํธ๋กค๋ฌ๋ก ๋๊ฒจ์ค๋ค.
- ์์์ ์ป์ GoogleUser๊ฐ์ฒด์ ๋ด๊ธด user_id๋ฅผ ์ถ์ถํ๋ค.
- ํด๋น id๋ฅผ ์ฐ๋ฆฌ ์๋ฒ์ db์ ๊ฒ์ฆํ๊ณ , ํ์ ๊ฐ์
์ด ๋์ด์์ผ๋ฉด ์์ผ๋ก ์ธ๊ฐ ์ฒ๋ฆฌ๋ฅผ ์ํด jwtToken์ ๋ฐ๊ธํ๋ค.
- jwtToken์ ๋ฐ๊ธํ๋ jwtService ๊ฐ๋ฐ์ ์ด์ ํฌ์คํ ์์ ํ์ธํ ์ ์๋ค.
- ํ์๊ฐ์ ์ด ๋์ด์์ง ์์ผ๋ฉด ํด๋ผ์ด์ธํธ๋ก ์ค๋ฅ๋ฅผ ์ ์กํ๋ค.
- ์ด์ ํด๋ผ์ด์ธํธ์๊ฒ ๋ณด๋ด์ค GetSocialOauthRest ๊ฐ์ฒด๋ฅผ ๊ตฌ์ฑํ๋๋ฐ, ๋ด๊ธด ๋ด์ฉ์ ๋ค์๊ณผ ๊ฐ๋ค.
- ํ์ ์ธ๊ฐ์ฒ๋ฆฌ๋ฅผ ์ํ jwtToken
- ์ ์ ์ ํ์๋ฒํธ
- ์์ผ๋ก ๊ตฌ๊ธ๊ณผ ํต์ ํ ๋ ์ฌ์ฉํ accessToken
- Token์ ํ์
public GetSocialOAuthRes oAuthLogin(Constant.SocialLoginType socialLoginType, String code) throws IOException {
switch (socialLoginType){
case GOOGLE:{
//๊ตฌ๊ธ๋ก ์ผํ์ฑ ์ฝ๋๋ฅผ ๋ณด๋ด ์ก์ธ์ค ํ ํฐ์ด ๋ด๊ธด ์๋ต๊ฐ์ฒด๋ฅผ ๋ฐ์์ด
ResponseEntity<String> accessTokenResponse= googleOauth.requestAccessToken(code);
//์๋ต ๊ฐ์ฒด๊ฐ JSONํ์์ผ๋ก ๋์ด ์์ผ๋ฏ๋ก, ์ด๋ฅผ deserializationํด์ ์๋ฐ ๊ฐ์ฒด์ ๋ด์ ๊ฒ์ด๋ค.
GoogleOAuthToken oAuthToken=googleOauth.getAccessToken(accessTokenResponse);
//์ก์ธ์ค ํ ํฐ์ ๋ค์ ๊ตฌ๊ธ๋ก ๋ณด๋ด ๊ตฌ๊ธ์ ์ ์ฅ๋ ์ฌ์ฉ์ ์ ๋ณด๊ฐ ๋ด๊ธด ์๋ต ๊ฐ์ฒด๋ฅผ ๋ฐ์์จ๋ค.
ResponseEntity<String> userInfoResponse=googleOauth.requestUserInfo(oAuthToken);
//๋ค์ JSON ํ์์ ์๋ต ๊ฐ์ฒด๋ฅผ ์๋ฐ ๊ฐ์ฒด๋ก ์ญ์ง๋ ฌํํ๋ค.
GoogleUser googleUser= googleOauth.getUserInfo(userInfoResponse);
String user_id=googleUser.getEmail();
//์ฐ๋ฆฌ ์๋ฒ์ db์ ๋์กฐํ์ฌ ํด๋น user๊ฐ ์กด์ฌํ๋ ์ง ํ์ธํ๋ค.
int user_num=accountProvider.getUserNum(user_id);
if(user_num!=0){
//์๋ฒ์ user๊ฐ ์กด์ฌํ๋ฉด ์์ผ๋ก ํ์ ์ธ๊ฐ ์ฒ๋ฆฌ๋ฅผ ์ํ jwtToken์ ๋ฐ๊ธํ๋ค.
String jwtToken=jwtService.createJwt(user_num,user_id);
//์ก์ธ์ค ํ ํฐ๊ณผ jwtToken, ์ด์ธ ์ ๋ณด๋ค์ด ๋ด๊ธด ์๋ฐ ๊ฐ์ฒด๋ฅผ ๋ค์ ์ ์กํ๋ค.
GetSocialOAuthRes getSocialOAuthRes=new GetSocialOAuthRes(jwtToken,user_num,oAuthToken.getAccess_token(),oAuthToken.getToken_type());
return getSocialOAuthRes;
}
else {
throw new BaseException(BaseResponseStatus.ACCOUNT_DOESNT_EXISTS);
}
}
default:{
throw new IllegalArgumentException("์ ์ ์๋ ์์
๋ก๊ทธ์ธ ํ์์
๋๋ค.");
}
}
3. ์ค์ ์์ ๋ก๊ทธ์ธ ํ๋ก์ธ์ค
- localhost:9000/app/accounts/auth/google ๋ก ์ ๊ทผ
- ๊ณง๋ฐ๋ก ์์ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ๋จ.
3. ํ๋กํ ์ ํ (๋ก๊ทธ์ธ) ์ดํ์ ๋ฏธ๋ฆฌ ์ง์ ํด๋ callback uri(๋ฏธ๋ฆฌ ๊ตฌ๊ธ์ ๋ฑ๋กํด์ผ ํจ)๋ก ๋ฆฌ๋ค์ด๋ ํธ๋๋ฉด์ ๊ตฌ๊ธ์์ ๋ด ์๋ฒ๋ก ์ผํ์ฑ ์ ๊ทผ ์ฝ๋ ๋ณด๋
4. ์ผํ์ฑ ์ฝ๋๋ฅผ ๋ค์ ์๋ํํฐ๋ก ๋ณด๋ด ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ์์ฒญํ ์ ์๋ ์ก์ธ์ค ํ ํฐ์ response์ ๋ด์์ค๋๋ก ํ๋ค.
5. ์ด ์ก์ธ์ค ํ ํฐ์ ๋ค์ ์๋ํํฐ๋ก ๋ณด๋ด์ ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ๋ฐ์์จ๋ค.
6. ๋ฐ์์จ ๊ตฌ๊ธ ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ๋ด db์ ๋น๊ตํ์ฌ JWT ํ ํฐ์ ๋ฐ๊ธํ๊ณ , ์ด๋ฅผ ํฌํจํด ์ก์ธ์ค ํ ํฐ, ์ ์ ํ์ ๋ฒํธ ๋ฑ์ด ๋ด๊ธด ๊ฐ์ฒด๋ฅผ ํด๋ผ์ด์ธํธ์๊ฒ ์๋ต์ผ๋ก ๋ณด๋ด์ค๋ค.
7. ์๋ต ์๋ฃ! JSON ํํ๋ก ์ ๋ด์ฉ์ด ๋์์ค๋ ๊ฒ์ ํ์ธ ํ ์ ์๋ค.
+) ์๊ฐ๋ณด๋ค ๊ณผ์ ์ด ๋ง์์ ๋ณต์กํ์์ง๋ง ๊ฐ๋ฐ์ด ์ ์๋ฃ๋์ด์ ๊ธฐ์๋ค! ๊ณต์ ๋ฌธ์๋ฅผ ์ ์ฝ์ด๋ณด๋ฉด์ ๊ฐ๋ฐํ๋๊ฒ ํ์์ ์ธ ๊ฒ ๊ฐ๊ณ , ๋ค์์๋ Spring Security, JPA๋ฑ์ ๋์ ํด์ ๋ ์์ ํ ์ธ์ฆ ํ๋ก์ธ์ค๊ฐ ๋๋๋ก ๋ฆฌํฉํ ๋ง๋ ํด๋ณด๊ณ ์ถ๋ค!
๐์ฐธ๊ณ ์๋ฃ
https://developers.google.com/identity/protocols/oauth2/web-server#libraries
https://antdev.tistory.com/70?category=919963
https://tecoble.techcourse.co.kr/post/2021-05-10-response-entity/
'Web > Java (Spring+JSP)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring Boot& Mongo DB & AWS EC2์์์ Timezone Sync์ ๋ํ ๊ณ ์ฐฐ (0) | 2022.06.19 |
---|---|
Spring Webflux/Netty/MongoDB๋ก ์ฑํ ์๋ฒ ๊ตฌํ (0) | 2022.05.24 |
JWT์ ํ์ฉํ ๋ก๊ทธ์ธ/ Interceptor๋ฅผ ํ์ฉํ ์ธ๊ฐ ์ฒ๋ฆฌ ๊ตฌํ (0) | 2022.03.11 |
๐ Spring Boot๋ก ๊ฐ๋ฐํ Restful API ์๋ ํ๋ก์ธ์ค (0) | 2022.03.03 |
Spring ํต์ฌ ์๋ฆฌ #9- ์ปดํฌ๋ํธ ์ค์บ (0) | 2022.01.15 |