MakerHyeon

[SpringBoot] Security 구글 로그인 본문

SpringBoot/Security

[SpringBoot] Security 구글 로그인

유쾌한고등어 2023. 1. 21. 17:44

● 구글 로그인 준비하기

 

구글 API 콘솔 바로가기

 

0. 프로젝트 생성

1. oauth 동의안구성

2. 사용자인증정보 -> Oauth client ID 만들기 -> 승인된 redirection(http://localhost:8080/login/oauth2/code/google)

3. dependency 추가

// build.gradle dependency 추가
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

 

4. application.yml에 키 등록

  security:
    oauth2:
      client:
        registration:
          google:
            client-id: 658211206947-ephbghihhrh662n8ks32ijhlnnlk91dq.apps.googleusercontent.com
            client-secret: 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
            scope:
              - email
              - profile

 

5. 로그인(고정링크) 링크 생성

// loginForm.html
<a href="/oauth2/authorization/google">구글 로그인</a>

 

6. SecurityConfig 설정

// SecurityConfig.java

...
.defaultSuccessUrl("/")
.and()
.oauth2Login()
.loginPage("/loginForm"); // 구글 로그인 완료 후 후처리가 필요함
return http.build();
}

● 구글 회원 정보 받아오기

1) 코드받기 (인증)

2) 액세스토큰 (권한)

3) 사용자프로필 정보를 가져오고

4-1) 그 정보를 토대로 회원가입을 자동으로 진행시키기도 한다.

4-2) 집주소등 추가적인 주소가 필요하면 추가적 회원가입창으로 보낸다.

 

구글 로그인이 완료된 후에는 코드를 받지않고 액세스토큰과 사용자프로필정보를 바로 받는다.

 

7. PrincipalOauth2UserService 파일 생성

 

// PrincipalOauth2UserService.java
@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {

// 구글로부터 받은 userRequest 데이터에 대한 후처리가 되는 함수
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
//        System.out.println("getClientRegistration: "+userRequest.getClientRegistration());
//        System.out.println("getAccessToken: "+userRequest.getAccessToken().getTokenValue());
//        System.out.println("getAttributes: "+super.loadUser(userRequest).getAttributes());
    return super.loadUser(userRequest);
	}
}

 

실행결과:

- registrationId로 어떤 OAuth로 로그인 했는지 확인 가능.

- 구글로그인 버튼 클릭 -> 구글로그인창 -> 로그인 완료-> code를 리턴(OAuth-Client라이브러리) -> AccessToken요청

- userRequest 정보 -> loadUser 함수 호출 -> 회원프로필 받아줌

 

