반응형
OAuth 로그인이란?
- 요즘 대부분의 사이트에서 카카오, 네이버, 구글 로그인 등을 지원함
- 사이트에서 직접 회원가입을 하지 않고 위의 소셜 서비스에 로그인을 함으로써 로그인을 할 수 있게 해줌
- 이러한 로그인 과정을 OAuth 로그인이라고 함
구글 로그인 예제
- [Spring Boot] Spring Security를 사용한 로그인 구현 (Form Login) 여기에서 만든 코드들을 사용하여 구글 로그인을 진행
- PrincipalDetails, SecurityConfig, 화면, Entity, Controller 등 재사용
- 과정은 사이트마다 비슷하기 때문에 일단 구글 로그인부터 정리
- 카카오, 네이버, 페이스북은 [Spring Boot] OAuth 2.0 로그인 (카카오, 네이버, 페이스북 로그인)에 정리
구글 API 콘솔에서 프로젝트 생성
- 구글 API 콘솔 접속
- 새 프로젝트 생성 (프로젝트명 : OAuth-Login)
- 방금 생성한 프로젝트로 이동 후 OAuth 동의 화면 탭으로 이동
- 외부 선택 후 만들기 클릭
- 앱 이름(MyApp), 사용자 지원 이메일 등 필수 칸들을 채워주고 저장 후 계속 클릭
- 다음 버튼을 계속 눌러 생성 완료
- 사용자 인증 정보 탭으로 이동
- 사용자 인증 정보 만들기 클릭
- OAuth 클라이언트 ID 클릭
- 원하는 애플리케이션 유형 선택 후 이름은 자유롭게 설정
- 승인된 리디렉션 URI는 일단 로컬 프로젝트이기 때문에 'http://localhost:8080/login/oauth2/code/google'로 설정해 주었음
- 프로젝트의 주소에 맞는 URL을 넣어주면 사용 가능하지만 /login/oauth2/code/google은 고정
- 생성이 완료되면 다음과 같이 클라이언트 ID와 클라이언트 보안 비밀번호를 다운받을 수 있음
라이브러리 추가
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
application.yml에 아래 정보 추가
spring:
security:
oauth2:
client:
registration:
google:
client-id: 생성된 클라이언트 ID
client-secret: 생성된 보안 비밀번호
scope:
- email
- profile
- 만약 프로젝트를 깃에 올리는 등의 경우 보안을 위해 client-id, client-secret을 application.yml에 적어주지 않고 환경변수로 등록하여 사용할 수도 있음
- 키 값으로 아래 두개를 넣어주고 각각에 클라이언트 Id와 보안 비밀번호를 넣어주면 됨
- SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_CLIENT-ID
- SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_CLIENT-SECRET
User 엔티티에 컬럼 추가
- provider에는 "google"이 들어가게 되고, providerId에는 구글로 로그인한 유저의 고유 ID가 들어가게 됨
private String provider;
private String providerId;
PrincipalDetails 수정 사항
- 전에는 UserDetails만 상속받았지만, OAuth 로그인을 위해 OAuth2User 상속받아야 함
- 사이트에서 직접 회원가입한 User들은 UserDetails 타입으로 저장되고, OAuth로 가입한 User들을 OAuth2User로 저장되는데 이를 PrincipalDetails로 한번에 상속받아 사용할 수 있도록 하기 위해 상속
public class PrincipalDetails implements UserDetails, OAuth2User
- attributes라는 Map을 만들고 생성자 추가 (메소드 오버로딩)
- attributes는 구글 로그인을 통해서 받은 정보들을 그대로 담아 return 해주는 역할
private Map<String, Object> attributes;
public PrincipalDetails(User user, Map<String, Object> attributes) {
this.user = user;
this.attributes = attributes;
}
- 이제 상속받은 OAuth2User의 메소드 Override 진행
@Override
public String getName() {
return null;
}
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
PrincipalOauth2UserService 추가
- Form을 통한 회원가입은 PrincipalDetailsService에서 진행되었음
- PrincipalOauth2UserService는 DefaultOauth2UserService를 상속받아 회원가입을 진행시켜 줌
- 만약 첫 OAuth 로그인이면 회원가입도 자동으로 시켜줘야 함
- userRequest와 oAuth2User로 필요한 정보들을 추출하여 회원가입 진행
- oAuth2User.getAttributes를 통해 구글에서 넘어온 oAuth2User에 담겨진 정보들을 로그로 출력해보면 아래와 같음
- 고유 ID, 이름, 프로필 이미지, email 등을 받아옴
getAttributes : {sub=103058387739722400130, name=안창범, given_name=창범, family_name=안, picture=https://lh3.googleusercontent.com/a/AEdFTp5SiCyTaOLog9sDPN6QhWwsUj7xPbfj4HQF0fdC=s96-c, email=chb20050@gmail.com, email_verified=true, locale=ko}
- 이 정보들을 사이트에서 사용할 User에 맞게끔 정보들을 추출해 저장
- provider : 구글
- providerId : 구글에서 해당 유저 고유 ID
- loginId : provider_providerId 형식으로 저장해서 중복 방지
- password : 필요없음
- nickname : 구글에 저장된 사용자 이름으로 설정
@Service
@RequiredArgsConstructor
@Slf4j
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder encoder;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
log.info("getAttributes : {}", oAuth2User.getAttributes());
String provider = userRequest.getClientRegistration().getRegistrationId();
String providerId = oAuth2User.getAttribute("sub");
String loginId = provider + "_" +providerId;
Optional<User> optionalUser = userRepository.findByLoginId(loginId);
User user;
if(optionalUser.isEmpty()) {
user = User.builder()
.loginId(loginId)
.nickname(oAuth2User.getAttribute("name"))
.provider(provider)
.providerId(providerId)
.role(UserRole.USER)
.build();
userRepository.save(user);
} else {
user = optionalUser.get();
}
return new PrincipalDetails(user, oAuth2User.getAttributes());
}
}
SecurityConfig 수정
- Form 로그인에 대한 설정 뿐 아닌 OAuth 로그인에 대한 설정도 추가적으로 필요
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final PrincipalOauth2UserService principalOauth2UserService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
// 인증
.antMatchers("/security-login/info").authenticated()
// 인가
.antMatchers("/security-login/admin/**").hasAuthority(UserRole.ADMIN.name())
.anyRequest().permitAll()
.and()
// Form Login 방식 적용
.formLogin()
// 로그인 할 때 사용할 파라미터들
.usernameParameter("loginId")
.passwordParameter("password")
.loginPage("/security-login/login") // 로그인 페이지 URL
.defaultSuccessUrl("/security-login") // 로그인 성공 시 이동할 URL
.failureUrl("/security-login/login") // 로그인 실패 시 이동할 URL
.and()
.logout()
.logoutUrl("/security-login/logout")
.invalidateHttpSession(true).deleteCookies("JSESSIONID")
// OAuth 로그인
.and()
.oauth2Login()
.loginPage("/security-login/login")
.defaultSuccessUrl("/security-login")
.userInfoEndpoint()
.userService(principalOauth2UserService)
;
http
.exceptionHandling()
.authenticationEntryPoint(new MyAuthenticationEntryPoint())
.accessDeniedHandler(new MyAccessDeniedHandler());
}
}
login.html 추가 사항
- 아래의 a 태그를 클릭하면 아까 설정했던 URI로 이동하여 구글 로그인을 자동으로 진행
<a href="/oauth2/authorization/google">구글 로그인</a>
결과
- 이제 로그인 페이지에 방금 추가했던 a 태그도 출력됨
- 구글 로그인 클릭 시 아래와 같이 구글 로그인이 자동으로 진행됨
- 여기서 "MyApp"은 내가 설정한 앱 이름
- 첫 구글 로그인 시 아래와 같이 DB에 정보를 자동으로 넣어줘서 회원가입 진행
- 4번 유저는 직접 회원가입한 유저, 5번 유저는 구글 로그인을 통해 가입한 유저
- 인증이 필요한 페이지에도 접근됨을 확인 => 로그인 성공
반응형