Spring Security: Form Login & OAuth2 Login (2)

Sora Yoo·2023년 1월 27일

Spring 웹 Form Login과 OAuth2 Login을 위한 Spring Security 구현하기

지난 포스팅에서 Form Login Spring Security를 완료하고,
이번 포스팅에서는 Google OAuth2 Login을 사용하여 Spring Security 설정을 마무리한다.

Form Login 설정은 https://velog.io/@sorayoo/Spring-Security-Form-Login-OAuth2-Login-1 에서 확인할 수 있다.


Google OAuth2 설정

Google 프로젝트 설정

  1. Google API console(https://console.cloud.google.com/apis/)에서 새 프로젝트 생성

  2. 설정시 OAuth2 동의 화면(메뉴)에서 사용자 인증 정보 URI를
    http://localhost:8080/login/oauth2/code/google
    로 작성하여야 한다.

  3. 프로젝트 설정 내용에서 client ID와 client secret을 복사해놓는다.

Spring OAuth2 설정

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

application.yml

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            clientId: ***
            clientSecret: ***
            scope:
            - email
            - profile
  • 복사해놓은 client ID와 client secret을 yml에 설정해준다.
  • scope은 google로부터 받아오고자 하는 유저 정보의 scope를 지정할 수 있다.

loginForm.html

<a href="/oauth2/authorization/google">구글 로그인</a>
  • 로그인 화면에서 구글 로그인을 할 수 있는 버튼 생성
  • 프론트에서 백엔드로 소셜로그인 요청을 보내는 default UrI가 /oauth2/authorization/google이다.

SpringConfig.java

http
	...
    .and()
    .oauth2Login()
    .loginPage("/loginForm");
  • OAuth2 로그인을 활성화, 로그인 페이지 지정

로그인 진행 순서는 아래와 같다.

  1. 구글 로그인
  2. 인증 코드 받기
  3. (권한이 담긴) 액세스 토큰을 받고
  4. 유저의 프로필 정보를 가져온다.
  5. 이후 자동 회원가입 / 로그인

후처리 함수(service) 생성 후에 이를 SecurityConfig에 추가해준다.

PrincipalOauth2UserService.java

@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService{
    
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        System.out.println("userRequest: "+userRequest.getClientRegistration());
        System.out.println("userRequest: "+userRequest.getAccessToken().getTokenValue());
        System.out.println("userRequest: "+userRequest.getClientRegistration());
        System.out.println("userRequest: "+super.loadUser(userRequest).getAttributes());
        return super.loadUser(userRequest);
    }
}
  • loadUser()는 구글로부터 받은 userRequest 데이터를 후처리하는 함수

SecurityConfig.java

@Autowired
private PrincipalOauth2UserService principalOauth2UserService;

...

@Override
protected void configure(HttpSecurity http) throws Exception {
	http
    	...
        .and()
	    .oauth2Login()
	    .loginPage("/loginForm")
        .userInfoEndpoint()
	    .userService(principalOauth2UserService);
}
  • config에서 PrincipalOauth2UserService를 autowired 해주고, userService로 지정해준다.

웹에서 구글 로그인 후 userRequest 정보가 잘 받아와지는지 print하여 확인한다.
super.loadUser(userRequest).getAttribute()

{sub=***, // 구글 id(key)
name=***, 
given_name=***, 
family_name=***, 
picture=http://******, 
email=***@gmail.com, 
email_verified=true, 
locale=ko}

OAuth2 가입 유저의 경우, 가입 루트 표기를 위해 Member Domain에 provider와 providerId를 추가해준다.
Member.class

...
private String provider;
private String providerId;
...

Test

구글 로그인 후 유저 정보를 잘 받아오는지 테스트
IndexController.java

@GetMapping("/test/oauth/login")
public @ResponseBody String testOauthLogin(
    Authentication authentication,
    @AuthenticationPrincipal OAuth2User oauth
    ) {
    System.out.println("=================/test/oauth/login");
    OAuth2User oauth2user = (OAuth2User) authentication.getPrincipal();
    System.out.println("oauth2user: "+oauth2user.getAttributes());
    System.out.println("oauth: "+oauth.getAttributes());
    return "authentication";
}
  • OAuth2 가입 유저의 경우 Authentication 내의 유저 정보가 OAuth2User 객체로 담겨있다.
  • Form Login의 경우, UserDetails 객체로 유저 정보가 넘어온다.


OAuth2UserUserDetailsPrincipalDetails로 implements하여 통합해야한다. (Form Login에서 미리 작성해놓은 PrincipalDetails.class를 수정하며 진행한다.)

Form Login & OAuth2 Login 통합

UserDetailsOAuth2User를 implements하는 PrincipalDetails를 만든다.

PrincipalDetails.java

public class PrincipalDetails implements UserDetails, OAuth2User {
	...
	@Override
    public Map<String, Object> getAttributes() {
        return attributes;
    }
    @Override
    public String getName() {
        return null;
    }
}
  • implements에 OAuth2User를 추가
  • OAuth2User의 메소드인 getAttributes()getName()을 추가

OAuth2로 접속한 유저의 자동 회원가입 로직 추가
PrincipalOauth2UserService.java

@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService{
    
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    private MemberRepository memberRepository;

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

        OAuth2User oAuth2User = super.loadUser(userRequest);

        String provider = userRequest.getClientRegistration().getClientId();
        String providerId = oAuth2User.getAttribute("sub");
        String username = provider + "_" + providerId;

        String password = bCryptPasswordEncoder.encode("****");
        String email = oAuth2User.getAttribute("email");

        Member findOne = memberRepository.findByUsername(username);
        Member member = new Member();
        if (findOne == null) {
            System.out.println("최초 구글 로그인 / 가입");
            member.setUsername(username);
            member.setPassword(password);
            member.setProvider(provider);
            member.setProviderId(providerId);
            memberRepository.save(member);
            return new PrincipalDetails(member, oAuth2User.getAttributes());
        } else {
            System.out.println("이미 존재하는 회원입니다.");
            return new PrincipalDetails(findOne, oAuth2User.getAttributes());
        }

    }
}
  • 유저 정보가 없는 경우 자동 회원가입 진행
  • 이후 PrincipalDetails 리턴
  • 함수가 종료될 때 @AuthenticationPrincipal이 생성된다.

0개의 댓글