OAuth2 적용하기 [1] (w. google)

최준호·2022년 12월 11일
0

molu 개발일지

목록 보기
2/2
post-thumbnail

참고 Spring Boot 게시판 OAuth 2.0 구글 로그인 구현

📗 Google OAuth2 적용하기

Google Oauth api 설정을 이미 진행한 뒤 내용을 진행합니다!

📄 Oauth2 설정

✅ 의존성 설정

dependencies {
	...
    
	// OAuth2
	implementation "org.springframework.boot:spring-boot-starter-oauth2-client:2.6.2"
}

의존성 설정과 함께

spring:
  # google
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: {clientId}
            client-secret: {secret}
            scope: profile, email

application.yml 파일도 설정해주었다.

✅ Member 객체 생성

@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EntityListeners(AuditingEntityListener.class)
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long memberId;

    @Column(nullable = false, length = 30, unique = true)
    private String username;    //아이디

    @Column(nullable = false, unique = true)
    private String nickname;

    @Column(length = 300)
    private String password;    // 추후에 자체 회원가입 만들 때 필요

    @Column(nullable = false)
    private String email;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private Role role;
}
@Getter
@RequiredArgsConstructor
public enum Role {
    USER("ROLE_USER"),
    ADMIN("ROLE_USER"),
    SOCIAL("ROLE_USER"),    // OAuth
    ;
    private final String value;
}

✅ Security 설정

import com.molu.molu.service.member.CustomOAuth2UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;

@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private final CustomOAuth2UserService customOAuth2UserService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .headers().frameOptions().disable()
                .and()
                .authorizeRequests()
                .antMatchers("/", "/v1/**", "/login/**", "/h2-console", "/h2-console/**", "/docs/index.html").permitAll()
                .anyRequest().authenticated()
                .and()
                .oauth2Login()
                .successHandler(new SimpleUrlAuthenticationSuccessHandler("/login/oauth2"))
                .userInfoEndpoint()
                .userService(customOAuth2UserService);
    }
}

security 설정을 하나씩 확인해보자

.csrf().disable() 기존 security의 Cross-Site Request Forgery 시스템은 여러 사이트의 api 호출을 받아야하는 어플리케이션에는 필요 없으므로 disable 처리 해준다.

.headers().frameOptions().disable() h2-console 사용하기 위해서 설정해주어야한다.

antMatchers() 특정 url을 지정할 수 있다.

successHandler() 로그인 성공시 endpoint url을 지정할 수 있다.

userService() OAuth 로그인을 성공한 이후에 받아온 데이터를 핸들링 하는 부분이다.

@RequiredArgsConstructor
@Service
@Slf4j
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
    private final MemberRepository memberRepository;

    @Override
    @Transactional
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
        OAuth2User oAuth2User = delegate.loadUser(userRequest);

        // OAuth2 서비스 id (google, naver, kakao ...)
        String registrationId = userRequest.getClientRegistration().getRegistrationId();
        // OAuth2 로그인 진행 시 키값
        String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
        OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName, oAuth2User.getAttributes());

        // TODO 회원 가입 로직
        Member member = memberRepository.findByEmail(attributes.getEmail())
                .orElse(attributes.saveMember());
        memberRepository.save(member);

        return new DefaultOAuth2User(
                Collections.singleton(new SimpleGrantedAuthority(member.getRole().getValue())),
                attributes.getAttributes(),
                attributes.getNameAttributeKey()
        );
    }
}

CustomOAuth2UserService 로직 구현은 다음과 같다.
OAuth2에서 모두 구현해 두었기때문에 우리는 따로 설정할 필요가 없다.

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
public class OAuthAttributes {
    private Map<String, Object> attributes;
    private String nameAttributeKey;
    private String username;
    private String nickname;
    private String email;
    private Role role;

    public static OAuthAttributes of(String registrationId, String userNameAttributeName, Map<String, Object> attributes){
        if (registrationId.equals("google")){
            return ofGoogle(userNameAttributeName, attributes);
        }
        return null;
    }

    private static OAuthAttributes ofGoogle(String userNameAttributeName, Map<String, Object> attributes) {
        return OAuthAttributes.builder()
                .username(attributes.get("email").toString())
                .email(attributes.get("email").toString())
                .nickname(attributes.get("name").toString())
                .attributes(attributes)
                .nameAttributeKey(userNameAttributeName)
                .build();
    }

    public Member saveMember(){
        return Member.builder()
                .username(email)
                .email(email)
                .nickname(nickname)
                .role(Role.SOCIAL)
                .build();
    }

}

OAuthAttributes에서는 앞으로 OAuth에서 사용될 데이터를 만들어주는 부분이다.

@RestController
@RequestMapping("/login/oauth2")
public class LoginController {

    @GetMapping("")
    public ResponseEntity<CommonApi<String>> loginGoogle(){
        return ResponseEntity.ok(CommonApi.<String>builder()
                .resultCode(ResultCode.SUCCESS)
                .resultType(ResultType.NONE)
                .data("성공")
                .build());
    }
}

최종적으로 로그인이 완료된 후 도착할 endpoint url 부분이다. 이쪽에서 마지막 JWT 토큰을 발급해주는 부분을 구현해주면 될거 같다.

profile
코딩을 깔끔하게 하고 싶어하는 초보 개발자 (편하게 글을 쓰기위해 반말체를 사용하고 있습니다! 양해 부탁드려요!) 현재 KakaoVX 근무중입니다!

0개의 댓글