[ Security ] 03. 시큐리티 소셜 로그인 처리 - Google oauth2
시큐리티 소셜 로그인 처리 - Google oauth2
사용자가 간편하게 로그인 할 수 있도록 설정해주는 소셜 로그인 기능을 추가해보려고한다.
먼저 google api 서비스를 이용해서 프로젝트를 등록하고 사용자 고유 아이디와 비밀번호를 받아야한다.
01 ) Google api 신청
Google 클라우드 플랫폼
로그인 Google 클라우드 플랫폼으로 이동
accounts.google.com
1. 프로젝트 등록
2. OAuth 클라이언트 아이디 만들기
- 'OAuth 클라이언트 ID' 를 선택 시 먼저 OAuth 동의를 해주어야한다.
- 동의 후 다시 'OAuth 클라이언트 ID' 선택
- 애플리케이션 유형 : 웹 애플리케이션
- 승인된 리다이랙션 URL : http://localhost:8089/login/oauth2/code/google
중요 한 위의 두가지를 입력하면 구글 API 서비스에서의 설정은 끝난다.
생성 후 출력되는 클라이언트 아이디와 보안 비밀번호는 Code에 추가해야하기 때문에 따로 저장해두는 것이 좋다.
02 ) 프로젝트 내 설정
1. 빌드설정
프로젝트에 소셜 로그인을 적용하기 위해 build.gradle 에 'spring-boot-starter-oauth2-client'를 추가해주어야한다.
📑 build.gradle
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'//구글소셜로그인
2. Properties 설정
기존 applicaiton.properties 파일에 추가해도 되고,
따로 관리하는 properties 파일을 생성하여 내용을 추가해주어도 된다.
내부에 들어가는 내용은 구글에서 발급받은 OAuth 클라이언트 ID/PW이다.
📑 applicaiton-oauth.properties
spring.security.oauth2.client.registration.google.client-id=631790768947-l20nog5jnte2i3m6qf13p1fd63ioum23.apps.googleusercontent.com
spring.security.oauth2.client.registration.google.client-secret=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
spring.security.oauth2.client.registration.google.scope=email
위 처럼 파일을 따로 생성해주었을 경우 기존 파일에도 추가된 파일이 동작 할 수 있도록 설정을 해주어야한다.
📑 applicaiton.properties
spring.profiles.include=oauth
03 ) 로그인 화면에 소셜로그인 버튼 생성
📑 SecurityConfig
SecurityConfig파일에 oauth2Login을 추가해주면 된다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
...
http.oauth2Login(); //소셜 로그인 - 구글 추가
return http.build();
}
04 ) 소셜 로그인 로직
- 회원 Service : 소셜 로그인 후, 프로젝트 내 회원처리와 성공 결과 정보를 반환 해주는 서비스 클래스
- 회원 DTO : 소셜 로그인 시 반환되는 객체타입이 기존 회원의 타입과 다르기 때문에 일치시켜주어야한다.
OAuth2User · OAuth2UserService · DefaultOAuth2UserService
소셜로그인으로 로그인한 사용자 정보는 OAuth2User 타입 객체로 출력된다.
-
OAuth2UserService는 일반 로그인에서 사용되었던,
UserDetailsService의 OAuth 버전이다.
-
DefaultOAuth2UserService은 OAuth2UserService를 상속받아 구현한 클래스로
원하는 기능을 편리하게 사용이 가능하다.
📑ClubOAuth2UserDetailService
: 소셜 로그인 후, 프로젝트 내 회원처리와 성공 결과 정보를 반환 해주는 서비스 클래스
1. DefaultOAuth2UserService 상속
@Service
public class ClubOAuth2UserDetailService extends DefaultOAuth2UserService {
...
}
2. 회원 처리를 하기 위해, loadUser 메서드 오버라이드
- 회원가입 처리를 위한 repository final 선언
- 받아오는 값 userRequest 에 들어 있는 속성 값은 구글 프로젝트 생성시 설정한 'API 설정 범위' 와 연관이 있다.
- Map<String, Object> 형태로 사용할 수 있다.
- 범위 값 : sub,pricture,email,email_verified
package com.example.springsecurity.security.service;
import com.example.springsecurity.entity.ClubMember;
import com.example.springsecurity.entity.ClubMemberRole;
import com.example.springsecurity.repository.ClubMemberRepository;
import com.example.springsecurity.security.dto.ClubAuthMemberDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import java.util.Optional;
import java.util.stream.Collectors;
@Log4j2
@Service
@RequiredArgsConstructor
public class ClubOAuth2UserDetailService extends DefaultOAuth2UserService {
private final ClubMemberRepository repository;
private final PasswordEncoder passwordEncoder;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
log.info("oauth2 login--------------------");
log.info("userRequest : {}" , userRequest);
String clientName = userRequest.getClientRegistration().getClientName();
log.info("clientName : {}",clientName);
log.info("clientName : {}",userRequest.getAdditionalParameters());
OAuth2User oAuth2User = super.loadUser(userRequest);
log.info("=======================================");
log.info("어떤 값을 가지고 있는지 출력 ");
oAuth2User.getAttributes().forEach((k,v)->{
log.info(k+":"+v);
//sub,picture, email,email_verified 정보가 추출된다.
});
log.info("=======================================");
// 이메일 추출
String email = null;
//구글 로그인일 경우
if (clientName.equals("Google")){
email = oAuth2User.getAttribute("email");
}
log.info("email : {}",email);
//회원가입 기능
ClubMember member = saveSocialMember(email);
//가입 또는 로그인 완료된 회원 정보 출력
ClubAuthMemberDTO clubAuthMemberDTO = new ClubAuthMemberDTO(
member.getEmail(),
member.getPassword(),
true,
member.getRoleSet().stream().map(
role -> new SimpleGrantedAuthority("ROLE_"+role.name())
).collect(Collectors.toSet()),
oAuth2User.getAttributes()
);
clubAuthMemberDTO.setName(member.getName());
return clubAuthMemberDTO;
}
// 회원가입 로직
private ClubMember saveSocialMember(String email) {
// 이미 있는 회원인지 확인
Optional<ClubMember> result = repository.findByEmail(email, true);
// 이미 존재한다면
if (result.isPresent()){
return result.get();
}
// 존재하지 않는 회원이라면, 패스워드는 기본 1111, social은 true로 설정하여 회원가입
ClubMember clubMember = ClubMember.builder().email(email)
.name(email)
.password("1111")
.fromSocial(true)
.build();
clubMember.addMemberRole(ClubMemberRole.USER); // 권한 기본 User
ClubMember save = repository.save(clubMember);
return save;
}
}
📑ClubAuthMemberDTO
: 소셜 로그인 시 반환되는 객체타입이 기존 회원의 타입과 다르기 때문에 일치
3. 소셜가입 정보 내용 추가
- OAuth2User 인터페이스를 상속
public class ClubAuthMemberDTO extends User implements OAuth2User {
- OAuth2User 에서 정보를 가져오는 getAttributes()메서드 오버라이드
//OAuth2User 정보
@Override
public Map<String, Object> getAttributes() {
return this.attr;
}
- OAuth2User 정보를 받아주는 필드 추가
private Map<String,Object> attr;
- OAuth2User 정보가 포함된 생성자 추가
//OAuth2User 정보를 바꿔주는 생성자
public ClubAuthMemberDTO(
String username,
String password,
Boolean fromSocial,
Collection<? extends GrantedAuthority> authorities,
Map<String,Object> attr //OAuth2User 정보
) {
this(username,password,fromSocial,authorities); // 기존생성자
this.attr = attr; //OAuth2User 정보
}
05 ) 소셜 로그인 성공 후 페이지 이동
📑ClubLoginSuccessHandler
1. AuthenticationSuccessHandler를 상속 받아 성공 후 로직 추가
- DefaultRedirectStrategy: 소셜 로그인시 다른 링크로 이동시키기 위한 객체로 내부적으로 sendRedirect를 통해 화면을 전환한다.
package com.example.springsecurity.security.handler;
...
@Log4j2
public class ClubLoginSuccessHandler implements AuthenticationSuccessHandler {
// 소셜 로그인시에는 다른 링크로 보내주기위해 사용.
DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
//login 성공 후 로직
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication)
throws IOException, ServletException {
log.info("----------------------------");
log.info("소셜 로그인 성공");
ClubAuthMemberDTO authMemberDTO = (ClubAuthMemberDTO)authentication.getPrincipal();
boolean fromSocial = authMemberDTO.getFromSocial();
//boolean passwordResult = passwordEncoder.matches("1111", authMemberDTO.getPassword());
boolean passwordEquals = authMemberDTO.getPassword().equals("1111");
log.info(authMemberDTO.getPassword());
log.info("Need Modify Member ? {} ", fromSocial);
log.info("passwordResult ? {} ", passwordEquals);
if (fromSocial && passwordEquals){
log.info("소셜확인 - 회원정보 변경 가능 페이지로 이동");
redirectStrategy.sendRedirect(request,response,"/sample/modify?from=social");
}
}
}
📑SampleController
2. 회원정보 수정페이지 이동
...
@PreAuthorize("hasRole('USER')")
@GetMapping("/modify")
public void memberModify(){
log.info("USER page");
}