springSecurityOAuth2
์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ์ฌ์ฉํ ์นด์นด์ค ๋ค์ด๋ฒ ๋ก๊ทธ์ธ
์คํ๋ง๋ถํธ๊ฐ ์๋ ์คํ๋ง์์ ์คํ๋ง ์ํ๋ฆฌํฐ์ OAuth2๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ SNS๋ก๊ทธ์ธ์ ๊ตฌํํ๋ฉด์ ์ป๊ฒ ๋ ์ง์์ ์ ์ต๋๋ค. Spring Security๋ก ๊ตฌํํ๋ OAuth2๋ ๋๋ถ๋ถ์ ํ๊ธ ์๋ฃ๊ฐ ์คํ๋ง ๋ถํธ ๊ธฐ๋ฐ์ด์๊ธฐ์, ์คํ๋ง ๊ธฐ๋ฐ์ผ๋ก ๊ตฌํํ๊ธฐ ์ํด์ ํด๋์ค ์์ ๊ตฌ์กฐ์ ์ฌ๋ฌ ๊ฐ์ฒด์ ๋ฉ์๋๋ฅผ ๋ณด๋ ๊ฒ์ด ์ฝ๋ ์์ฑ์ ๋ง์ ๋์์ด ๋์์ต๋๋ค.
OAuth 2.0์ ์ฃผ์ 3๋จ๊ณ ํ๋ก์ธ์ค
๊ณต์๋ฌธ์ : https://oauth.net/2/
OAuth 2.0๋ OAuth 2.0์ ์ธ์ฆ์ ์ํ ์ ๊ณ ํ์ค ํ๋กํ ์ฝ์ผ๋ก ์ปค์คํ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ค๋ฅธ ๋ฆฌ์์ค ์๋ฒ(ex. ์นด์นด์ค, ๋ค์ด๋ฒ)์ ์ธ์ฆ๋ ์ฌ์ฉ์ ์ ๋ณด(ex. ์ด๋ฉ์ผ, ์๋ ์์ผ ๋ฑ์ ์ ํ์ ์ ๋ณด)๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ค. ๊ฐ์ ธ์จ ์ ๋ณด๋ก ์ธ์ฆ, ์ธ๊ฐ ๋ฑ์ ๋ก์ง์ ๊ตฌํํ๋ ๊ฒ์ ์ฌ์ฉ์์ ๋ชซ์ด๋ค. ๋ณดํต ์ธ๊ฐ ์ฝ๋ ๋ฐ๊ธ, ํ ํฐ ๋ฐ๊ธ, ์ฌ์ฉ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ์ 3๋จ๊ณ์ ํ๋ก์ธ์ค๋ก ๋๋ ์ ์๋ค. ์๋๋ ์ค๋ช ์ด ๋๋ฌด ๊น๋ํ๊ฒ ๋์ด ์์ด์ ์นด์นด์ค ๋ก๊ทธ์ธ ๊ณต์๋ฌธ์์์ ๊ฐ์ ธ์ ๋ดค๋ค.