결과: getClientRegistration: ClientRegistration{registrationId='google', clientId='658211206947-ephbghihhrh662n8ks32ijhlnnlk91dq.apps.googleusercontent.com', clientSecret='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', clientAuthenticationMethod=org.springframework.security.oauth2.core.ClientAuthenticationMethod@4fcef9d3, authorizationGrantType=org.springframework.security.oauth2.core.AuthorizationGrantType@5da5e9f3, redirectUri='{baseUrl}/{action}/oauth2/code/{registrationId}', scopes=[email, profile], providerDetails=org.springframework.security.oauth2.client.registration.ClientRegistration$ProviderDetails@155e2571, clientName='Google'} getAccessToken: ya29.a0AX9GBdXYx8t9s6j48WgCHZecwAedRIKWZYrDMh7RB6RpAI5zSaQEw8xKoWloigiWw95zuapTEEAYhm2FxHtSQF7YtJXDHvmIb4XCXkAjq7fu0q2QZYmULwb-X3OnpZSFEH-V5VybSYuiPB_E8eD7jdpt7ErraCgYKAfMSARASFQHUCsbCLmWrMnCFRb3jL5iexAsKpw0163 getAttributes: {sub=111633286854698474532, name=보노보노처럼살래, given_name=처럼살래, family_name=보노보노, picture=https://lh3.googleusercontent.com/a/AEdFTp5_v6xJfWQerJ01KvBLjIj2nHj6hxyRqPpyIMmrOA=s96-c, email=hyeonkjjang@gmail.com, email_verified=true, locale=ko}

 

 

8. SecurityConfig 설정

      .defaultSuccessUrl("/")
        .and()
        .oauth2Login()
        .loginPage("/loginForm")
        .userInfoEndpoint()
        .userService(principalOauth2UserService);
return http.build();

●  Authentication객체가 가질수 있는 2가지 타입

스프링 시큐리티는 스프링 시큐리티 세션을 들고 있다.
그러면 원래 서버 세션 영역 안에 시큐리티가 관리하는 세션이 따로 존재하게 된다.
시큐리티 세션에는 무조건 Authentication 객체 만 들어갈 수 있다.
Authentication가 시큐리티세션 안에 들어가 있다는 것은 로그인된 상태라는 의미이다.
Authentication에는 2개의 타입이 들어갈 수 있는데 UserDetails, OAuth2User이다.
 
문제점 : 이때 세션이 2개의 타입으로 나눠졌기 때문에 컨트롤러에서 처리하기 복잡해진다는 문제점이 발생한다! 왜냐하면 일반적인 로그인을 할 때엔 UserDetails 타입으로 Authentication 객체가 만들어지고, 구글 로그인처럼 OAuth 로그인을 할 때엔 OAuth2User 타입으로 Authentication 객체가 만들어지기 때문이다.
 
해결방법 : PrincipalDetails에 UserDetails, OAuth2User를 implements한다. 우리는 오직 PrincipalDetails 만 활용하면 된다.

●  구글 로그인 및 자동 회원가입 진행 완료

- PrincipalDetails implements UserDetails, OAuth2User

// PrincipalDetails.java
private Map<String,Object> attributes;
// Map
@Override
public Map<String, Object> getAttributes() {
    return attributes;
}
// 생성자

// 일반 로그인
public PrincipalDetails(User user) {
    this.user = user;
}

// OAut 로그인
public PrincipalDetails(User user,Map<String,Object> attributes) {

    this.user = user;
    this.attributes = attributes;
}

 

- User에 Builder 생성

@Builder
public User(String username, String password,
String email, String role, String provider,
String providerId, Timestamp loginDate, Timestamp createDate) {
    this.username = username;
    this.password = password;
    this.email = email;
    this.role = role;
    this.provider = provider;
    this.providerId = providerId;
    this.loginDate = loginDate;
    this.createDate = createDate;
}

 

- PrincipalOauth2UserService

- Service 함수 종료시 @AuthenticationPrincipal 어노테이션이 만들어진다.

@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    private UserRepository userRepository;

    // 구글로부터 받은 userRequest 데이터에 대한 후처리가 되는 함수
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
//        System.out.println("getClientRegistration: "+userRequest.getClientRegistration());
//        System.out.println("getAccessToken: "+userRequest.getAccessToken().getTokenValue());
//        System.out.println("getAttributes: "+super.loadUser(userRequest).getAttributes());

        OAuth2User oAuth2User = super.loadUser(userRequest);
       // oAuth2User =oAuth2User.getAttributes();

        String provider = userRequest.getClientRegistration().getRegistrationId(); // google
        String providerId = oAuth2User.getAttribute("sub");
        String username = provider+"-"+providerId; // google_103234234234534543534
        String password = bCryptPasswordEncoder.encode("겟인데어");
        String email = oAuth2User.getAttribute("email");
        String role = "ROLE_USER";


        User userEntity = userRepository.findByUsername(username);
        if(userEntity == null){        // 이미 회원가입이 되어있을때 에러
            userEntity = User.builder()
                    .username(username)
                    .password(password)
                    .email(email)
                    .role(role)
                    .provider(provider)
                    .providerId(providerId)
                    .build();
            userRepository.save(userEntity);
        }
        return new PrincipalDetails(userEntity,oAuth2User.getAttributes());
    }
}

 

- Controller접근

// OAuth 로긴을 해도 PrincipalDetails
// 일반 로그인을 해도 PrincipalDetails
@GetMapping("/user")
public @ResponseBody String user(@AuthenticationPrincipal PrincipalDetails principalDetails){
    System.out.println("principalDetails: "+principalDetails.getUser());

    return "user";
}

 

 

Comments