OAuth2.0์„ ์ด์šฉํ•œ ๋กœ๊ทธ์ธ(์ธ์ฆ)์— ๋Œ€ํ•œ ์ดํ•ด

OAuth๋ž€? 

๋‹ค์–‘ํ•œ ํ”Œ๋žซํผ(Google, Facebook, Naver...)์˜ ํŠน์ • ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด ์ œ 3์ž์˜ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์‚ฌ์šฉ์ž์˜ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์œ„์ž„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ํ‘œ์ค€ ํ”„๋กœํ† ์ฝœ์„ ๋งํ•œ๋‹ค.

OAuth์˜ ํƒ„์ƒ 

OAuth๊ฐ€ ์กด์žฌํ•˜๊ธฐ ์ „์—๋Š” ์ธ์ฆ ๋ฐฉ์‹์˜ ํ‘œ์ค€์ด ์—†์—ˆ๊ธฐ์— ๊ธฐ๋ณธ์ธ์ฆ์ธ ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋Š”๋ฐ ์ด๋Š” ๊ต‰์žฅํžˆ ๋ณด์•ˆ์ƒ ์น˜๋ช…์ ์ธ ๊ตฌ์กฐ์ด๋‹ค. ๋งŒ์•ฝ ์•…์˜์ ์ธ ์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ฆ ๊ณผ์ • ์ค‘์— ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํƒˆ์ทจํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ํ•ด๋‹น ํ”Œ๋žซํผ์— ์กด์žฌํ•˜๋Š” ์‚ฌ์šฉ์ž์˜ ๋ชจ๋“  ์ •๋ณด๋“ค์ด ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ฆ‰, ์‚ฌ์šฉ์ž์˜ ๊ณ„์ • ์ •๋ณด๋งŒ ํƒˆ์ทจํ•˜๋ฉด ํ”Œ๋žซํผ์— ์กด์žฌํ•˜๋Š” ๋ชจ๋“  ์„œ๋น„์Šค์— ๋Œ€ํ•œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค. ๋˜ ๋ณดํ†ต ์‚ฌ์šฉ์ž๋“ค์€ ํ”Œ๋žซํผ๋“ค ๋งˆ๋‹ค ๋‹ค๋ฅธ ๊ณ„์ • ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค๋Š” ๋™์ผ ๊ณ„์ • ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๊ธฐ ๋•Œ๋ฌธ์— ํ”ผํ•ด๊ฐ€ ๋์—†์ด ํผ์งˆ์ˆ˜๋„ ์žˆ๋‹ค. 

 

์ด๋Ÿฌํ•œ ์œ„ํ—˜์„ฑ ๋•Œ๋ฌธ์— ์ฃผ์š” ํ”Œ๋žซํผ๋“ค์€ ๊ฐ์ž ์ง์ ‘ ๊ฐœ๋ฐœํ•œ ์ธ์ฆ ํ”„๋กœํ† ์ฝœ์„ ์ฑ„ํƒํ•˜์˜€๋Š”๋ฐ ๋Œ€ํ‘œ์ ์œผ๋กœ ๊ตฌ๊ธ€์˜ AuthSub, AOL์˜ OpenAuth, ์•ผํ›„์˜ BBAuth, ์•„๋งˆ์กด์˜ ์›น์„œ๋น„์Šค API ๋“ฑ์ด ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ๋˜๋ฉด ์—ฌ๋Ÿฌ ํ”Œ๋žซํผ์— ์กด์žฌํ•˜๋Š” ์„œ๋น„์Šค์˜ ์ ‘๊ทผ ๊ถŒํ•œ์„ ๋ถ€์—ฌ๋ฐ›๊ธฐ ์œ„ํ•ด์„œ๋Š” ์—ฌ๋Ÿฌ ์ข…๋ฅ˜์˜ ์ธ์ฆ ํ”„๋กœํ† ์ฝœ์— ๋Œ€ํ•ด ๋ชจ๋‘ ๋Œ€์‘ํ•ด์•ผ ํ•˜๋Š” ๋ฌธ์ œ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์—ˆ๋‹ค. 

 

