기본적인 Spring Security 내용과 OAuth2의 동작 방식에 대한 설명 글이다.
OAuth에서 사용하는 클라이언트, 리소스 오너, auth 서버, 리소스 서버 용어에 대한 개념도 들어가있다.
Spring Security + OAuth2
1. Spring Security # Spring Security? : Spring 환경 안에서 authentication, authorization을 진행하는 하위 프레임워크이다. authentication은 유저가 제시한 ID와 password에 따라 이 유저가 해당 서비스에 등록된 유저인
buchu-doodle.tistory.com
Spring Security와 OAuth2를 연동하여 프로젝트에 담아보았다. 예시로는 구글을 들었지만 페이스북, 네이버 등에서 적용되는 방식도 크게 다르지 않다.
깃헙 주소:
GitHub - BuchuKim/oauth2-study: OAuth2 관련 공부를 위한 프로젝트입니다.
OAuth2 관련 공부를 위한 프로젝트입니다. Contribute to BuchuKim/oauth2-study development by creating an account on GitHub.
github.com
0. Dependency
OAuth2 study 프로젝트를 진행하기 위해 사용한 dependency 목록이다. 핵심이 되는 것은 oauth2-client, security 정도일 것이다. 템플릿 엔진으로 thymeleaf를, 디비로 h2 in-memory DB를 이용했다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
1. 구글 로그인 연동 준비
OAuth 서비스를 이용하기 위해선 우리의 클라이언트 서비스가 Resource Server의 승인을 받는 과정이 먼저 필요하다. 구글에서 허락을 해줘야 구글 사용자 정보를 가져오든가 말든가 하기 때문이다.
1) 구글 API 및 서비스 페이지로 이동한다. 상단의 프로젝트 리스트 메뉴로 이동한 뒤 <새 프로젝트> 버튼을 클릭한다.
2) 프로젝트 이름과 위치(관련 조직이 존재하지 않으므로 조직 없음으로 선택) 설정 후 만들기 버튼을 눌러 프로젝트를 생성한다.
3) 사용자 인증 정보 - 동의 화면 구성 - 외부 - 만들기를 클릭한다. 구글의 리소스 오너라면 누구나 클라이언트 서비스를 이용할 수 있어야 하므로 대상 사용자를 외부로 선택해야 한다.
4) 앱 등록 configuration을 진행한다.
- OAuth2 동의 화면 구성에서 현재는 localhost 환경에서 개발 진행중이므로 넘기고, 필요한 관리자 이메일 정보를 등록한다.
- 구글 서버에 존재하는 유저의 데이터 중 현재 클라이언트에 제공할 범위를 설정한다. 정말 로그인을 위한 기본적인 유저 정보와 Access Token만이 필요하므로 위의 세 개를 추가하여 <업데이트>버튼을 누른다.
- 현재는 개발 단계이므로 기타 추가 설정은 건들 필요 없다.
5) 사용자 인증 정보 - 사용자 인증 정보 만들기 - OAuth 클라이언트 ID를 클릭한다.
6) 그 뒤 아래처럼 OAuth를 이용하는 클라이언트 서비스를 등록한다.
현재 웹 서비스를 개발하고 있으므로 어플리케이션 유형을 웹 애플리케이션으로, 스프링 부트를 이용한 로컬 환경의 개발이므로 승인된 자바스크립트 원본 URI를 localhost:8080으로 작성한다.
리다이렉션 URI는 유저가 구글을 통한 로그인 성공 후에 구글에서 제공하는 정보를 담고 리다이렉션할 주소이다. Access Token과 앞서 설정한 권한 정보 목록을 담은 구글의 OAuth2Request가 해당 리디렉션 URI에 도착할 것이다. 실제 리다이렉션 주소에 도착한 request 정보를 살펴보면 리소스 서버, 즉 구글이 제공한 사용자 정보를 확인할 수 있다.
Spring Security 환경에서는 리다이렉션 주소를 "{DOMAIN_NAME}/login/oauth2/code/{REGISTRATION_ID}" 로 지정해주면 자동으로 OAuth2 request로서 Authorization Server가 제공한 유저 정보를 받는다. 서비스 단에서 유저 정보를 저장하는 등의 핸들러 작성만 잘 하면 따로 추가 configuration할 필요 없이 권한에 막혀 들어가지 못했던 페이지로 유저를 리다이렉트 시킨다.
7) 생성된 OAuth 클라이언트 ID와 비밀번호를 확인한다. 이 정보는 프로젝트의 OAuth2 configuration에 쓰이므로 잘 보관해야 한다.
2. 프로젝트 설정
프로젝트의 resource 디렉토리 아래 "application-oauth.properties" 파일을 추가하고 다음을 입력한다. 아래 정보는 깃허브와 같은 공개된 장소에 올라가면 안되는 민감정보이므로 따로 관리하기 위해서 설정 파일을 별도로 작성해야한다.
spring.security.oauth2.client.registration.google.client-id={클라이언트 ID}
spring.security.oauth2.client.registration.google.client-secret={클라이언트 보안 비밀번호}
spring.security.oauth2.client.registration.google.scope=profile,email
application-oauth로 파일 이름을 설정했으므로 oauth라는 property profile이 생성됐다. (application-**.properties 형식의 이름으로 설정 파일을 생성했을 경우 **에 들어가는 이름의 property profile이 생성된다.) application.properties에 다음 코드를 추가하여 방금 생성한 설정 파일을 프로젝트에 적용시킨다.
spring.profiles.include=oauth
이 이후 .gitignore에 application-oauth.properties를 추가해 git commit할 때에 비밀번호가 올라가지 않도록 한다.
참고로 github의 public repository에 해당 client secret이 올라가게 되면 이메일로 비밀번호가 깃헙에 올라갔다는 경고문이 오더라.. 알고 싶지 않았다. 잘 관리하도록 하자.
3. Entity 및 Repository 설정
가장 먼저 Spring Security의 Authorization에서 사용할 Role이다. 유저가 가진 role에 따라 클라이언트에서 접근 가능한 서비스 목록이 달라진다. Spring Security에서 role은 "ROLE_"의 접두어가 붙어야 한다는 것을 알아두자.
package com.buchu.oauth2.entity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum Role {
ADMIN("ROLE_ADMIN","관리자 유저"),
USER("ROLE_USER","일반 유저"),
GUEST("ROLE_GUEST","게스트 유저");
private final String key;
private final String description;
}
그 다음은 유저 정보를 담아둘 Person 엔티티이다. 원래 테이블 명을 User로 하려고 했는데 h2 데이터베이스 환경에서 user가 예약어로 설정되어 있어서 부득이하게 Person을 사용하게 됐다.
package com.buchu.oauth2.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.*;
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
@Setter
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
@Email
@NonNull
private String email;
@NotBlank
private String nickName;
@NonNull
private String provider;
@NotBlank
private String oauthPk;
@NonNull
@Enumerated(EnumType.STRING)
private Role role;
}
저장하는 정보는 기본적으로 구글 이메일인 email과 닉네임인 nickName이다. 추가로 서비스가 확장될 것을 고려해 어떤 사이트를 이용해 로그인했는지를 표시하는 provider 필드, 그리고 특정 OAuth 서비스에서 해당 유저를 구분하는 pk 값인 oauthPk 필드도 정의했다.
Role은 spring security에서 authentication에 사용하는 필드이다. @Enumerated 어노테이션을 붙여 이 필드가 enum type을 가짐을 명시해줬다. EnumType.STRING을 줌으로써 이 필드는 Enum의 key String값 자체를 갖고 있다고 설정해줬다.
그리고 Spring Data JPA 환경의 PersonRepository는 다음과 같이 작성했다. 클라이언트 사이트에서 유저 구분에 email을 이용하기로 했다. 파라미터로 준 email로 DB의 유저 쿼리를 날릴 것이다.
package com.buchu.oauth2.repository;
import com.buchu.oauth2.entity.Person;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {
public Optional<Person> findByEmail(String email);
}
4. Spring Security + OAuth2 설정
현재 spring boot 프로젝트에서 사용되는 spring security 6.0이상 버전(실제로는 6.0.2)에서는 과거와 조금 다른 부분이 많은데, 공식 문서에서 프로젝트와 관련되어 확인할 내용은 아래 몇 가지 정도이다.
- java 17 이상 필요
- WebSecurityConfigurerAdapter + configure() 없어짐 -> SecurityFilterChain 타입의 Bean 이용
- antMatchers, mvcMatchers, regexMatchers 없어짐 -> requestMatchers 이용
2023년이 다 되어서야 바뀐 내용이니 그 전에 작성된 security 글은 대부분 adapter extend 후 configure()을 overried해놓아서 조금 헤맸다.🥲 근데 굳이굳이 extends하고 override 하는 것보다는 이렇게 바꾸는게 훨씬 깔끔한 듯 하다. requestMatchers로 통일한 것도 마음에 들고.
추가로, 최근의 스프링 시큐리티에서는 @EnableWebSecurity를 붙이지 않아도 프로젝트의 모든 경로의 접근에 auth가 필요하다. 때문에 spring security를 프로젝트에 적용시킨 후에는 보안 관련 configuration을 진행하는 것이 필수이다!
이제 코딩할 부분이 가장 많은 단계 도착! 유저가 사이트에 도착해서 구글 로그인을 성공하기까지 흐름을 하나하나 따라가보겠다.
1) SecurityConfiguration 클래스 안에 SecurityFilterChain Bean 정의
일단 유저의 authenticated 여부에 따라 허용되거나 그렇지 않은 경로를 정의해야 한다. 이를 위해 SecurityFilterChain Bean을 설정한다.
package com.buchu.oauth2.security;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
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.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final CustomOAuth2UserService customOAuth2UserService;
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.headers().frameOptions().disable()
.and()
.authorizeHttpRequests()
.requestMatchers("/check").authenticated()
.anyRequest().permitAll()
.and()
.oauth2Login().userInfoEndpoint().userService(customOAuth2UserService);
return http.build();
}
}
- csrf().disable().headers().frameOptons().disable() : h2 console 이용을 위해 잠시 disable 해놓았다. CSRF와 관련된 보안 관련 공격 및 방어 이슈는 다른 포스트에서 다룰 예정이다.
- authorizeHttpRequests().requestMatchers() : requestMatchers의 각 request path(pattern)와 method에 필요한 인증 및 인가 정보를 설정하는 단계이다. permitAll()로 별도의 auth 정보가 필요 없음을, authenticated()로 로그인이 필요한 경로임을, hasRole("{ROLE_NAME}")으로 특정 role이 필요한 경로임을 설정할 수 있다. Ex) .requestMatchers("/needs-admin-path").hasRole("ADMIN")
- anyRequest() : 앞서 설정한 request path 외의 나머지 request를 어떻게 처리할 것인지 설정할 수 있다.
- oauth2Login() : OAuth2 client와 관련된 설정 진입점. loginPage()로 oauth2 로그인 페이지 path를 설정할 수 있고(defalut : /login), 로그인 성공 후 요청을 처리할 successHandler나 defaultSuccessUrl 등을 설정할 수 있다.
- userInfoEndpoint() : oauth를 이용한 로그인 성공 후 사용자 정보를 가져올 핸들러/서비스를 설정한다. .userService()의 경우파라미터로 OAuth2UserService 빈이 들어가게 된다.
소셜 로그인 성공 뒤 구글의 redirection URI로 도착한 유저의 구글 정보를 토대로 회원가입 혹은 로그인 처리를 할 user service bean을 작성하겠다. 필요한 것을 몇 가지 생각해보자.
일단 registrationId, 즉 현재 로그인하는 oauth2 서비스가 구글인지 페이스북인지 등을 나타내주는 정보에 따라 OAuth2 request를 다르게 처리해주어야 한다. 지금은 구글만 사용하지만, 추후에 페이스북이나 카카오, 네이버 등에서 추가로 이용할 수도 있기 때문이다.
그리고 소셜 이메일을 받은 뒤, findByEmail()을 통해 지금 구글을 통해 로그인 시도를 하는 유저가 현재 서비스의 유저인지 확인한다. 구글 이메일이 DB에 존재하지 않는 유저라면 새로운 Person 오브젝트를 DB에 넣어 회원가입 처리를 해주어야 하고, 그렇지 않은 경우 기존 서비스의 프로필과 구글 프로필을 비교해 달라진 점을 update해야 한다.
마지막으로 세션에 넣을 간단한 유저 정보이다. SecurityContextHolder을 이용해 현재 authenticated된 실제 유저 정보를 직접 가져올 수도 있지만, 매번 context를 호출하는 것은 부담스럽다. 현재 로그인중인 유저의 닉네임과 이메일 정보 정도를 세션에 간단히 저장하는 과정이 필요하다.
2) OAuth2Attributes 정의
일단 request에서 필요한 정보를 추려내는 처리부터 시작하자. callback URI로 받은 각 소셜 oauth 서비스의 유저 정보 attributes를 뽑아내기 위해 OAuth2Attributes 클래스를 만들었다.
우리가 설정한 Person Entity에 필요한 정보는 email, nickName, provider, oauthPk이다. role은 일반 사용자로 로그인할 시 "USER"을 기본값으로 주기로 했다.
package com.buchu.oauth2.security;
import com.buchu.oauth2.entity.Person;
import com.buchu.oauth2.entity.Role;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import java.util.Map;
@AllArgsConstructor
@Builder
@Getter
public class OAuth2Attributes {
private String email;
private String nickName;
private String provider;
private String nameAttributeKey;
private Map<String,Object> attributes;
public static OAuth2Attributes of(String registrationId,
String userNameAttributeName,
Map<String,Object> attributes) {
// registrationId 에 따라 attributes 달라짐
switch (registrationId) {
case "google":
return ofGoogle(userNameAttributeName, attributes);
default:
// 지원하지 않는 registrationId
throw new RuntimeException("NO AVAILABLE REGISTRATION ID!!");
}
}
private static OAuth2Attributes ofGoogle(String userNameAttributeName,
Map<String,Object> attributes) {
return OAuth2Attributes.builder()
.email((String) attributes.get("email"))
.nickName((String) attributes.get("name"))
.provider("google")
.nameAttributeKey(userNameAttributeName)
.attributes(attributes)
.build();
}
public Person toEntity() {
// 현재 oauth attribute 가지고 entity 생성
return Person.builder()
.email(this.email)
.nickName(this.nickName)
.provider(this.provider)
.role(Role.USER)
.oauthPk((String) this.attributes.get(this.nameAttributeKey))
.build();
}
}
- of() : oauth 서비스를 제공하는 주체를 구분하기 위한 registrationId, 각 소셜 서비스에서 유저를 구분하는데 쓰이는 필드 이름인 userNameAttributeName, 그리고 oauth 서비스에서 실제 유저에 대한 정보를 담아 제공하는 map 오브젝트인 attributes를 파라미터로 받는다. registrationId가 google인지, facebook인지, naver인지에 따라 제공하는 attributes 정보가 다르기 때문에 소셜 서비스에 따라 유저 정보를 다르게 뽑아내는 method들을 switch 구문으로 구분했다.
- ofGoogle() : 구글의 경우, spring oauth2 client가 뽑아낸 OAuth2User attributes는 다음과 같다.
{sub=***, name=***, given_name=***, family_name=***, picture=***, email=***, email_verified=***, locale=**}
구글 API 생성시에 설정한 리다이렉션 URI로 위 정보를 body로 담은 request가 도착하는 것이다.
sub은 구글에서 사용하는 userNameAttributeName field로, value값은 구글 내부에서 구글 유저들을 구분하는데 쓰인다. name은 닉네임, given_name과 family_name은 이름과 성, email은 구글 이메일 주소이다.
- toEntity() : of() method를 통해 Person object 구성에 필요한 정보들을 가져온 뒤 실제 서비스에서 사용되는 유저 오브젝트를 빌드하는 method이다.
3) CustomOAuth2UserService 정의
다음으로 구글 서비스를 통해 로그인한 유저가 기존에 있던 유저인지 여부에 따라 DB 저장을 실행하고 로그인 처리를 하는 유저 서비스를 작성해야한다. 이를 위해 auth request로부터 OAuth2User를 로드하고 로그인 처리까지 실행하는 OAuth2UserService를 implement했다.
package com.buchu.oauth2.security;
import com.buchu.oauth2.entity.Person;
import com.buchu.oauth2.repository.PersonRepository;
import jakarta.servlet.http.HttpSession;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import java.util.Collections;
@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final PersonRepository personRepository;
private final HttpSession httpSession;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
// default oauth2 service 사용하여 OAuth2 request로부터 OAuth2 유저 가져옴
OAuth2User oAuth2User = new DefaultOAuth2UserService()
.loadUser(userRequest);
// registrationId : oauth2 service 구분. 구글? 페이스북? 네이버?
String registrationId = userRequest.getClientRegistration().getRegistrationId();
// userNameAttributeName : oauth2 서비스에서 pk로 사용되는 필드 이름
String userNameAttributeName = userRequest.getClientRegistration()
.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
// oauth2 service에 따른 attribute 뽑아내기
OAuth2Attributes attributes = OAuth2Attributes.of(registrationId,
userNameAttributeName, oAuth2User.getAttributes());
Person person = saveIfNew(attributes);
// session 저장
httpSession.setAttribute(
"user",
SessionUser.fromEntity(person));
return new DefaultOAuth2User(Collections.singleton(
new SimpleGrantedAuthority(person.getRole().getKey())),
attributes.getAttributes(),
attributes.getNameAttributeKey());
}
@Transactional
private Person saveIfNew(OAuth2Attributes attributes) {
// 기존에 email 가진 유저가 없을 때 save
return personRepository.save(
personRepository.findByEmail(attributes.getEmail())
.orElse(attributes.toEntity()));
}
}
전반적인 과정은 주석으로 설명했으니 넘어가고, saveIfNew() method가 DB 접근 관련 method이므로 @Transactional을 붙여주었다. 그리고 loadUser()에서 return되는 DefaultOAuth2User의 생성자로 role, attribute, nameAttributeKey를 넘겨주는 것을 확인하자.
session 저장 관련 사항은 바로 밑에 설명하겠다.
4) Session에 간단한 유저 정보를 저장하는 SessionUser 클래스 작성
로그인을 한 뒤 유저 정보를 표시하기 위해 매번 Security Context를 불러오는 대신 세션에 필요한 정보, 여기선 간단하게 이메일과 닉네임만을 저장하기 위해 그 DTO를 담당할 SessionUser 클래스를 작성했다.
package com.buchu.oauth2.security;
import com.buchu.oauth2.entity.Person;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
@AllArgsConstructor
@Builder
@Getter
public class SessionUser {
private String email;
private String nickName;
public static SessionUser fromEntity(Person person) {
return SessionUser.builder()
.email(person.getEmail())
.nickName(person.getNickName())
.build();
}
}
fromEntity()로 방금 로그인 처리 된 Person entity를 받아 SessionUser를 빌드한 후 세션의 user attribute에 저장하는 식이다.
끝났다! 전체적인 시나리오를 정리해보자.
유저가 로그인이 필요한 경로에 접근 -> 구글 로그인 페이지로 이동 -> 로그인 후 유저 정보를 담은 body를 가지고 리다이렉션 URI로 이동 -> OAuth2UserService가 OAuth2Attributes로 필요 정보 뽑아냄 -> saveIfNew -> 로그인 유저 세션에 저장 -> 로그인 필요한 경로로 리다이렉트
5. 컨트롤러 작성
만든 oauth2 서비스가 제대로 동작하는지 확인하기 위해 컨트롤러를 간단하게 작성했다.
package com.buchu.oauth2.controller;
import com.buchu.oauth2.security.SessionUser;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
@RequiredArgsConstructor
@Slf4j
public class OAuth2Controller {
private final HttpSession httpSession;
@GetMapping("/")
public String getHome() {
return "home.html";
}
@GetMapping("/check")
public String showCurrentUser(Model model) {
OAuth2User user =
(OAuth2User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.info("oauth2 유저의 user.getName() 결과 : {}", user.getName());
SessionUser sessionUser =
(SessionUser) httpSession.getAttribute("user");
model.addAttribute("sessionUser",sessionUser);
return "check.html";
}
}
@Slf4j 모듈을 이용해 현재 SecurityContextHolder에 로그인되어있는 유저를 가져와 getName() 로그를 찍어보고, sessionUser을 view단으로 내보내 현재 유저가 로그인이 잘 되었는지 확인할 생각이다.
thymeleaf를 이용한 view 템플릿도 작성했다.
home.html:
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>HOME</title>
</head>
<body>
<h4>home</h4>
<h3><a th:href="@{/check}">Check user info</a></h3>
</body>
</html>
check.html:
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CHECK</title>
</head>
<body>
<h3 th:text="${'session 유저 이메일 : '+sessionUser.email}"></h3>
<h3 th:text="${'session 유저 닉네임 : '+sessionUser.nickName}"></h3>
</body>
</html>
6. 테스트
h2 콘솔을 사용하고 hibernate가 생성한 SQL문을 확인하기 위해 application.properties에 다음 설정을 추가했다.
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.datasource.url=jdbc:h2:mem:test
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.hibernate.ddl-auto=create
프로젝트 빌드 후 실행하여, h2 console창으로 들어간다.
현재 Person 테이블에 존재하는 데이터는 아무것도 없다.
localhost:8080 주소로 접속하면 home.html에 작성한 html 파일이 아래와 같이 나타난다.
Check user info를 클릭해 /check 페이지로 이동하면 설정된 security filter chain에 의해 authenticated()로 막히게 되므로, 곧바로 소셜 로그인 창으로 이동된다.
현재의 경우 등록된 OAuth 서비스가 구글밖에 없으므로 자동으로 구글 로그인 페이지로 이동하게 되는데, 만약 목록이 여러개일 경우 login 선택 페이지로 이동하게 된다.
원하는 계정을 선택하여 로그인한다.
세션 등록은 성공! 이제 로그에 찍혔을 getName()을 확인해보자.
여기도 별 탈 없이 마무리 된 것 같다.
마지막으로 현재 "처음으로" 로그인한 유저의 정보가 DB에 잘 저장되었는지 확인해야한다. 다시 h2 콘솔창으로 들어가 유저를 검색한다.
원하는 내용이 완벽하게 들어갔음을 확인할 수 있다.
created_at과 updated_at 필드는 내가 프로젝트를 진행하는 과정에서 따로 추가한 것이니 신경쓰지 않아도 된다.
OAuth 관련한 실습을 처음부터 끝까지 쭉 한 번 진행해보고 싶었는데 할 수 있어서 좋은 시간이었다! 비록 정리하는데 하루가 다 걸렸지만..