๐Ÿ’ป ์ฝ”๋”ฉ ์ผ๊ธฐ : [Spring Security] 'OAuth2 ๋„ค์ด๋ฒ„ ๋กœ๊ทธ์ธ' ํŽธ

ybkยท2024๋…„ 5์›” 8์ผ

spring

๋ชฉ๋ก ๋ณด๊ธฐ
30/55
post-thumbnail

๐Ÿ”” 'OAuth2 ๋„ค์ด๋ฒ„ ๋กœ๊ทธ์ธ'์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์ž!


๐Ÿ’Ÿ OAuth2(Open Authorization)

  • ๋„ค์ด๋ฒ„, ์นด์นด์˜ค, ๊ตฌ๊ธ€ ๋“ฑ ํ”Œ๋žซํผ์„ ์‚ฌ์šฉํ•ด์„œ ๋กœ๊ทธ์ธ์„ ์ฒ˜๋ฆฌํ•  ๋•Œ ๋งŽ์ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ์ž์‹ ์˜ ๊ณ„์ • ์ •๋ณด๋ฅผ ์›นํŽ˜์ด์ง€์™€ ๊ณต์œ ํ•˜์ง€ ์•Š๊ณ  ๋„ค์ด๋ฒ„, ์นด์นด์˜ค, ๊ตฌ๊ธ€ ๋“ฑ๊ณผ ๊ฐ™์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์˜ํ•ด ๋กœ๊ทธ์ธ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ ‘๊ทผ ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ˜์•„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • OAuth2๋Š” ์ธ์ฆ/์ธ๊ฐ€์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ ๋‹ค๋ฅธ ์„œ๋ฒ„์—์„œ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์žฅ์  :

  • ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ์— ๊ฐ€์ž…ํ•  ๋•Œ ๋ฒˆ๊ฑฐ๋กญ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๋ณด์•ˆ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

๋‹จ์ :

  • ๋งŒ์•ฝ ๋ชจ๋“  ์›น์‚ฌ์ดํŠธ๊ฐ€ ๋„ค์ด๋ฒ„๋กœ ํšŒ์›๊ฐ€์ž…์ด ๋˜์–ด ์žˆ๋‹ค๋ฉด ๋„ค์ด๋ฒ„ ๊ณ„์ •์ด ํ•ดํ‚น๋‹นํ•˜๋ฉด ๊ฐ€์ž…๋œ ๋ชจ๋“  ์›น์‚ฌ์ดํŠธ๋„ ํ•ดํ‚น๋  ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋„ค์ด๋ฒ„ ๊ณ„์ •์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฐ”๊พผ๋‹ค๋˜์ง€ ํ•œ๋‹ค๋ฉด ๋ชจ๋“  ์›น์‚ฌ์ดํŠธ๊ฐ€ ์•ˆ์ „ํ•ด์ง‘๋‹ˆ๋‹ค. ์žฅ๋‹จ์ ์ด ์„ž์—ฌ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ’Ÿ ๋„ค์ด๋ฒ„ ๋กœ๊ทธ์ธ ๊ตฌํ˜„ํ•˜๊ธฐ

1 ~ 3 : ์ธ์ฆ ์ฒ˜๋ฆฌ ์™„๋ฃŒ (code)
4 ~ 5 : ๋„ค์ด๋ฒ„ ์ •๋ณด์— ์ ‘๊ทผ ๊ถŒํ™˜ ๋ถ€์—ฌ (Access Token)
์›นํŽ˜์ด์ง€๊ฐ€ ์„ ์žฌ ๋Œ€์‹  ๋„ค์ด๋ฒ„์— ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.

#OAuth2 
spring.security.oauth2.client.registration.naver.client-id=[ํด๋ผ์ด์–ธํŠธID]
spring.security.oauth2.client.registration.naver.client-secret=[ํด๋ผ์ด์–ธํŠธ secret]
spring.security.oauth2.client.registration.naver.scope=email, nickname
spring.security.oauth2.client.registration.naver.client-name=Naver
spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.naver.redirect-uri=http://localhost:8080/login/oauth2/code/naver