#1. ์ธ๊ฐ ์ฝ๋ ๋ฐ๊ธ : ์ฌ์ฉ์๊ฐ ๋ฆฌ์์ค ์๋ฒ์ ์ธ์ฆ๋ ์ฌ์ฉ์์ธ์ง ๊ฒ์ฆํ๋ ๊ณผ์ ์ด๋ค. ์๋ฅผ ๋ค์ด ์นด์นด์ค๋ฅผ ๋ฆฌ์์ค ์๋ฒ๋ก ์ฌ์ฉํ ๊ฒ์ด๋ผ๋ฉด ์ฌ์ฉ์๊ฐ ์นด์นด์ค์ ์ธ์ฆ๋ ์ฌ์ฉ์์์ ํ์ธํด์ผ ํ๋ค. ์ ๊ทธ๋ฆผ์์๋ ๋ณผ ์ ์๋ฏ์ด ์ด๋ ๋ณดํต ์๋ฐ์คํฌ๋ฆฝํธ๋ก ํด๋ผ์ด์ธํธ ์ธก์์ ๋ฆฌ์์ค ์๋ฒ๋ก ์์ฒญ์ ๋ณด๋ด๋ ํํ๋ก ์ด๋ฃจ์ด์ง๋ค. ๋ฆฌ์์ค ์๋ฒ ์ธก์์๋ ํด๋ผ์ด์ธํธ ์ธก์ ์์ฒญ์ ๋ฐ์ผ๋ฉด ๋ก๊ทธ์ธ ํ๋ฉด์ ๋ด๋ ค์ฃผ๊ณ , ๋์ ํญ๋ชฉ๊น์ง ์๋ต๋ฐ์ผ๋ฉด ์ด ์ ๋ณด๋ฅผ ์ฌ์ฉํ ์๋ฒ๋ก ์ธ๊ฐ ์ฝ๋์ ํจ๊ป redirectํ๊ฒ ๋๋ค. spring security์์๋ "{baseUrl}/{action}/oauth2/code/{registrationId}"
์ด default์ค์ ์ redirect url์ด๋ค. ์๋ฅผ ๋ค์ด, example.com์์ ์ฌ์ฉํ ๊ฒ์ด๊ณ , ๋ก๊ทธ์ธ ์ก์
์ ์ฌ์ฉํ ๊ฒ์ด๋ฉฐ, ๋ฆฌ์์ ์๋ฒ๋ก๋ ์นด์นด์ค๋ฅผ ์ฌ์ฉํ ๊ฒ์ด๋ผ๋ฉด "https://example.com/login/oauth2/code/kakao"
๊ฐ redirect url์ด ๋๊ฒ ๋ค. ๋น์ฐํ ์ด redirect url์ ๋ฐ๊ฟ ์ ์๋ค. ๋ค๋ง ์คํ๋ง์์ ๋ฐ๊พธ์๋ค๋ฉด, ๋ฆฌ์์ค ์๋ฒ์๋ ๊ทธ์ ํด๋นํ๋ redirect url์ค์ ์ ํ์์ ์ผ๋ก ํด์ผ ํ๋ค.
#2. ํ ํฐ ๋ฐ๊ธ : ์ธ๊ฐ ์ฝ๋๋ฅผ ๋ฐ์๋ค๋ฉด, ์ธ๊ฐ ์ฝ๋๋ฅผ ์์ฒญ์ ๋ด์ ํ ํฐ ๋ฐ๊ธ url๋ก ๋ณด๋ธ๋ค. ๊ทธ ํ ๋ฆฌ์์ค ์๋ฒ๋ ๋ฐ์ ์์ฒญ์ด ์ ํจํ ํด๋ผ์ด์ธํธ๊ฐ ๋ณด๋๋์ง ํ์ธํ๊ณ ํ ํฐ์ ๋ฐ๊ธํด์ค๋ค. ์ด ํ ํฐ์ ์ฌ์ฉํ๋ ์ ์ฅ์์๋ ์ด ํ ํฐ์ด ๋ฌด์์ ์๋ฏธํ๋์ง ๋ชฐ๋ผ๋ ๋๋ค. ์ฌ์ฉ์๋ ๊ทธ์ ์ด ํ ํฐ์ด ๋ง์น ํธํ ํค์ฒ๋ผ ์ ํจํ๊ฒ ๋์ํ ๊ฒ์ด๋ผ๋ ์ ๋ง ์๋ฉด ๋๋ค. ๋ฌผ๋ก ๋น์ฐํ ๋ฆฌ์์ค ์๋ฒ์์๋ ์ด ํ ํฐ์ ํด์ํ์ฌ ์ด๋ค ์ ์ ์ ์ ๋ณด์ ์ ๊ทผํ ์ ์๋์ง ํ๋จํด์ผ ํ๋ค. ํธํ ๋ฝ๋์ด๊ฐ ํธํ ํค๋ฅผ ํด์ํ์ฌ ๋ฐฉ๋ฒํธ์ ํธํ ํค๊ฐ ์์ํ๋์ง ํ์ธํ๋ ๊ฒ์ฒ๋ผ ๋ง์ด๋ค.
access token๋ฅผ ํธํ ํค์นด๋์ ๋น๋์ด ๊ฐ๋จํ ์ค๋ช ํ ์ ํ๋ธ : https://www.youtube.com/watch?v=BNEoKexlmA4&ab_channel=OktaDev
#3. ์ฌ์ฉ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ : ํ ํฐ์ ๋ฐ์๋ค๋ฉด, ํ ํฐ์ Authorizationํค๋์ ๋ด์ ์ฌ์ฉ์ ์ ๋ณด ์กฐํ url๋ก ์์ฒญํ๋ค.
spring(without spring boot) + spring security ์กฐํฉ์ ์นด์นด์ค ๋ก๊ทธ์ธ ๊ตฌํ
OAuth 2.0์ REST API๋ฅผ ์ฌ์ฉํ๋ ๋ชจ๋ ๋ฐฑ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๊ตฌํ์ด ๊ฐ๋ฅํ๋ค. ์ด ๋ง์ธ ์ฆ์จ ์คํ๋ง ํ๋ ์์ํฌ๊ฐ ์์ด๋ ๊ตฌํ์ด ๊ฐ๋ฅํ๋ค๋ ๋ง์ด๋ค. ์ด ๋ง์ ํ๋ ์ด์ ๋ ์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ์ฌ์ฉํ๊ณ ์์ง๋ง, ํ์ฌ ์ฌ์ฉํ๊ณ ์๋ ์คํ๋ง์ ๋ฒ์ ์ด ๋๋ฌด ๋ฎ์ ์คํ๋ง ์ํ๋ฆฌํฐ์ OAuth2๋ฅผ ์ง์ํ์ง ์๋๋ค๋ฉด, ํจ์ดํ๊ฒ REST API๋ฅผ ์ฌ์ฉํด์ OAuth2๋ฅผ ๊ตฌํํ ํ ์ด๋ฅผ ์คํ๋ง ์ํ๋ฆฌํฐ์ ํตํฉํ๋ ๋ก์ง์ ๊ตฌํ ํด์ผ ํ๊ธฐ ๋๋ฌธ์ด๋ค. (๋ฉ์ด๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ ๊ธฐ์ค์ผ๋ก ์คํ๋ง ์ํ๋ฆฌํฐ 5.x์ด์ ๋ฒ์ ๋ถํฐ oauth2 client๋ฅผ ์ง์ํ๋ค.) ํ์ง๋ง ์คํ๋ง์์๋ ์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ์ฌ์ฉํด์ ๋ณด์ ๊ด๋ จ ๋ก์ง์ ๊ตฌํํ๋ ๊ฒ์ ๊ถ์ฅํ๊ณ , ์ธ์ฆ, ์ธ๊ฐ, ์ํ๋ฆฌํฐ ํํฐ ๊ฐ์ ๊ธฐ๋ฅ๋ค์ ๊ฐํธํ๊ฒ ์ฌ์ฉํ ์ ์์ผ๋, ๋๋๋ก์ด๋ฉด ๋ฒ์ ์ ์ ํด์ ์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ์ฌ์ฉํด์ OAuth2๋ฅผ ๊ตฌํํ๋ ๊ฒ์ด ์ณ๊ฒ ๋ค.
#1. ๋ฆฌ์์ค ์๋ฒ(ex. ์นด์นด์ค)์ ์ฌ์ฉ์(์ ํ๋ฆฌ์ผ์ด์ ) ๋ฑ๋กํ๊ธฐ
OAuth2.0์ ์ฌ์ฉํ๊ธฐ ์ํด์ ์ฌ์ฉํ๊ณ ์ถ์ ์๋น์ค์ ๋ก๊ทธ์ธํด์ ์ด๋ค ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ด ์๋น์ค์ ๋ฑ๋ก๋ ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๊ณ ์ถ๋ค๊ณ ์๋ ค์ค์ผ ํ๋ค. ์๋ฅผ ๋ค์ด ์นด์นด์ค๋ฅผ ๋ฆฌ์์ค ์๋ฒ๋ก ์ฌ์ฉํ ์ ์๋ค. ์ฌ์ฉ๋ฒ์ ์นด์นด์ค ๊ณต์ ๋ฌธ์์ ์์ธํ ๋์ ์๋ค.
์นด์นด์ค ๋ก๊ทธ์ธ ๊ณต์๋ฌธ์ : https://developers.kakao.com/docs/latest/ko/kakaologin/common
#2. ์ฌ์ฉ์(์ ํ๋ฆฌ์ผ์ด์ )์ ๋ฑ๋ก์ ๋ณด ์ค์ ํ๊ธฐ
clientregistration์ ์งํํ๋๋ฐ ๊ตฌ๊ธ, ํ์ด์ค๋ถ, ๊นํ๋ธ๋ฅผ ๋ฆฌ์์ค ์๋ฒ๋ก ์ฌ์ฉํ ๊ฒฝ์ฐ CommonOAuth2Provider
์ ์ฌ์ฉํ๋ฉด ๊ฐํธํ๊ฒ ์ค์ ํ ์ ์๋ค. ํ์ง๋ง ์นด์นด์ค, ๋ค์ด๋ฒ๊ฐ์ ๊ฒฝ์ฐ ์ง์ OAuth2Provider๋ฅผ ๋ง๋ค์ด์ค์ผ ํ๋ค. ์๋ ์ฝ๋๋ฅผ ์ฐธ๊ณ ํด์ ์ค์ ํ์.
/** @Configuration **/
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
// ๋ค๋ฅธ client registration์ด ํ์ํ ๊ฒฝ์ฐ ์๋ ์์ฑ์์ ๋งค๊ฐ๋ณ์์ ์นด์นด์ค๋ฅผ ๋ฑ๋กํ ๊ฒ์ฒ๋ผ ์ถ๊ฐํด์ฃผ๋ฉด ๋๋ค.
return new InMemoryClientRegistrationRepository(this.kakaoClientRegistration());
}
@Bean
public OAuth2AuthorizedClientService authorizedClientService(
ClientRegistrationRepository clientRegistrationRepository) {
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
}
@Bean
public OAuth2AuthorizedClientRepository authorizedClientRepository(
OAuth2AuthorizedClientService authorizedClientService) {
return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService);
}
private ClientRegistration kakaoClientRegistration() {
/** ๋ฆฌ์์ค ์๋ฒ๋ก ์นด์นด์ค๋ฅผ ์ฌ์ฉํ๋ค.**/
Builder builder = ClientRegistration.withRegistrationId("kakao");
/** grant_type์ผ๋ก๋ ์ธ๊ฐ ์ฝ๋ ๋ฐ๊ธฐ, ํ ํฐ ๊ฐฑ์ ๋ฑ์ด ์๋๋ฐ ํ์ฌ๋ ์ธ๊ฐ ์ฝ๋๋ฅผ ๋ฐ์ ๋ ์ฌ์ฉํ ๊ฒ์ด๋ฏ๋ก ์๋์ฒ๋ผ ์ฌ์ฉํ๋ค.**/
builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
/** ๋ฆฌ์์ค ์๋ฒ๋ก๋ถํฐ ๋ฐ์ ์ ๋ณด ๋ฒ์์ด๊ณ , ๋ฆฌ์์ค ์๋ฒ์ ์ฑ์ ๋ฑ๋กํ ๋ ์ ์๋ ์ ๋ณด
๋ฒ์์ ์ผ์นํด์ผ ํ๋ค.**/
builder.scope(new String[]{"profile_nickname", "account_email", "birthday"});
/** ํด๋ผ์ด์ธํธ ์ธ์ฆ ๋ฉ์๋๋ CLIENT_SECRET_POST๋ฅผ ์ฌ์ฉํ ๊ฒ์ด๋ค. ๊ผญ ์ ์ด์ผ ํ๋ค. **/
builder.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST);
/** ์๋ 4๊ฐ์ URI๋ฅผ ์ ์ผ๋ฉด ์๋์ผ๋ก ์ธ๊ฐ ์ฝ๋ ์์ฒญ, ๋ฆฌ๋ค์ด๋ ํธ url ์ค์ , ํ ํฐ ์์ฒญ,
์ ์ ์ ๋ณด ์กฐํ ์์ฒญ์ ๋ค ๊ตฌํํด์ค๋ค. **/
builder.authorizationUri("https://kauth.kakao.com/oauth/authorize");
builder.redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}");
builder.tokenUri("https://kauth.kakao.com/oauth/token");
builder.userInfoUri("https://kapi.kakao.com/v2/user/me");
/** UserInfo Response์ ์๋ id๋ฅผ userNameAttributeName์ผ๋ก ์ฌ์ฉํ๊ฒ ๋ค๋ ์๋ฏธ์ด๋ค.
์ฌ์ฉ์ ๊ตฌ๋ณ ์ฉ๋๋ก ์ฌ์ฉํ๋ค. ์กด์ฌํ์ง ์๋ ๊ฐ์ ์ ์ผ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ๋ค. ๋ฆฌ์์ค ์๋ฒ
๋ณ๋ก userNameAttributeName์ด ๋ค๋ฅผ ์ ์์ผ๋ ์ฒดํฌํด์ผ ํ๋ค. **/
builder.userNameAttributeName("id");
builder.clientName("Kakao");
builder.clientId("์ฑ ๋ฑ๋กํ ๋ ๋ฐ์ clientId");
builder.clientSecret("์ฑ ๋ฑ๋กํ ๋ ๋ฐ์ clientSecret");
return builder.build();
}
spring security without boot ๊ณต์ ๋ฌธ์ ์ฐธ๊ณ ์ฝ๋ : https://docs.spring.io/spring-security/reference/servlet/oauth2/login/core.html#oauth2login-javaconfig-wo-boot
#3. OAuth2 ๋ก๊ทธ์ธ ๋ฑ๋กํ๊ธฐ
@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CustomOAuth2UserService customOAuth2UserService;
@Override
protected void configure(HttpSecurity http){
http.oauth2Login()
.userInfoEndpoint()
.userService(customOAuth2UserService);
}
}
@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final UserService userService;
// ๋ฆฌ์์ค ์๋ฒ๋ก๋ถํฐ ์ ์ ์ ๋ณด๋ฅผ ๋ฐ์์จ ํ ์คํ๋๋ ๋ฉ์๋
@Override
public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
OAuth2UserService oAuth2UserService = new DefaultOAuth2UserService();
OAuth2User oAuth2User = oAuth2UserService.loadUser(oAuth2UserRequest);
/** ๊ถํ ๋ถ์ฌ **/
Set<GrantedAuthority> authorities = new LinkedHashSet();
/** ์ ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ๋ก์ง, ํ ํฐ ์ ๊ณต์๋ณ๋ก ์๋ต json์ ๊ตฌ์กฐ๊ฐ ๋ค๋ฅด๋ค.**/
Map<String, Object> kakaoAccount = (Map<String, Object>) oAuth2User.getAttributes().get("kakao_account");
String email = (String) kakaoAccount.get("email");
/** ์ด ๋ถ๋ถ์ findUserByEmail๊ฐ์ ๋ฉ์๋๋ก ์ ์ ๋ฅผ ์ฐพ๊ณ ์์ผ๋ฉด update
์์ผ๋ฉด insertํ๋ ๋ก์ง์ ๋ฃ์ผ๋ฉด ๋๋ค.**/
/** ์๋์์ ๋ฆฌํดํ๋ OAuth2User๋ principal์ด ๋๊ธฐ ๋๋ฌธ์ ๊ธฐ์กด ์ํ๋ฆฌํฐ ์ฌ์ฉ ๋ก์ง๊ณผ ํตํฉ
ํ๊ธฐ ์ด๋ ค์ธ ์ ์๋๋ฐ, OAuth2User๋ฅผ ์์ํ๊ณ CustomOAuth2User๋ฅผ ๊ตฌํํ๋ฉด ์ํ๋
๋ก์ง์ ๋ง๋ค ์ ์์ต๋๋ค.**/
return new DefaultOAuth2User(
authorities,
oAuth2User.getAttributes(),
"id");
}
}
Last updated