[Security] OAuth2.0 ๋ค์ด๋ฒ, ์นด์นด์ค ๋ก๊ทธ์ธ ๊ธฐ๋ฅ ์ถ๊ฐ
by rlaehddnd0422์ด๋ฒ ํฌ์คํ ์์๋ ์ง๋ ํฌ์คํ ์ ์ด์ด OAuth2.0์ ์ด์ฉํด ๋ค์ด๋ฒ์ ์นด์นด์ค ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ์ถ๊ฐํด๋ด ์๋ค.
Spring Security์์๋ Google, Twitter, Facebook๊ณผ ๊ฐ์ ๊ธ๋ก๋ฒํ๊ฒ ์ฌ์ฉ๋๋ ํ๋ซํผ์ ํํด์๋ Provider๋ฅผ ์ง์ํ์ง๋ง ๋ค์ด๋ฒ์ ์นด์นด์ค์ ๊ฐ์ด ํน์ ๋๋ผ์ ํํด์๋ Provider๋ฅผ ๋ณ๋๋ก ์ง์ํ์ง ์์ต๋๋ค.
๋ฐ๋ผ์ ๋ค์ด๋ฒ, ์นด์นด์ค ์์ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ธฐ ์ํด์๋ ์ ํ๋ฆฌ์ผ์ด์ ๋ถ๊ฐ์ ๋ณด์ provider๋ฅผ ๋ณ๋๋ก ์ถ๊ฐํด ์ฃผ์ด์ผ ํฉ๋๋ค.
Naver ๋ก๊ทธ์ธ API ๋ฐ๊ธ ๋ฐ ์ค์
Naver๋ ์๋ ๋ค์ด๋ฒ ๊ฐ๋ฐ์ ์ฌ์ดํธ์์ API๋ฅผ ๋ฐ๊ธ๋ฐ๊ณ , Authorization URI ์ Token ๋ฐ๊ธ URI๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
applicaiton.yml์ ์ถ๊ฐ
provider:
naver:
authorization-uri: https://nid.naver.com/oauth2.0/authorize
token-uri: https://nid.naver.com/oauth2.0/token
user-info-uri: https://openapi.naver.com/v1/nid/me
user-name-attribute: response
- ๋ค์ด๋ฒ๋ Spring Security๊ฐ ๋ณ๋๋ก provider๋ฅผ ์ ๊ณตํ์ง ์๊ธฐ ๋๋ฌธ์ ์ ๋งํฌ์ ๋ฌธ์๋ฅผ ๋ณด๊ณ ๋ณ๋๋ก ๋ง๋ค์ด ์ค์๋ค.
- Naver์์๋ ์์ฒญ ์ ๋ณด๋ฅผ response ํค์ ๋ด์์ ๋ฆฌํดํด์ค๋ค๊ณ ํ๋, user-name-attribute๋ฅผ response๋ก ์ค์ ํด์ค์๋ค.
naver:
client-id: zmffkdldjsxmdkdleldlqslekfkawnlTjsej
client-secret: qlalfdlqslekfkawnl
scope:
- name
- email
client-name: Naver
authorization-grant-type: authorization_code
redirect-uri : http://localhost:8080/login/oauth2/code/naver
- ์ ๋งํฌ์์ ๋ฐ๊ธ๋ฐ์ ํด๋ผ์ด์ธํธ ์์ด๋์ ์ํฌ๋ฆฟ ํค, ๋ฐ์์ฌ ์ค์ฝํ, ํด๋ผ์ด์ธํธ ์ด๋ฆ, ์ฝ๋๋ฅผ ๋ฐ๊ธ๋ฐ์ ๋ฆฌ๋ค์ด๋ ํธ URI๋ฅผ ์ง์ ํด์ค์๋ค.
- authorization-grant-type : OAuth2.0 ์์ ๋ก๊ทธ์ธ ๋ฐฉ์์ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์์ง๋ง, ๊ตฌ๊ธ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก authorization_code ๋ฐฉ์์ ์ฌ์ฉํ๋ฏ๋ก authorization_code๋ก ์ง์ ํด์ค๋๋ค.
loginForm์ ์ถ๊ฐ
<a href="/oauth2/authorization/naver">๋ค์ด๋ฒ ๋ก๊ทธ์ธ</a>
- ์ด href์์ /oauth2/authorization/{registrationId} ๋ Spring Security์์ ์ ํด๋ ์์์ด๊ธฐ ๋๋ฌธ์ ๋ฐ๋์ ์์ ํ์์ ๋ง์ถฐ์ฃผ์ด์ผ application.yml์ ์ค์ ํด์ค Authorization URI๋ก ์ด๋ํฉ๋๋ค. ์ ํ์์ ๊ผญ ์งํค๋๋ก ํฉ์๋ค.
Kakao ๋ก๊ทธ์ธ API ๋ฐ๊ธ ๋ฐ ์ค์
์นด์นด์ค๋ ์๋ ์นด์นด์ค ๊ฐ๋ฐ์ ์ฌ์ดํธ๋ฅผ ํตํด API๋ฅผ ๋ฐ๊ธ๋ฐ๊ณ , Authorizaiton URI์ Token ๋ฐ๊ธ URI๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
application.yml์ ์ถ๊ฐ
provider:
kakao:
authorization-uri: https://kauth.kakao.com/oauth/authorize
token-uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
user-name-attribute: id
- ์นด์นด์ค๋ํ Spring Security๊ฐ ๋ณ๋๋ก provider๋ฅผ ์ ๊ณตํ์ง ์๊ธฐ ๋๋ฌธ์ ์ ๋งํฌ์ ๋ฌธ์๋ฅผ ๋ณด๊ณ ๋ณ๋๋ก ๋ง๋ค์ด ์ค์๋ค.
- ์นด์นด์ค์์๋ ์์ฒญ ์ ๋ณด๋ฅผ id ํค์ ๋ด์์ ๋ฆฌํดํฉ๋๋ค. user-name-attribute๋ฅผ id๋ก ์ค์ ํด์ค์๋ค.
kakao:
client-id: 55d6f3e6d86d66049f210faad338113f
client-secret: DzTWAOL22KXwKyZiaecjivShsBnQxSL7
scope:
- profile_nickname
- account_email
client-name: Kakao
redirect-uri: http://localhost:8080/login/oauth2/code/kakao
authorization-grant-type: authorization_code
client-authentication-method: POST
- ์ ๋งํฌ์์ ๋ฐ๊ธ๋ฐ์ ํด๋ผ์ด์ธํธ ์์ด๋์ ์ํฌ๋ฆฟ ํค, ๋ฐ์์ฌ ์ค์ฝํ, ํด๋ผ์ด์ธํธ ์ด๋ฆ, ์ฝ๋๋ฅผ ๋ฐ๊ธ๋ฐ์ ๋ฆฌ๋ค์ด๋ ํธ URI๋ฅผ ์ง์ ํด์ค์๋ค.
- authorization-grant-type : OAuth2.0 ์์ ๋ก๊ทธ์ธ ๋ฐฉ์์ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์์ง๋ง, ๊ตฌ๊ธ ๋ค์ด๋ฒ์ ๋ง์ฐฌ๊ฐ์ง๋ก authorization_code ๋ฐฉ์์ ์ฌ์ฉํ๋ฏ๋ก authorization_code๋ก ์ง์ ํด์ค๋๋ค.
- ์ฃผ์ํ ์ : ์นด์นด์ค๋ client-authentication-method๋ฅผ ๋ฐ๋์ POST๋ก ์ง์ ํด์ฃผ์ด์ผ ํฉ๋๋ค.
loginForm์ ์ถ๊ฐ
<a href="/oauth2/authorization/naver">๋ค์ด๋ฒ ๋ก๊ทธ์ธ</a>
- ์ด href์์ /oauth2/authorization/{registrationId} ๋ Spring Security์์ ์ ํด๋ ์์์ด๊ธฐ ๋๋ฌธ์ ๋ฐ๋์ ์์ ํ์์ ๋ง์ถฐ์ฃผ์ด์ผ application.yml์ ์ค์ ํด์ค Authorization URI๋ก ์ด๋ํฉ๋๋ค. ์ ํ์์ ๊ผญ ์งํค๋๋ก ํฉ์๋ค.
๋ก๊ทธ์ธ ์ดํ ํ์ฒ๋ฆฌ ์๋น์ค๋ ๊ธฐ์กด์ ๊ตฌ๊ธ ๋ก๊ทธ์ธ ํ์ฒ๋ฆฌ ์๋น์ค ํด๋์ค๋ฅผ ์ปค์คํ ํด์ ๊ตฌ๊ธ,๋ค์ด๋ฒ, ์นด์นด์ค ๋ชจ๋ ํ ๊ณณ์์ ์ฒ๋ฆฌํ๋๋ก ๋ฆฌํํ ๋งํด๋ด ์๋ค.
PrincipalOauth2userService ๋ฆฌํํ ๋ง
์ฐ์ PrincipalOauth2userService์์ DB์ ์์ ๋ก๊ทธ์ธ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ฝ์ ํ๊ธฐ ์ํด์ผ ํ๋๋ฐ, ํ๋ซํผ๋ง๋ค Attribute์ name์ด ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ DB์ ์ ์ฅํ ์์ฑ๋ค์ ๊ณตํต ์ธํฐํ์ด์ค๋ก ํ๋ ๋ง๋ค์ด ๋๊ณ ํ๋ซํผ์ ๋ฐ๋ผ ๊ตฌํ์ฒด๋ฅผ ๋ง๋ค์ด์ ๊ฐ ํ๋ซํผ์ด ๋์ ธ์ฃผ๋ attribute name์ ๋ง๊ฒ ์์ ํด๋ด ์๋ค.
OAuth ์ฌ์ฉ์ ์ ๋ณด ๊ณตํต ์ธํฐํ์ด์ค
// OAuth2.0 ์ ๊ณต์๋ค ๋ง๋ค ์๋ตํด์ฃผ๋ ์์ฑ๊ฐ์ด ๋ฌ๋ผ์ ๊ณตํต์ผ๋ก ๋ง๋ค์ด์ค๋ค.
public interface OAuth2UserInfo {
String getProviderId();
String getProvider();
String getEmail();
String getName();
}
- GoogleUserInfo
public class GoogleUserInfo implements OAuth2UserInfo {
private Map<String, Object> attributes;
public GoogleUserInfo(Map<String, Object> attributes) {
this.attributes = attributes;
}
@Override
public String getProviderId() {
return (String) attributes.get("sub");
}
@Override
public String getProvider() {
return "google";
}
@Override
public String getEmail() {
return (String) attributes.get("email");
}
@Override
public String getName() {
return (String) attributes.get("name");
}
}
- ๊ตฌ๊ธ์ Provider Id๋ sub, ์ด๋ฉ์ผ์ email, ์ด๋ฆ์ name ์ attribute-name์ผ๋ก ๋ฆฌํดํฉ๋๋ค.
- NaverUserInfo
public class NaverUserInfo implements OAuth2UserInfo {
private Map<String, Object> attributes;
public NaverUserInfo(Map<String, Object> attributes) {
this.attributes = attributes;
}
@Override
public String getProviderId() {
return (String) attributes.get("id");
}
@Override
public String getProvider() {
return "naver";
}
@Override
public String getEmail() {
return (String) attributes.get("email");
}
@Override
public String getName() {
return (String) attributes.get("name");
}
}
- ๋ค์ด๋ฒ๋ Provider id๋ฅผ id, ์ด๋ฉ์ผ์ email, ์ด๋ฆ์ name ์ attribute-name์ผ๋ก ๋ฆฌํดํฉ๋๋ค.
- KakaoUserInfo
public class KakaoUserInfo implements OAuth2UserInfo {
private Map<String, Object> attributes;
public KakaoUserInfo(Map<String, Object> attributes) {
this.attributes = attributes;
}
@Override
public String getProviderId() {
return (String) attributes.get("id").toString();
}
@Override
public String getProvider() {
return "kakao";
}
@Override
public String getEmail() {
return (String) attributes.get("account_email");
}
@Override
public String getName() {
return (String) attributes.get("profile_nickname");
}
}
- ์นด์นด์ค๋ id๋ฅผ Long ํ์ ์ผ๋ก, ์ด๋ฉ์ผ์ account_email๋ก, ์ด๋ฆ์ profile_nickname์ผ๋ก ๋ฆฌํดํฉ๋๋ค.
- PrincipalOauth2UserService ๋ฆฌํํ ๋ง
@Service
@RequiredArgsConstructor
@Slf4j
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {
private final UserRepository userRepository;
// ๊ตฌ๊ธ๋ก ๋ถํฐ ๋ฐ์ userRequest์ ๋ํ ํ์ฒ๋ฆฌ ํจ์
// ํจ์ ์ข
๋ฃ์ @AuthenticationPrincipal ์ด๋
ธํ
์ด์
์ด ๋ง๋ค์ด์ง.
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
OAuth2UserInfo oAuth2UserInfo = null;
if (userRequest.getClientRegistration().getRegistrationId().equals("google")) {
oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes());
} else if (userRequest.getClientRegistration().getRegistrationId().equals("naver")) {
oAuth2UserInfo = new NaverUserInfo((Map)oAuth2User.getAttributes().get("response"));
} else if (userRequest.getClientRegistration().getRegistrationId().equals("kakao")) {
oAuth2UserInfo = new KakaoUserInfo(oAuth2User.getAttributes());
} else {
log.info("์ง์ํ์ง ์๋ ์์
์
๋๋ค.");
}
Optional<User> userEntity = userRepository.findByProviderAndProviderId(oAuth2UserInfo.getProvider(), oAuth2UserInfo.getProviderId());
User user;
if (userEntity.isPresent()) {
user = userEntity.get();
user.setEmail(oAuth2UserInfo.getEmail());
userRepository.save(user);
} else {
user = User.builder()
.username(oAuth2UserInfo.getProvider() + "_" + oAuth2UserInfo.getProviderId())
.email(oAuth2UserInfo.getEmail())
.provider(oAuth2UserInfo.getProvider())
.providerId(oAuth2UserInfo.getProviderId())
.role("ROLE_USER")
.build();
userRepository.save(user);
}
return new PrincipalDetails(user, oAuth2User.getAttributes());
}
}
- 1. super.loadUser(userRequest) ๋ฅผ ํตํด OAuth2.0 ์์ ๋ก๊ทธ์ธ ์ฌ์ฉ์์ ๋ํ ์ ๋ณด๋ฅผ ๋ฐ์์ oAuth2user์ ์ ์ฅํฉ๋๋ค.
- 2-1. ๋ง์ฝ ๋ฐ์์จ ์ ๋ณด์ Registration_ID๊ฐ "google"์ด๋ฉด GoogleUserInfo ์์ฑ
- 2-2. ๋ง์ฝ ๋ฐ์์จ ์ ๋ณด์ Registration_ID๊ฐ "naver"์ด๋ฉด NaverUserInfo ์์ฑ
- 2-3. ๋ง์ฝ ๋ฐ์์จ ์ ๋ณด์ Registration_ID๊ฐ "kakao"๋ฉด KakaoUserInfo ์์ฑ
- 3. ๋ฐ์์จ ์ ๋ณด(2์์ ์์ฑํ Info)๋ฅผ ํ ๋๋ก Provider์ Provider ID๋ก DB์์ ๊ฒ์ํฉ๋๋ค ( Spring Data JPA ์ฟผ๋ฆฌ ์์ฑ )
- ๊ฒ์ -> ๊ฒฐ๊ณผ ์์ -> (2์์ ์์ฑํ Info๋ฅผ ํ ๋๋ก)์ํฐํฐ ์์ฑ ๋ฐ ์ ์ฅ
- ๊ฒ์ -> ๊ฒฐ๊ณผ ์์ -> (2์์ ์์ฑํ Info์ ์ด๋ฉ์ผ๋ง)์ํฐํฐ ์ ๋ฐ์ดํธ
- 4. PrincipalDetails์ OAuth2 ์ฌ์ฉ์ ์ ๋ณด ๋ฐ attribute๋ฅผ ๋ด์ ๋ฆฌํดํฉ๋๋ค.
<์ ๋ฆฌ>
- ๊ตฌ๊ธ ์์ ๋ก๊ทธ์ธ์ ์ด์ด ๋ค์ด๋ฒ, ์นด์นด์ค๊น์ง ์์ ๋ก๊ทธ์ธ๋ถํฐ ํ์ฒ๋ฆฌ๊น์ง ํ์ฅํด๋ณด์์ต๋๋ค.
- OAuth ๋ก๊ทธ์ธ ํ์ฒ๋ฆฌ ์๋น์ค๋ฅผ ํ ๊ณณ์์ ์ฒ๋ฆฌํ๋ ๊ณผ์ ์ค, ๊ฐ ์์ ๋ง๋ค ๋ฆฌํดํ๋ attribute-name์ด ๋ฌ๋ผ ์ธํฐํ์ด์ค๋ก ๊ณตํตํ ํ ํ, ํ๋ซํผ์ด ๋์ ธ์ฃผ๋ ํ์์ ๋ง์ถฐ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ์์ต๋๋ค.
- ๊ตฌ๊ธ, ๋ค์ด๋ฒ, ์นด์นด์ค ๋ชจ๋ ๋ก๊ทธ์ธ ์ฑ๊ณต ์ code๋ฅผ ๋ฆฌ๋ค์ด๋ ์ URI์ ์ ๋ฌํด์ฃผ๋ authorization_code ๋ฐฉ์์ ์ฑํํ์ฌ ์ฌ์ฉํฉ๋๋ค.
- ์์ ๋ก๊ทธ์ธ ํ์ฅ์ ๋ฐ๋ผ PrincipalUserDetailsService๋ฅผ ๋ฆฌํํ ๋ง ํ์์ต๋๋ค.
<์ฐธ๊ณ ์๋ฃ>
'๐ Backend > Spring Security' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Security] JWT(Json Web Token) ์ธ์ฆ ๋ฐฉ์ (0) | 2023.06.02 |
---|---|
[Security] ์ฟ ํค vs ์ธ์ vs ํ ํฐ (0) | 2023.06.02 |
[Security] OAuth2.0 ๋ก๊ทธ์ธ ํ์ฒ๋ฆฌ - ๊ถํ ๋ถ์ฌ, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ์ฅ (0) | 2023.05.31 |
[Security] OAuth2.0์ ์ด์ฉํ ์์ ๋ก๊ทธ์ธ (๊ตฌ๊ธ) (0) | 2023.05.31 |
[Security] ๊ถํ ์ฒ๋ฆฌ @PreAuthorize, @PostAuthorize, @Secured (0) | 2023.05.29 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
Study Repository
rlaehddnd0422