# provider
spring.security.oauth2.client.provider.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize
spring.security.oauth2.client.provider.naver.token-uri=https://nid.naver.com/oauth2.0/token
spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me
spring.security.oauth2.client.provider.naver.user-name-attribute=response
  • application.properties ํŒŒ์ผ ๋จผ์ € ์„ค์ •ํ•ด์ค๋‹ˆ๋‹ค. ๋„ค์ด๋ฒ„์—์„œ ๋กœ๊ทธ์ธ api ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋“ฑ๋กํ•œ ๋‹ค์Œ ํด๋ผ์ด์–ธํŠธ ID์™€ ํด๋ผ์ด์–ธํŠธ secret ๋ฐ›์•„์„œ ์„ค์ •ํ•ด์ค๋‹ˆ๋‹ค.
    ๋„ค์ด๋ฒ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋“ฑ๋ก

1. SecurityConfiguration ํด๋ž˜์Šค์—์„œ OAuth2 ์„ค์ •ํ•ด์ค๋‹ˆ๋‹ค.

@Configuration
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfiguration {

    private final Oauth2MemberService oauth2MemberService;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http.csrf(csrf -> csrf.disable())
                .formLogin(login -> login.loginPage("/member/login"))
                .authorizeHttpRequests(authorize -> authorize
                        .anyRequest()
                        .permitAll()
                )
                // OAuth2 ๋กœ๊ทธ์ธ ๊ตฌ์„ฑ์„ ์œ„ํ•œ ์„ค์ • ๊ฐ€์ ธ์˜ด
                .oauth2Login(oauth2 -> oauth2
                        // ์‚ฌ์šฉ์ž ์ •๋ณด ์—”๋“œํฌ์ธํŠธ์— ๋Œ€ํ•œ ์„ค์ • ์ถ”๊ฐ€
                        .userInfoEndpoint(infoEndpoint ->
                                // ์‚ฌ์šฉ์ž ์„œ๋น„์Šค ๊ตฌ์„ฑ(๋กœ๊ทธ์ธ ์„ฑ๊ณต ํ›„ ์‚ฌ์šฉ์ž ์ •๋ณด ์ฒ˜๋ฆฌ)
                                infoEndpoint.userService(oauth2MemberService)));
        return http.build();
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
  • .csrf : CSRF ๋น„ํ™œ์„ฑํ•˜์—ฌ ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.
  • .formLogin : ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ๋”ฐ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • .authorizeHttpRequests : ๋ชจ๋“  ์‚ฌ๋žŒ์—๊ฒŒ HTTP ์š”์ฒญ์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•ฉ๋‹ˆ๋‹ค.
  • .oauth2Login : OAuth2๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋กœ๊ทธ์ธ ์„ฑ๊ณต ํ›„ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

2. OAuth2MemberInfo ์ธํ„ฐํŽ˜์ด์Šค

public interface OAuth2MemberInfo {
    String getEmail();
    String getProvider();
    String getProviderId();
    String getNickName();
}
  • ๋„ค์ด๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋„ค์ด๋ฒ„์—์„œ ์ œ๊ณตํ•˜๋Š” ์‚ฌ์šฉ์ž ์ •๋ณด์˜ ํ˜•์‹์— ๋งž๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. NaverMemberInfo ํด๋ž˜์Šค (OAuth2MemberInfo ๊ตฌํ˜„)

@AllArgsConstructor
public class NaverMemberInfo implements OAuth2MemberInfo {

    private Map<String, Object> attributes;

    // naver OAuth2๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์‚ฌ์šฉ์ž ์ •๋ณด ์ถ”์ถœ
    @Override
    public String getProviderId() {
        return (String) attributes.get("id");
    }

    @Override
    public String getNickName() {
        return (String) attributes.get("nickname");
    }

    @Override
    public String getEmail() {
        return (String) attributes.get("email");
    }

    @Override
    public String getProvider() {
        return "naver";
    }
}
  • OAuth2MemberInfo ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ๋„ค์ด๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.

  • ๋„ค์ด๋ฒ„์˜ attributes์—์„œ ์šฐ๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ response์˜ ํ˜•์‹์ด Map์œผ๋กœ ๋˜์–ด ์žˆ์–ด Map์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • getXXXX() : ๋„ค์ด๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์‚ฌ์šฉ์ž ์ •๋ณด์—์„œ ํ‚ค์— ํ•ด๋‹นํ•˜๋Š” ๊ฐ’์„ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.

  • getProvider() : ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š” ์ œ๊ณต์ž๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ œ๊ณต์ž๋Š” naver์ž…๋‹ˆ๋‹ค.


4. Oauth2MemberService ํด๋ž˜์Šค(DefaultOAuth2UserService)

@Service
@RequiredArgsConstructor
public class Oauth2MemberService extends DefaultOAuth2UserService {

    private final MemberMapper mapper;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        System.out.println("userRequest.getAccessToken().getTokenValue() = " + userRequest.getAccessToken().getTokenValue());
        System.out.println("userRequest.getClientRegistration() = " + userRequest.getClientRegistration());
        System.out.println("userRequest.getClientRegistration().getRegistrationId() = " + userRequest.getClientRegistration().getRegistrationId());

        OAuth2User oAuth2User = super.loadUser(userRequest); //๋„ค์ด๋ฒ„ ์‚ฌ์šฉ์ž ์ •๋ณด ๋กœ๋“œ
        System.out.println("oAuth2User = " + oAuth2User.getAttributes());

        String platform = userRequest.getClientRegistration().getRegistrationId(); //๋กœ๊ทธ์ธํ•œ ํด๋ผ์ด์–ธํŠธ์˜ ๋“ฑ๋ก ID

        OAuth2MemberInfo response = null;
        if (platform.equals("naver")) {
            System.out.println("๋„ค์ด๋ฒ„ ๋กœ๊ทธ์ธ ์š”์ฒญ");
            response = new NaverMemberInfo((Map) oAuth2User.getAttributes().get("response")); //๋„ค์ด๋ฒ„์— ์žˆ๋Š” ์‚ฌ์šฉ์ž ์ •๋ณด ์ถ”์ถœ
            System.out.println(response);
        }
        
        // DB์— ์ €์žฅ
        Member member = mapper.selectByEmail(response.getEmail());
        if (member == null) {
            member = Member
                    .builder()
                    .email(response.getEmail())
                    .nickName(response.getNickName())
                    .password(userRequest.getAccessToken().getTokenValue())
                    .build();

            // ๋„ค์ด๋ฒ„์—์„œ ๋ฐ›์€ ์ด๋ฉ”์ผ ์กฐํšŒ ํ›„ ํ•ด๋‹น ์ด๋ฉ”์ผ์ด ์—†๋Š” ๊ฒฝ์šฐ insert
            System.out.println("์ตœ์ดˆ");
            mapper.insert(member);
        }

        return new CustomOauth2MemberDetails(member, oAuth2User.getAttributes());
    }
}
  • DefaultOAuth2UserService๋ฅผ ์ƒ์†๋ฐ›์•„ loadUser๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  • Form์„ ์‚ฌ์šฉํ•œ ๋กœ๊ทธ์ธ์€ loadUserByUsername ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  OAuth๋ฅผ ์‚ฌ์šฉํ•œ ๋กœ๊ทธ์ธ์€ loadUser ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ์ธ ์ดํ›„ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
  • super.loadUser(userRequest) : OAuth2 ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
  • platform : ํด๋ผ์ด์–ธํŠธ์˜ ๋“ฑ๋ก ID๋ฅผ ๊ฐ€์ ธ์™€ ์ œ๊ณต์ž๋ฅผ ์‹๋ณ„ํ•ฉ๋‹ˆ๋‹ค. ํ”Œ๋žซํผ๋งˆ๋‹ค ID๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.
  • ๋“ฑ๋ก ID๊ฐ€ naver์ธ ๊ฒฝ์šฐ์— ๋„ค์ด๋ฒ„์—์„œ ์ œ๊ณตํ•˜๋Š” ์‚ฌ์šฉ์ž ์ •๋ณด, ์ฆ‰ NaverMemberInfo ํด๋ž˜์Šค์˜ ์ƒ์„ฑ์ž์—์„œ๋Š” response๋ผ๋Š” ํ‚ค์— ํ•ด๋‹นํ•˜๋Š” ๊ฐ’์„ Map์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ response ๊ฐ์ฒด์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ DB์— ์กฐํšŒํ•˜๋ฉฐ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด DB์— ์ €์žฅ ํ•ด์ค๋‹ˆ๋‹ค.

