반응형

OAuth2 Authentication이란?

  • 외부 Provider(ex. google)을 통해 회원 인증을 처리하는 방식
  • 아래 예제에서는 google을 Provider로 처리한다. 따라서 사전에 구글 클라우드 콘솔에 프로젝트 등록이 필요하다.
  • OAuth2LoginAuthenticationFilter를 사용한다.
    • OAuth2 로그인을 시도한 경우
    • Provider에 로그인 인증 요청
    • OAuth2LoginAuthenticationProvider를 통해 인증정보(Authentication) 생성
      • 해당 과정에서 DefaultOAuth2UserService.loadUser()를 호출
    • ThreadLocalSecurityContextHolderStrategy.setContext() 를 호출하여 인증정보를 컨텍스트에 세팅
    • HttpSessionSecurityContextRepository.saveContext() 를 호출하여 인증정보를 세션에 저장(세션키: SPRING_SECURITY_CONTEXT)

의존성 추가

implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")

application.yml

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: {client_id} // 구글 클라우드 콘솔에서 발급받은 client_id
            client-secret: {client_secret} // 구글 클라우드 콘솔에서 발급받은 client_secret
            scope: profile, email

User

@Getter
@Builder
public class User {
    private String email;
    private String password;
    private String role;
}

PrincipalUser

@Getter
@RequiredArgsConstructor
public class PrincipalUser implements UserDetails, OAuth2User {
    private final User user;
    private final Map<String, Object> attributes;

    @Override
    public String getUsername() {
        return user.getEmail();
    }

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

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

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

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

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

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

    @Override
    public String getName() {
        return (String) attributes.get("name");
    }

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

OAuth2UserService

@Service
public class OAuth2UserService extends DefaultOAuth2UserService {
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2User oAuth2User = super.loadUser(userRequest);
        String provider = userRequest.getClientRegistration().getRegistrationId();
        String email = oAuth2User.getAttribute("email");
        String oauthEmail = provider + ";" + email;

        User user = User.builder()
            .email(oauthEmail)
            .password(null)
            .role("USER")
            .build();

        // TODO 위 객체를 여기서 데이터베이스에 저장하여 회원가입 처리

        return new PrincipalUser(user, oAuth2User.getAttributes());
    }
}

SecurityConfig

@EnableWebSecurity(debug = true)
@Configuration
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(
        HttpSecurity http,
        OAuth2UserService oAuth2UserService
    ) throws Exception {
        return http
            .authorizeRequests(authorize -> {
                authorize.requestMatchers("/error/**").permitAll();
                authorize.anyRequest().authenticated();
            })
            .oauth2Login(oauth2 -> {
                oauth2.defaultSuccessUrl("/"); // 로그인 성공 후 이동할 페이지 URL
                oauth2.userInfoEndpoint(userInfo -> {
                    userInfo.userService(oAuth2UserService);
                });
            })
            .csrf(AbstractHttpConfigurer::disable)
            .build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

DemoController

@RestController
public class DemoController {
    @GetMapping("/api/v1/demo")
    public String demoV1(@AuthenticationPrincipal PrincipalUser user) {
        return user.getUser().getEmail();
    }
}

인증 및 API 요청 테스트

  • /oauth2/authorization/google 로 접속하거나 인증되지 않은 상태로 /api/v1/demo 요청을 하면 google 로그인 페이지로 이동
  • 로그인을 성공하면 /api/v1/demo API를 요청하여 응답이 내려오는지 확인
반응형

+ Recent posts