Google 클라우드 플랫폼을 클릭해서 페이지 이동을 하자.
좌측 상단에 보면 노란색 부분을 클릭해서 프로젝트를 하나 만들어주자. 프로젝트 이름은 아무거나 해도 상관없으니 식별하기 쉽게 만들자 !
사이드 바에 OAuth 동의 화면을 클릭해서 내부와 외부 두 가지 선택지가 있을 텐데 외부를 선택해주고 만들기를 눌러주자.
바로 위에 사용자 인증 정보를 클릭해서 + 사용자 인증 정보 만들기를 눌러주면 아래처럼 OAuth 클라이언트 ID가 나올 것이다. 노란색 부분을 클릭해서 클라이언트 ID를 만들자.
애플리케이션 유형은 웹 애플리케이션으로 설정해주고 이름도 아무거나 상관없으니 식별하기 쉬운 이름으로 설정하자.
그리고 아래로 쭉 내려가면 승인된 리디렉션 URI를 만들어주자.
작성하고 만들기 버튼을 클릭하면 클라이언트 ID와 클라이언트 보안 비밀번호가 팝업창으로 뜨면 성공한 것이다.
그리고 Dependency를 추가해야한다. 강의에서는 Maven을 사용했고 필자는 gradle을 사용해서 Dependency 추가하는 과정에서 에러가 발생해서 시간이 걸렸지만 gradle을 사용하는 분이라면 아래처럼 추가하면 될 것이다.
<build.gradle에 OAuth2 추가>
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version: '2.3.5.RELEASE'
application.yml에 security.oauth2.client.registration을 추가해주자.
security:
oauth2:
client:
registration:
google:
client-id: //본인의 클라이언트 ID
client-secret: // 본인의 클라이언트 보안 비밀번호
scope:
- email
- profile
SecurityConfig에 configure 메소드 마지막에 아래 코드를 추가해주자.
<SecurityConfig에 configure 메소드에 추가>
.and()
.oauth2Login()
.loginPage("/loginForm")
.userInfoEndpoint()
.userService(principalOAuth2UserService);
구글 로그인을 할 수 있도록 login.html에 태그로 구글 로그인화면으로 갈 수 있도록 추가한다.
<login.html에 추가>
<a href="/oauth2/authorization/google">구글 로그인</a>
구글로 로그인을 할 때, 구글로부터 받은 데이터를 후처리하기 위한 서비스 로직을 작성하기 위해 config 패키지 하위에 oauth 패키지를 작성하고 DefaultOAuthUserService를 상속받는 클래스를 하나 만들어주자.
@Service
public class PrincipalOAuth2UserService extends DefaultOAuth2UserService {
// google로부터 받은 userRequest 데이터에 대한 후처리되는 함수.
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
return super.loadUser(userRequest);
}
}
서버를 올리고 /loginForm 요청을 하면 로그인 화면을 보여준다. 구글 로그인을 누르면 index.html을 응답해준다.
스프링 시큐리티는 시큐리티 session을 가진다. 그리고 시큐리티 session 안에는 Authentication 객체만 들어올 수 있다.
Authentication 객체에는 두 가지 타입이 들어올 수 있는데 바로 UserDetails와 OAuth2User이다.
UserDetails는 회원 가입을 하고 일반적으로 로그인할 때 사용하고 OAuth2User는 OAuth로그인을 할 때 사용한다.
그래서 로그인 방법이 다르면 접근하기 위한 매개변수를 다르게 설정해야 한다.
그런데 따로따로 하면 처리하는 게 복잡해지는데 한 번에 처리하는 방법은 없을까?
처음 UserDetails를 구현해서 PrincipalDetails를 만들었다. 인터페이스는 다중 상속이 가능하기 때문에 OAuth2User도 같이 구현해주면 한 번에 처리할 수 있게 된다.
여기서 하나 짚고 넘어가고 싶은 게 있다. 우리가 PrincipalDetails를 만든 이유를 알고싶다.
시큐리티 session은 Authentication 객체만 들어가질 수 있고 그 안에는 UserDetails와 OAuth2User만 들어올 수 있다고 했다. 그런데 회원 가입을 하면 User 오브젝트가 사용되는데 어느 곳에서도 User 오브젝트에 접근할 수 없다. 그래서 UserDetails를 구현한 PrincipalDetails에 User 오브젝트를 넣어줘서 User 오브젝트에 접근할 수 있게 하기 위해 PrincipalDetails를 만든 것이다.
PrincipalDetails에 OAuth2User를 상속해주고 필요한 메서드를 오버라이딩해서 작성해주자.
public class PrincipalDetails implements UserDetails, OAuth2User {
private Map<String,Object> attributes;
.....
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
@Override
public String getName() {
return null;
}
}
OAuth2User를 상속받고 import method를 하면 두 개의 메소드가 오버라이딩할 수 있도록 나온다.
그리고 private 접근 제어자로 Map타입의 변수를 하나 만들어주는데 이 변수는 구글로부터 받은 데이터에 접근하기 위해서 사용할 것이다.
<PrincipalOAuth2UserService 일부>
OAuth2User oAuth2User = super.loadUser(userRequest);
OAuth2UserInfo oAuth2UserInfo = null;
if(userRequest.getClientRegistration().getRegistrationId().equals("google")){
System.out.println("구글 로그인 요청");
oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes());
}
String provider = oAuth2UserInfo.getProvider();
String providerId = oAuth2UserInfo.getProviderId();
String username = provider + "_" + providerId;
String password = bCryptPasswordEncoder.encode("겟인데어");
String email = oAuth2UserInfo.getEmail();
String role = "ROLE_USER";
User1 userEntity = userRepository.findByUsername(username); // username으로 DB에 해당 사용자가 있는지 확인
if(userEntity == null ){ // username으로 조회한 사용자가 없다면
userEntity = User1.builder() // 받은 정보를 가지고 강제로 회원가입 시킴.
.username(username)
.password(password)
.email(email)
.role(role)
.provider(provider)
.providerId(providerId)
.build();
userRepository.save(userEntity);
}
return new PrincipalDetails(userEntity, oAuth2User.getAttributes()); // 그리고 시큐리티 session에 전달
서버를 올리고 구글로 로그인하기를 클릭하면 구글에서 넘겨받은 정보가 DB에 저장되어 있는 것을 볼 수 있다.
전체 소스 코드
Github : https://github.com/gwamsoju/Security
'Spring > Security' 카테고리의 다른 글
[Spring Boot + Spring Security + jwt] 환경 설정 (0) | 2022.06.22 |
---|