5. CustomOauth2UserDetails ํด๋ž˜์Šค (UserDetails, OAuth2User ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„)

public class CustomOauth2MemberDetails implements UserDetails, OAuth2User {

    private Member member;
    private Map<String, Object> attributes; // ๋„ค์ด๋ฒ„์—์„œ ๋ฐ›์•„์˜จ ์ •๋ณด

    public CustomOauth2MemberDetails(Member member, Map<String, Object> attributes) {
        this.member = member;
        this.attributes = attributes;
    }

    public CustomOauth2MemberDetails(Member member) {
        this.member = member;
    }

    @Override
    public String getName() {
        return null;
    }

    @Override
    public String getPassword() {
        return member.getPassword();
    }

    @Override
    public String getUsername() {
        return member.getNickName();
    }

    @Override
    public Map<String, Object> getAttributes() {
        return attributes;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of();
    }


    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}
  • Spring Security์—์„œ ๋„ค์ด๋ฒ„์—์„œ ๋ฐ›์•„์˜จ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๊ด€๋ จ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • UserDetails๋Š” ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•œ ๊ฒฝ์šฐ ์‚ฌ์šฉํ•˜๊ณ  OAuth2User๋Š” ์†Œ์…œ ๋กœ๊ทธ์ธ ๊ตฌํ˜„ํ•œ ๊ฒฝ์šฐ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • UserDetails, OAuth2User๋Š” ํšŒ์› ์ •๋ณด๋ฅผ ๋”ฐ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š์•„ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

  • OAuth2MemberInfo ์ธํ„ฐํŽ˜์ด์Šค : ๋„ค์ด๋ฒ„์„œ ๋ฐ›์€ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋‹ค๋ฃจ๋ฉฐ ๊ฐ ์†์„ฑ์— ๋Œ€ํ•œ getXX() ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
  • NaverMemberInfo ํด๋ž˜์Šค : ๋„ค์ด๋ฒ„๋กœ ๋ถ€ํ„ฐ ๋ฐ›์€ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด ๋•Œ OAuth2MemberInfo ์ธํ„ฐํŽ˜์ด์Šค์˜ getXX() ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.
  • Oauth2MemberService ํด๋ž˜์Šค : ์ถ”์ถœํ•ด์˜จ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์ฒ˜๋ฆฌํ•˜๊ณ  DB์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • CustomOauth2UserDetails ํด๋ž˜์Šค : ๋„ค์ด๋ฒ„์—์„œ ๋ฐ›์•„์˜จ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ๋„ค์ด๋ฒ„ ์†Œ์…œ ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•˜๊ณ  ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•ฉ๋‹ˆ๋‹ค.

๋„ค์ด๋ฒ„์— ๋กœ๊ทธ์ธ์ด ๋˜์–ด ์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ,

DB์— ์ €์žฅ

profile
๊ฐœ๋ฐœ์ž ์ค€๋น„์ƒ~

0๊ฐœ์˜ ๋Œ“๊ธ€