JAVA Spring Boot 프로젝트-2. Oauth 2.0 구글로그인 구현하기
그다음으로 구글아이디를 통해 로그인하는 부분을 구현하고자 합니다.
시작하기에 앞서 Oauth 2.0에 대해 알아봅시다.
OAuth 2.0?
인터넷 사용자가 웹사이트나 애플리케이션에 자신의 계정 정보를 공유하지 않고도, 다른 서비스의 자원에 접근할 수 있게 해주는 개방형 표준입니다. 이 표준은 사용자의 개인정보 보호와 보안을 강화하기 위해 개발되었습니다.
OAuth 2.0의 핵심 개념/ 특징
- 토큰 기반 인증: OAuth 2.0은 사용자의 ID와 비밀번호와 같은 자격 증명 대신에, 토큰을 이용하여 서비스 간의 인증과 권한 부여를 수행합니다. 이 토큰은 제한된 시간 동안만 유효하며, 특정한 권한을 가집니다.
- 주요 파트: :
리소스 소유자(Resource Owner): 일반적으로 서비스를 사용하는 최종 사용자입니다.
리소스 서버(Resource Server): 사용자 데이터를 보유하고 있는 서버입니다.(ex네이버,구글,다음)
클라이언트(Client): 사용자 대신 리소스 서버에 접근하려는 애플리케이션입니다.
(ex 사용하려는 웹사이트나 어플리케이션)
인증 서버(Authorization Server): 클라이언트에게 접근 토큰을 발급하는 서버입니다.
- 프로세스(Flow): OAuth 2.0은 다양한 인증 프로세스을 제공합니다. 이들은 사용 사례와 애플리케이션의 유형에 따라 다릅니다. 대표적인 프로세스로는 Authorization Code Flow, Implicit Flow, Password Flow, Client Credentials Flow 등이 있습니다.
그중에서 평상시 가장많이 사용되는 Authorization Code Flow 에 대해 좀더 자세히 살펴보겠습니다.
과정설명:
- 클라이언트 등록 및 로그인 요청: 사용자가 클라이언트(예: 웹 애플리케이션)에서 'Google 로그인' 버튼을 클릭하여 인증을 시작합니다. 클라이언트는 인증 서버로 리디렉션하는 요청을 보냅니다.
- 사용자 인증 및 동의: 사용자는 Google 인증 페이지에서 로그인을 하고, 클라이언트 애플리케이션이 요청한 권한에 대해 동의합니다.
- 인증 코드 전송: 사용자가 동의하면 Google은 클라이언트에게 인중 코드를 리디렉션 URL을 통해 전송합니다.
- 인증 토큰 요청: 클라이언트는 받은 인증 코드를 사용하여 인증 서버에 접근 토큰을 요청합니다.
- 사용자 정보 획득: 클라이언트는 획득한 접근 토큰을 사용하여 사용자의 정보를 얻습니다. 이 정보는 사용자 데이터베이스에 저장하거나 업데이트할 수 있습니다.
- 인증된 사용자에게 서비스 제공: 사용자 인증이 성공하면 클라이언트는 사용자에게 서비스를 제공합니다. 예를 들어, 'Hello World :)' 메시지를 화면에 보여줍니다.
다시정리하자면 사용자가 자신의 정보를 직접 클라이언트에게 제공하지 않고도, 클라이언트가 사용자의 동의를 받아 인증 서버로부터 접근 토큰을 안전하게 얻을 수 있도록 해줍니다. 그리고 이 접근 토큰을 사용하여 리소스 서버로부터 보호된 자원에 접근할 수 있습니다. 이 과정은 Spring Security와 같은 보안 프레임워크를 통해 구현될 수 있습니다.
구글로그인을 OAuth2.0을 구현하기 위해서는 우선 구글API콘솔에서 OAuth2 설정을 해야합니다.
https://console.cloud.google.com/apis/
1. google cloud에 접속하여 프로젝트 만들기를 선택하고 API 및 서비스 링크를 클릭하여 이동합니다.
2. 좌측의 OAuth 동의화면을 클릭한후 UserType을 외부로 선택한후 만들기 버튼을 클릭합니다.
3. 앱정보등 필수부분을 작성한뒤 저장버튼을 클릭합니다.
4. 좌측 사용자인증정보를 클릭한후 OAuth 클라이언트ID 사용자인증정보만들기를 클릭합니다.
5. 어플리케이션 유형 및 승인된 리디렉션 URI를 입력합니다. (ex: http://localhost:8080/login/oauth2/code/google)
6. 클라이언트ID 및 클라이언트 보안비밀번호가 생성됩니다.
1. 의존성추가 pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
</dependencies>
2. application-oauth.properties 등록
양식에 맞춰 google cloud에서 부여받은 클라이언트id및 보안비밀번호를 입력합니다.
# Google
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
3. OAuth2UserService
PrincipalOauth2UserService
UserInfo엔드포인트에서 사용자의 속성을 가져오고 OAuth2Usesr타입의 AuthenticatedPrincipal을 리턴하는 DefaultOAuth2UserService를 구현한 클래스입니다.
프로세스: 구글로그인버튼->구글로그인창->로그인완료->인증코드리턴(OAuth-Client라이브러리)->AccessToken요청
userRequest정보 -> loadUser 함수호출-> 구글로부터 회원프로필을 받아옵니다.
@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {
@Autowired
private UserRepository userRepository;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oauth2User = super.loadUser(userRequest);
System.out.println("getAttributes : " + oauth2User.getAttributes());
String provider = userRequest.getClientRegistration().getRegistrationId(); // Google
String providerId = oauth2User.getAttribute("sub"); // Google ProviderId Sub
String shortenedProviderId = providerId.length() > 5 ? providerId.substring(0, 5) : providerId; // providerId에서 길이 5만큼의 문자열을 추출
String username = provider+"_"+shortenedProviderId; // google_123124
String password = "1234"; // 크게 의미 없음.
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());
}
}
loadUser메소드
구글로 부터 받은 userRequest 데이터에 대한 후처리를 하는 함수입니다.
OAuth2User
getAttributes메소드를 통해 로그인한 사용자의 정보를 추출합니다.
마지막으로 인증된 사용자의 정보와 OAuth2 제공자로부터 받은 속성을 포함한 PrincipalDetails 객체를 반환합니다. 이 객체는 애플리케이션 내에서 사용자의 인증 정보를 관리하는 데 사용됩니다.
4. PrincipalDetails
PrincipalDetails클래스는 스프링시큐리티의 'UserDetails' 및 'OAuth2User'인터페이스를 구현하며 , 사용자의 인증정보를 관리하는 역할을 합니다.
public class PrincipalDetails implements UserDetails, OAuth2User {
private User user;
private Map<String,Object> attributes;
// 일반 로그인 생성자
public PrincipalDetails(User user){
this.user = user;
}
// OAuth 로그인 생성자
public PrincipalDetails(User user,Map<String,Object> attributes){
this.user = user;
this.attributes = attributes;
}
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
// 해당 User의 권한을 리턴하는곳.
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collect = new ArrayList<>();
collect.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole();
}
});
return collect;
}
// 소셜 로그인 여부를 확인하는 메소드
public boolean isSocial() {
return attributes != null && !attributes.isEmpty();
}
// User 의 password 리턴
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getName() {
return null;
}
}
getAuthorities
사용자의 권한을 return합니다. 이는 GrantedAuthority의 객체 컬렉션으로 사용자의 역할을 나타냅니다.
getAttributes
소셜로그인과정에서 제공된 사용자의 속성을 return합니다.
isSocial
사용자가 소셜로그인으로 인증되었는지 여부를 확인합니다. 사용자속성이 존재하면 소셜로그인으로 포함됩니다.
5.SecurityConfig 수정
@Override
protected void configure(HttpSecurity http) throws Exception {
.formLogin()
.loginPage("/login")
.successHandler(customAuthenticationSuccessHandler())
.failureHandler(userLoginFailHandler)
.and()
.logout()
.logoutUrl("/logout")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessHandler(customLogoutSuccessHandler())
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.clearAuthentication(true)
.permitAll()
.and()
.oauth2Login()
.loginPage("/login")
.userInfoEndpoint()
.userService(principalOauth2UserService);
}
oauth2관련 구문을 추가합니다.