์ด๋Ÿฌํ•œ ์ด์œ ๋กœ ํƒ„์ƒํ•œ ๊ฒƒ์ด OAuth์ด๋‹ค. ์—ฌ๋Ÿฌ ์ข…๋ฅ˜๊ฐ€ ์กด์žฌํ–ˆ๋˜ ์ธ์ฆ ํ”„๋กœํ† ์ฝœ๋“ค์„ ํ‘œ์ค€ํ™”ํ•œ ์ธ์ฆํ”„๋กœํ† ์ฝœ์ด๋ฉฐ ์ด ์ธ์ฆ์„ ํ™œ์šฉํ•˜๋Š” ํ”Œ๋žซํผ๋“ค์˜ ์„œ๋น„์Šค๋กœ ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋‹จ OAuth์— ๋Œ€ํ•ด์„œ๋งŒ ๋Œ€์‘์„ ์ง„ํ–‰ํ•˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํด๋ผ์ด์–ธํŠธ ์ž…์žฅ์—์„œ๋Š” ์—ฌ๋Ÿฌ๋ชจ๋กœ ํšจ์œจ์ ์ธ ์ธ์ฆ ํ”„๋กœํ† ์ฝœ์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค. 

OAuth2.0์˜ ์ธ์ฆ FLOW

Resource Server๊ฐ€ Resource Owner๋ฅผ ์ธ์ฆํ•˜๋Š” ํ™”๋ฉด

  1. Resource Server์—์„œ Client๋ฅผ ์‹๋ณ„ํ•˜๊ธฐ ์œ„ํ•ด Client-id, Client-Secret์„ ๋„˜๊ฒจ์ค€๋‹ค. (Google ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” Google Cloud Platform์—์„œ ์ง์ ‘ ๋ฐœ๊ธ‰ ๋ฐ›๋Š”๋‹ค) 
  2. ๋งŒ์•ฝ ํŠน์ • ๊ธฐ๋Šฅ์— ์ธ์ฆ์ด ํ•„์š”ํ•˜๋‹ค๋ฉด Resource Owner๋ฅผ ์ธ์ฆํ•˜๊ธฐ ์œ„ํ•œ ๋กœ๊ทธ์ธ ๋งํฌ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋œ๋‹ค.  
  3. Resource Owner๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋กœ๊ทธ์ธ์„ ํ–ˆ๋‹ค๋ฉด, Resource Server๋Š” ์š”์ฒญ URL๊ณผ ์ด๋ฏธ Client๊ฐ€ ๋“ฑ๋กํ–ˆ๋˜ redirect_uri์™€ ๋™์ผํ•œ์ง€ ํ™•์ธํ•œ๋‹ค.
  4. ์ผ์น˜ํ•˜๋Š” ๊ฒฝ์šฐ Resource Server๋Š” Resource Owner์—๊ฒŒ Client๊ฐ€ ์š”๊ตฌํ•˜๋Š” ์ •๋ณด ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณด์—ฌ์ฃผ๋ฉฐ ์Šน์ธ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•œ๋‹ค. 
  5. Resource Owner๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๋กœ๊ทธ์ธ ํ•˜๊ฒŒ ๋˜๋ฉด Resource Server๊ฐ€ Client์—๊ฒŒ Code(์ž„์‹œ PW)๋ฅผ redirect_uri๋กœ ์‘๋‹ตํ•œ๋‹ค.
  6. Client๋Š” code๊ฐ’๊ณผ client-id, client-secret ์ •๋ณด๋ฅผ ๋‹ด์•„์„œ Resource Server์—๊ฒŒ ์ „๋‹ฌํ•œ๋‹ค. 
  7. Resource Server๋Š” Client์—๊ฒŒ ๋ฐœ๊ธ‰ํ•œ code๊ฐ’๊ณผ client-id, client-secret๋ฅผ ์ด์šฉํ•ด์„œ ๊ทธ Client๊ฐ€ ๋งž๋Š”์ง€ ๊ฒ€์ฆํ•œ๋‹ค.
  8. ๋งŒ์•ฝ ๋งž๋‹ค๋ฉด Access token๊ณผ Refresh token์˜ ์ •๋ณด๋ฅผ Client์—๊ฒŒ redirect_uri๋กœ ์‘๋‹ตํ•œ๋‹ค.
  9. Client๋Š” Access token ์ •๋ณด๋ฅผ ๊ฐ์ž์˜ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ณด๊ด€ํ•˜๋ฉฐ ์ด token์„ ์ด์šฉํ•˜์—ฌ Resource Server์—๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•œ๋‹ค.
  10. 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

https://velog.io/@piecemaker/OAuth2-%EC%9D%B8%EC%A6%9D-%EB%B0%A9%EC%8B%9D%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90

 

OAuth2 ์ธ์ฆ ๋ฐฉ์‹์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž.

OAuth2 ์ธ์ฆ ๋ฐฉ์‹์„ Google ์„œ๋น„์Šค๋ฅผ ์˜ˆ์ œ๋กœ ์•Œ์•„๋ณด์ž.

velog.io