OAuth๋?
๋ค์ํ ํ๋ซํผ(Google, Facebook, Naver...)์ ํน์ ์ฌ์ฉ์ ๋ฐ์ดํฐ์ ์ ๊ทผํ๊ธฐ ์ํด ์ 3์์ ํด๋ผ์ด์ธํธ๊ฐ ์ฌ์ฉ์์ ์ ๊ทผ ๊ถํ์ ์์ ๋ฐ์ ์ ์๋ ํ์ค ํ๋กํ ์ฝ์ ๋งํ๋ค.
OAuth์ ํ์
OAuth๊ฐ ์กด์ฌํ๊ธฐ ์ ์๋ ์ธ์ฆ ๋ฐฉ์์ ํ์ค์ด ์์๊ธฐ์ ๊ธฐ๋ณธ์ธ์ฆ์ธ ์์ด๋์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ฌ์ฉํ์๋๋ฐ ์ด๋ ๊ต์ฅํ ๋ณด์์ ์น๋ช ์ ์ธ ๊ตฌ์กฐ์ด๋ค. ๋ง์ฝ ์ ์์ ์ธ ์ฌ์ฉ์๊ฐ ์ธ์ฆ ๊ณผ์ ์ค์ ์์ด๋์ ๋น๋ฐ๋ฒํธ๋ฅผ ํ์ทจํ๊ฒ ๋๋ค๋ฉด ํด๋น ํ๋ซํผ์ ์กด์ฌํ๋ ์ฌ์ฉ์์ ๋ชจ๋ ์ ๋ณด๋ค์ด ๊ฐ์ ธ๊ฐ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค. ์ฆ, ์ฌ์ฉ์์ ๊ณ์ ์ ๋ณด๋ง ํ์ทจํ๋ฉด ํ๋ซํผ์ ์กด์ฌํ๋ ๋ชจ๋ ์๋น์ค์ ๋ํ ์ ๊ทผ ๊ถํ์ ์ป์ ์ ์๋ค๋ ์๋ฏธ์ด๋ค. ๋ ๋ณดํต ์ฌ์ฉ์๋ค์ ํ๋ซํผ๋ค ๋ง๋ค ๋ค๋ฅธ ๊ณ์ ์ ๋ณด๋ฅผ ์ฌ์ฉํ๋ ๊ฒ๋ณด๋ค๋ ๋์ผ ๊ณ์ ์ ๋ณด๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๊ธฐ ๋๋ฌธ์ ํผํด๊ฐ ๋์์ด ํผ์ง์๋ ์๋ค.
์ด๋ฌํ ์ํ์ฑ ๋๋ฌธ์ ์ฃผ์ ํ๋ซํผ๋ค์ ๊ฐ์ ์ง์ ๊ฐ๋ฐํ ์ธ์ฆ ํ๋กํ ์ฝ์ ์ฑํํ์๋๋ฐ ๋ํ์ ์ผ๋ก ๊ตฌ๊ธ์ AuthSub, AOL์ OpenAuth, ์ผํ์ BBAuth, ์๋ง์กด์ ์น์๋น์ค API ๋ฑ์ด ์๋ค. ํ์ง๋ง ์ด๋ ๊ฒ ๋๋ฉด ์ฌ๋ฌ ํ๋ซํผ์ ์กด์ฌํ๋ ์๋น์ค์ ์ ๊ทผ ๊ถํ์ ๋ถ์ฌ๋ฐ๊ธฐ ์ํด์๋ ์ฌ๋ฌ ์ข ๋ฅ์ ์ธ์ฆ ํ๋กํ ์ฝ์ ๋ํด ๋ชจ๋ ๋์ํด์ผ ํ๋ ๋ฌธ์ ์ ์ ๊ฐ์ง๊ณ ์์๋ค.
์ด๋ฌํ ์ด์ ๋ก ํ์ํ ๊ฒ์ด OAuth์ด๋ค. ์ฌ๋ฌ ์ข ๋ฅ๊ฐ ์กด์ฌํ๋ ์ธ์ฆ ํ๋กํ ์ฝ๋ค์ ํ์คํํ ์ธ์ฆํ๋กํ ์ฝ์ด๋ฉฐ ์ด ์ธ์ฆ์ ํ์ฉํ๋ ํ๋ซํผ๋ค์ ์๋น์ค๋ก ์ ๊ทผํ๊ธฐ ์ํด์๋ ๋จ OAuth์ ๋ํด์๋ง ๋์์ ์งํํ๋ฉด ๋๊ธฐ ๋๋ฌธ์ ํด๋ผ์ด์ธํธ ์ ์ฅ์์๋ ์ฌ๋ฌ๋ชจ๋ก ํจ์จ์ ์ธ ์ธ์ฆ ํ๋กํ ์ฝ์ด๋ผ๊ณ ํ ์ ์๋ค.
OAuth2.0์ ์ธ์ฆ FLOW
- Resource Server์์ Client๋ฅผ ์๋ณํ๊ธฐ ์ํด Client-id, Client-Secret์ ๋๊ฒจ์ค๋ค. (Google ๊ฐ์ ๊ฒฝ์ฐ๋ Google Cloud Platform์์ ์ง์ ๋ฐ๊ธ ๋ฐ๋๋ค)
- ๋ง์ฝ ํน์ ๊ธฐ๋ฅ์ ์ธ์ฆ์ด ํ์ํ๋ค๋ฉด Resource Owner๋ฅผ ์ธ์ฆํ๊ธฐ ์ํ ๋ก๊ทธ์ธ ๋งํฌ๋ก ๋ฆฌ๋ค์ด๋ ํธ ๋๋ค.
- Resource Owner๊ฐ ์ ์์ ์ผ๋ก ๋ก๊ทธ์ธ์ ํ๋ค๋ฉด, Resource Server๋ ์์ฒญ URL๊ณผ ์ด๋ฏธ Client๊ฐ ๋ฑ๋กํ๋ redirect_uri์ ๋์ผํ์ง ํ์ธํ๋ค.
- ์ผ์นํ๋ ๊ฒฝ์ฐ Resource Server๋ Resource Owner์๊ฒ Client๊ฐ ์๊ตฌํ๋ ์ ๋ณด ๋ฆฌ์คํธ๋ฅผ ๋ณด์ฌ์ฃผ๋ฉฐ ์น์ธ ์ฌ๋ถ๋ฅผ ํ์ธํ๋ค.
- Resource Owner๊ฐ ์ฑ๊ณต์ ์ผ๋ก ๋ก๊ทธ์ธ ํ๊ฒ ๋๋ฉด Resource Server๊ฐ Client์๊ฒ Code(์์ PW)๋ฅผ redirect_uri๋ก ์๋ตํ๋ค.
- Client๋ code๊ฐ๊ณผ client-id, client-secret ์ ๋ณด๋ฅผ ๋ด์์ Resource Server์๊ฒ ์ ๋ฌํ๋ค.
- Resource Server๋ Client์๊ฒ ๋ฐ๊ธํ code๊ฐ๊ณผ client-id, client-secret๋ฅผ ์ด์ฉํด์ ๊ทธ Client๊ฐ ๋ง๋์ง ๊ฒ์ฆํ๋ค.
- ๋ง์ฝ ๋ง๋ค๋ฉด Access token๊ณผ Refresh token์ ์ ๋ณด๋ฅผ Client์๊ฒ redirect_uri๋ก ์๋ตํ๋ค.
- Client๋ Access token ์ ๋ณด๋ฅผ ๊ฐ์์ ๋ฐฉ๋ฒ์ผ๋ก ๋ณด๊ดํ๋ฉฐ ์ด token์ ์ด์ฉํ์ฌ Resource Server์๊ฒ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๋ค.
- Resource Server๋ token์ ๋ณด๊ณ ์์ ์ด ๋ฐ๊ธํ๋ token ๊ฐ์ด๋ผ๋ฉด ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ค.
์ฐธ๊ณ ) OAuth๋ ๋จ์ํ '์์ฒด์ ์ธ ์ธ์ฆ์ ํตํด Resource Owner์ ๋ฐ์ดํฐ๋ฅผ ๋๊ฒจ์ฃผ๋ ๋ถ๋ถ'๋ง ๋ค๋ฃจ๋ฉฐ, Client ์๋ฒ์์์ ์ธ์ฆ๊ณผ ์ธ๊ฐ๋ Client์์ ์ง์ ๊ตฌํํด์ผ ํ๋ค.
*code : Resource Server์์ ๋ฆฌ๋ค์ด๋ ์ URL๋ก ๋ฐ๋ก Resource Owner์ ์ ๋ณด๋ฅผ ์ ๋ฌํ๋ ๊ฒ์ ๋ณด์์ ์ทจ์ฝํ๋ฏ๋ก ํ๋์ ๋จ๊ณ๋ฅผ ๋ ์ถ๊ฐํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ ๋งค๊ฐ๋ณ์์ด๋ค. Client๋ code, client-id, client-secret, redirect_uri ๋ฅผ ํฌํจํ์ฌ ๋ค์ Resource Server์๊ฒ ์์ฒญํ๋ค.
*scope : ๊ถํ์ ๋ฒ์ (์๋ฅผ ๋ค์ด calender์ list๋ฅผ ๊ฐ์ ธ์ค๊ธฐ)
*Refresh token : ์ฒ์์ผ๋ก Access token์ ๋ฐ๊ธ๋ฐ์ผ๋ฉด Refresh token์ ๋ฑ ํ๋ฒ ๋ฐํํ๋ค. Refresh token์ access token์ ์ฌ ๋ฐ๊ธ ๋ฐ์ ๋ ์ฌ์ฉํ๋ค.
๋ณดํต์ ์์ ๊ณผ์ ์ ์ฝ๊ฒ ๊ตฌํํ ์ ์๋ sdk๋ฅผ ์ ๊ณตํ๋ค.
๋๋ง์ ์ท์ฅ ํ๋ก์ ํธ์์์ OAuth2.0
๋๋ง์ ์ท์ฅ ํ๋ก์ ํธ๋ OAuth2.0 Google login์ ํตํด ์ ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ Spring Security๋ฅผ ํตํด ์ธ์ฆ ์ฒ๋ฆฌ๋ฅผ ํ๊ณ ์๋ค. ๊ทธ๋ฐ๋ฐ ํ์คํ ์ดํด๋ฅผ ํ์ง ์๊ณ ๋์ด๊ฐ๋ ค๋ ๋ต๋ตํ ๊ฐ์ด ์์ง ์์ ์์ด ์์ค๋ฅผ ์ด์ง ํํค์ณ ๋ณด์๋ค.
public final class ClientRegistration implements Serializable {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private String registrationId;
private String clientId;
private String clientSecret;
private ClientAuthenticationMethod clientAuthenticationMethod = ClientAuthenticationMethod.BASIC;
private AuthorizationGrantType authorizationGrantType;
private String redirectUriTemplate;
private Set<String> scopes = Collections.emptySet();
private ProviderDetails providerDetails = new ProviderDetails();
private String clientName;
Resource Server์ ํต์ ํ๊ธฐ ์ํ Client์ ๊ด๋ จ๋ ํ๋๋ฅผ ๊ฐ์ง๊ณ ์๋ค. (client-id, client-secret, refistrationId)
public class OAuth2UserRequest {
private final ClientRegistration clientRegistration;
private final OAuth2AccessToken accessToken;
private final Map<String, Object> additionalParameters;
public OAuth2UserRequest(ClientRegistration clientRegistration, OAuth2AccessToken accessToken) {
this(clientRegistration, accessToken, Collections.emptyMap());
}
public OAuth2UserRequest(ClientRegistration clientRegistration, OAuth2AccessToken accessToken,
Map<String, Object> additionalParameters) {
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
Assert.notNull(accessToken, "accessToken cannot be null");
this.clientRegistration = clientRegistration;
this.accessToken = accessToken;
this.additionalParameters = Collections.unmodifiableMap(
CollectionUtils.isEmpty(additionalParameters) ?
Collections.emptyMap() : new LinkedHashMap<>(additionalParameters));
}
public ClientRegistration getClientRegistration() {
return this.clientRegistration;
}
public OAuth2AccessToken getAccessToken() {
return this.accessToken;
}
public Map<String, Object> getAdditionalParameters() {
return this.additionalParameters;
}
}
Resource Server์๊ฒ ์์ฒญํ๊ธฐ ์ํ ๋ชจ๋ ํ๋๋ฅผ ๊ฐ์ง๊ณ ์๋ค. (access-token, client-id, client-secret)
log.info(userRequest.getAccessToken().getTokenValue());
token์ ์ถ๋ ฅํด๋ณด๋ฉด ๋์ผํ ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธํ ๋๋ง๋ค ์๋ก์ด Access-token์ผ๋ก ๊ฐฑ์ ํ์ฌ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์จ๋ค. Access Token์ด ๋ง๋ฃ๋ ๋๋ง๋ค Refresh Token์ ํตํด Access Token์ ์ฌ๋ฐ๊ธ ๋ฐ๋ ๊ฒ์ด ๋ ํจ์จ์ ์ด์ง ์์๊น? ์ด ๋ถ๋ถ์ ์ด๋ป๊ฒ ๋์ํ๋์ง ์ข ๋ ์์๋ด์ผ๊ฒ ๋ค.
public class CustomOAuth2MemberService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final MemberRepository memberRepository;
private final HttpSession httpSession;
/**
* Google ๋ก๊ทธ์ธ์ ํตํด ์ ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์จ๋ค.
* @param userRequest
* @return
* @throws OAuth2AuthenticationException
*/
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest);
String registrationId = userRequest.getClientRegistration().getRegistrationId(); // ๋ก๊ทธ์ธ ์๋น์ค ์ฝ๋ ๊ตฌ๋ถ (ํ์ ๋ค์ด๋ฒ, ์นด์นด์ค ๋ก๊ทธ์ธ ์๋น์ค ์ถ๊ฐ ๋๋น)
String userNameAttributeName = userRequest
.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getUserNameAttributeName(); // OAuth2 ๋ก๊ทธ์ธ ์งํ ์ ํค๊ฐ ๋๋ ํ๋ ๊ฐ, ๋ค์ด๋ฒ ๋ก๊ทธ์ธ/ ๊ตฌ๊ธ ๋ก๊ทธ์ธ ๋์ ์ง์ ์ ์ฌ์ฉ๋จ
OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName, oAuth2User.getAttributes()); //OAuth2User์ attribute๋ฅผ ๋ด๋ DTO
Member member = saveOrUpdate(attributes);
httpSession.setAttribute("user", new SessionMember(member)); // ์ธ์
DTO
return new DefaultOAuth2User(
Collections.singleton(new SimpleGrantedAuthority(member.getRoleKey())),
attributes.getAttributes(),
attributes.getNameAttributeKey());
}
/**
* Member ์์์ฑ์ ํ์ฉํ ์ ์ฅ ๋ฐ ์์
* ๊ตฌ๊ธ Picture๋ ์ด๋ฆ์ด ๋ณ๊ฒฝ๋์์ ์๋ ์์ผ๋ ๋ฐ๋ก ๋ฐ๋ก ์ ์ฉํ๋๋ก ํ๋ค.
* orElse : null์ด๋ ๋ง๋ ํญ์ ํธ์ถ
* orElseGet : null์ผ ๋๋ง ํธ์ถ
* @param attributes
* @return Member ๊ฐ์ฒด
*/
private Member saveOrUpdate(OAuthAttributes attributes) {
Member member = memberRepository.findByEmail(attributes.getEmail())
.map(entity -> entity.change(attributes.getName(), attributes.getPicture()))
.orElse(attributes.toEntity());
return memberRepository.save(member);
}
}
Resource Server๋ก ๋ถํฐ ์ฌ์ฉ์ ์ ๋ณด(oAuth2User)๋ฅผ ๋ฐ์์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ ๊ฐ์ฒด attributes๋ฅผ ์์ฑํ ํ์ attributes ๊ฐ์ฒด๋ฅผ ์ด์ฉํ์ฌ ์๋ฒ ๋ด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ต์ ํํ ํ Session ์ ์ฅ์์ ์ ์ฅํ๋ค.
Reference
OAuth2 ์ธ์ฆ ๋ฐฉ์์ ๋ํด ์์๋ณด์.
OAuth2 ์ธ์ฆ ๋ฐฉ์์ Google ์๋น์ค๋ฅผ ์์ ๋ก ์์๋ณด์.
velog.io