부자 되기 위한 블로그, 머니킹

안녕하세요 오늘도 열심히 프로젝트를 개발 중에 있습니다. 최근 매번 개인 프로젝트를 개발할 때마다 인증구현을 해줘야함에 번거로움을 느껴 통합인증(SSO) 시스템을 만드려고 합니다. 이에 먼저 Spring Security를 공부하고자 하는 마음으로 해당 내용을 정리해보았습니다.

 

 

스프링 시큐리티 개념

Filter단 실행

Spring Security는 Filter단에서 실행되며 DelegatingFilterProxy단에서 일어나 Spring 프레임워크에서 해당 인증 구현을 할 수 있게 한다. (FilterChain)

Tomcat 인증 session id

JSESSIONID를 톰캣에서 발급해주는데 Spring Security에서 로그인 성공시 이 JSESSONID에 로그인 정보를 담게 된다.

 

Spring Security ( 인프런 강좌 정리 )

1강 스프링 시큐리티 기본 환경 설정

  • spring boot devtools, lombok, jpa, mysql driver, spring security, mustache, spring web의 dependency 등록

 

-> 머스태치

  • 머스태치는 수많은 언어를 지원하는 심플한 템플릿 엔진으로 @Configuration 어노테이션과WebMvcConfigurer 상속으로 설정할 수 있다.
  • MustacheViewResolver 클래스 객체를 인스턴스로 생성하여서 header 부분을 설정할 수 있다.

 

2강 시큐리티 설정

import java.util.Iterator;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.cos.securityex01.config.auth.PrincipalDetails;
import com.cos.securityex01.model.User;
import com.cos.securityex01.repository.UserRepository;

@Controller
public class IndexController {

	@Autowired
	private UserRepository userRepository;

	@Autowired
	private BCryptPasswordEncoder bCryptPasswordEncoder;

	@GetMapping({ "", "/" })
	public @ResponseBody String index() {
		return "인덱스 페이지입니다.";
	}

	@GetMapping("/user")
	public @ResponseBody String user(@AuthenticationPrincipal PrincipalDetails principal) {
		System.out.println("Principal : " + principal);
		System.out.println("OAuth2 : "+principal.getUser().getProvider());
		// iterator 순차 출력 해보기
		Iterator<? extends GrantedAuthority> iter = principal.getAuthorities().iterator();
		while (iter.hasNext()) {
			GrantedAuthority auth = iter.next();
			System.out.println(auth.getAuthority());
		}

		return "유저 페이지입니다.";
	}

	@GetMapping("/admin")
	public @ResponseBody String admin() {
		return "어드민 페이지입니다.";
	}
	
	//@PostAuthorize("hasRole('ROLE_MANAGER')")
	//@PreAuthorize("hasRole('ROLE_MANAGER')")
	@Secured("ROLE_MANAGER")
	@GetMapping("/manager")
	public @ResponseBody String manager() {
		return "매니저 페이지입니다.";
	}

	@GetMapping("/login")
	public String login() {
		return "login";
	}

	@GetMapping("/join")
	public String join() {
		return "join";
	}

	@PostMapping("/joinProc")
	public String joinProc(User user) {
		System.out.println("회원가입 진행 : " + user);
		String rawPassword = user.getPassword();
		String encPassword = bCryptPasswordEncoder.encode(rawPassword);
		user.setPassword(encPassword);
		user.setRole("ROLE_USER");
		userRepository.save(user);
		return "redirect:/";
	}
}
  • Path Router 설정 (Getmapping PostMapping)
  • Router에 @ResponseBody 붙이면 rest api 형식으로 반환된다.
  • /login라우터 설정해도 spring security에서 기본 로그인 프로세스를 /login로 잡기 떄문에 (filter 단에서 spring security가 일어난다) 정상적으로 페이지가 나오지 않는다.

 

spring 필터 체인 등록

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import com.cos.securityex01.config.oauth.PrincipalOauth2UserService;

@Configuration // IoC 빈(bean)을 등록
@EnableWebSecurity // 필터 체인 관리 시작 어노테이션
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) // 특정 주소 접근시 권한 및 인증을 위한 어노테이션 활성화
public class SecurityConfig extends WebSecurityConfigurerAdapter{
   
   @Autowired
   private PrincipalOauth2UserService principalOauth2UserService;
   
   @Bean
   public BCryptPasswordEncoder encodePwd() {
      return new BCryptPasswordEncoder();
   }
   
   @Override
   protected void configure(HttpSecurity http) throws Exception {
      
      http.csrf().disable();
      http.authorizeRequests()
         .antMatchers("/user/**").authenticated()
         //.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')")
         //.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN') and hasRole('ROLE_USER')")
         .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
         .anyRequest().permitAll()
      .and()
         .formLogin()
         .loginPage("/login")
         .loginProcessingUrl("/loginProc")
         .defaultSuccessUrl("/")
      .and()
         .oauth2Login()
         .loginPage("/login")
         .userInfoEndpoint()
         .userService(principalOauth2UserService);
   }
}
  • SecurityConfig 클래스 만든다.
  • @EnableWebSecurity 어노테이션 작성 : 스프링 시큐리티 필터가 스프링 필터 체인에 등록된다.
  • WebSecurityConfigurerAdapter 를 상속 받는다.
  • configure 메소드 오버라이딩 한다. (여기서 기본적인 페이지 및 보안 설정 가능)
    • antMatcher 통해 페이지 접근, hasRole 통애 인증자의 권한 확인, /user/** 시에 모든 해당 경로 하위 패스는 hasRole(’user’)을 통해 user 권한을 가진 사람들만 접속 가능
  • and().formLogin().loginPage()를 통해 실제 로그인 웹 페이지를 커스텀 구성할 수 있다.

 

 

3강 시큐리티 회원가입

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 페이지</title>
</head>
<body>
<h1>로그인 페이지</h1>
<hr/>
<!-- 시큐리티는 x-www-form-url-encoded 타입만 인식 -->
<form action="/loginProc" method="post">
   <input type="text" name="username" />
   <input type="password" name="password" />
   <button>로그인</button>
</form>

<br />

   <h1>Social Login</h1>
   <br />
   <!-- javascript:; 는 클릭해도 반응을 없게 하는 키워드 -->
   <a href="/oauth2/authorization/google" > 
   <img src="https://pngimage.net/wp-content/uploads/2018/06/google-login-button-png-1.png"
      alt="google" width="357px" height="117px">
   </a>
   <br />
   <a href="/oauth2/authorization/facebook"> 
   <img src="https://pngimage.net/wp-content/uploads/2018/06/login-with-facebook-button-png-transparent-1.png"
      alt="facebook" width="357px" height="117px">
   </a>
   <br />
   
   <a href="/oauth2/authorization/naver"> 
   <img src="https://blogfiles.pstatic.net/MjAyMDA4MDRfMzMg/MDAxNTk2NTEyOTY4MDMx.vhXHCulffijGUnvlaBR2jW4__Lkz8R3ZTaEDcTeNV2gg.Wt_HNl_zktUJUMrYGkVmqJ-PhxKv_s4A7gG1uPKMZaQg.PNG.getinthere/naver_button.png"
      alt="naver" width="357px" height="50px">
   </a>
   <br />
</body>
</html>
  • 먼저 로그인 페이지에서 아이디 부분은 name이 기본 username으로 설정해야 한다. (바꾸려면 따로 세팅을 해주어야 한다.) password 또한 name 부분은 password로 되야 한다. (커스텀 로그인 페이지 구성 시에)
  • 이는 회원가입 페이지도 마찬가지이다. 하지만 회원가입 페이지는 추가 column 구성이 가능하다. (주소나 이메일 등)
  • 회원가입시 보통 password 부분은 Encode, decode 함수를 구현하여 암호화 시킨다. 인느 패스워드 부분을 암호화 안한다면 시큐리티로 로그인을 진행시킬 수 없다.
  • 암호화 부분은 SecurityConfig 부분에 추가한다. 해당 함수 부분은 빈 등록하여 인스턴스 하나를 반환한다. (보통 Encode시에는 bCryptPasswordEncoder로 사용함)

 

4강 시큐리티 로그인

  • loginProcessingUrl로 path 등록시에 login 주소가 호출되면서 시큐리티를 낚아채서 대신 로그인을 진행해준다.
  • defaultSuccessUrl은 로그인 성공시 따로 없으면 이동한다. 이전 url이 있을시에는 이전 Url로 이동한다.

 

시큐리티 로그인 과정

  • 시큐리티가 loginProcessingUrl로 등록된 Path로 요청을 낚아채서 로그인을 진행시킨다.
  • 로그인 진행이 완료되면 시큐리티 session을 만들어줍니다. (Security ContextHolder에 세션 정보 저장)
  • Security Session 정보 오브젝트가 정해져있다 → Authentication 타입 객체
  • Authentication 안에는 User 정보가 있어야 되는데 User 오브젝트 타입인 UserDetails타입 객체이다.
  • SecuritySession에 들어갈 수 있는 것은 Authentication 타입이고 여기에 들어갈 수 있는 객체는 UserDetails 타입이다.
  • UserDetails 커스텀 시에는 이 인터페이스를 상속받으면 된다.

 

UserDetils 메소드

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;

import com.cos.securityex01.model.User;

import lombok.Data;

// Authentication 객체에 저장할 수 있는 유일한 타입
public class PrincipalDetails implements UserDetails, OAuth2User{

   private static final long serialVersionUID = 1L;
   private User user;
   private Map<String, Object> attributes;

   // 일반 시큐리티 로그인시 사용
   public PrincipalDetails(User user) {
      this.user = user;
   }
   
   // OAuth2.0 로그인시 사용
   public PrincipalDetails(User user, Map<String, Object> attributes) {
      this.user = user;
      this.attributes = attributes;
   }
   
   public User getUser() {
      return user;
   }

   @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 Collection<? extends GrantedAuthority> getAuthorities() {
      Collection<GrantedAuthority> collet = new ArrayList<GrantedAuthority>();
      collet.add(()->{ return user.getRole();});
      return collet;
   }

   // 리소스 서버로 부터 받는 회원정보
   @Override
   public Map<String, Object> getAttributes() {
      return attributes;
   }

   // User의 PrimaryKey
   @Override
   public String getName() {
      return user.getId()+"";
   }
   
}
  • getAuthorities 메소드는 해당 User의 권한을 리턴하는 함수이다.
    • Collection 타입으로 반환해야 한다.
    • collect.add(new GrantedAuthority(){@override public String getAuthority(){return user.getRole()}} 형태로 반환하면 된다.
  • getPassword()는 패스워드 반환 부분
  • getUsername은 username 반환하는 부분
  • isAccountNonExpired()는 만료 기간에 대한 설정
  • isAccountNotLocked() 계정 잠금 기능
  • isCredentialsNonExpired() 계정 만든지 오래 되었나.
  • isEnabled 계정을 활성화 시킬 것인지
    • 우리 사이트에서 1년동안 회원이 로그인을 안하면 휴먼 계정으로 하겠다 할 때 설정
    • 이럴려면 도메인에 private Timestamp loginDate, private Timestamp createDate를 설정해야함

 

UserDetailsService

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.cos.securityex01.model.User;
import com.cos.securityex01.repository.UserRepository;

@Service
public class PrincipalDetailsService implements UserDetailsService{

   @Autowired
   private UserRepository userRepository;
   
   @Override
   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      User user = userRepository.findByUsername(username);
      if(user == null) {
         return null;
      }else {
         return new PrincipalDetails(user);
      }
      
   }

}
  1. 시큐리티 설정에서 loginProcessingUrl에서 등록된 Path 요청이 오면 자동으로 UserDetailsService 타입으로 ioc 되어 있는 loadUserByUsername 함수를 실행한다. (제어의 역전)
  2. 이런 구조화적 문제 떄문에 로그인 페이지의 아이디는 username으로 고정되어야 하는데 이를 커스텀 하려면 SecurityConfigure 클래스 configure에 usernameParameter옵션을 줘서 설정을 해줘야 한다.
  3. 이떄 return 타입은 UserDetails 타입을 가져야 한다. 이렇게 반환된 UserDetails는 Autentication 내부 UserDetails 에 들어가고 Authentication은 Security session 내부의 Authentication으로 등록된다.
  4. default 로그아웃 주소는 /logout 이다.

 

5강 시큐리티 권한처리

  1. SecurityConfig에 EnableGlobalMethodSecurity 어노테이션을 주고 securedEnable=true 설정을 하게 되면 securedEnable 어노테이션을 사용하여 인가 처리를 할 수 있다. (라우터 부분(GetMapping) 부분에)Secured 어노테이션 등록 형식으로 인가 처리 활성화)
  2. prePostEnable을 true로 설정시에는 preAuthorize 어노테이션을 활성화 시킨다. 이전 Secured 어노테이션과의 둘다 라우터 부분 실행 전에 권한 처리를 체크하지만 spEL(스프링 표현언어)을 지원하냐 안하냐의 차이가 있다. (권한 미달성 시에는 라우터 메소드 자체를 실행하지 않는다.)
  3. postAuthroize는 해당 메소드 처리 이후에 권한 체크를 한다.
  4. Global Security 설정은 하위 도메인이 아닌 특정 도메인 path에 권한 처리를 하고 싶으면 걸면된다.

 

6강 구글 로그인 준비

server:
  port: 8080
  servlet:
    context-path: /
    encoding:
      charset: UTF-8
      enabled: true
      force: true
      
spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: [db url]
    username: [db id]
    password: [db password]
    
  mvc:
    view:
      prefix: /templates/
      suffix: .mustache

  jpa:
    hibernate:
      ddl-auto: update #create update none
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    show-sql: true
    
  security:
    oauth2:
      client:
        registration:
          google: # /oauth2/authorization/google 이 주소를 동작하게 한다.
            client-id: [client id]
            client-secret: [client password]
            scope:
            - email
            - profile
            
          facebook:
            client-id: [client id]
            client-secret: [client password]
            scope:
            - email
            - public_profile
          
          # 네이버는 OAuth2.0 공식 지원대상이 아니라서 provider 설정이 필요하다.
          # 요청주소도 다르고, 응답 데이터도 다르기 때문이다.
          naver:
            client-id: [client id]
            client-secret: [client password]
            scope:
            - name
            - email
            - profile_image
            client-name: Naver # 클라이언트 네임은 구글 페이스북도 대문자로 시작하더라.
            authorization-grant-type: authorization_code
            redirect-uri: http://localhost:8080/login/oauth2/code/naver

        provider:
          naver:
            authorization-uri: https://nid.naver.com/oauth2.0/authorize
            token-uri: https://nid.naver.com/oauth2.0/token
            user-info-uri: https://openapi.naver.com/v1/nid/me
            user-name-attribute: response # 회원정보를 json의 response 키값으로 리턴해줌.

 

  1. 구글 로그인을 하려면 google cloud platform 에 앱을 생성하고 oauth 동의 화면에서 설정을 하여 oauth 생성해야한다.
  2. 사용자 인증정보로 가서 인증 정보를 만들고 웹 애플리케이션 유형으로 선택하고 oauth client id, password를 발급받는다.
  3. spring security에 oauth2 client 를 dependency에 추가한다. (maven repository 사이트) (Intellij 프로젝트 여러개 추가)
  4. application.properties에 google client id 및 password 등록
  5. SecurityConfigure에 auth2Login() 메소드 등록
  6. gradle 혹은 pom.xml 부분에 oauth2 dependency 부분 추가

 

7강 구글 회원 프로필 정보 받아보기

  1. 구글 로그인이 완료된 후에 후처리가 필요하다.
  2. DefaultOauth2UserSevice를 상속받는 클래스 만들기 (service class)
  3. PrincipalOauth2UserService를 autowired로 받기
  4. 엑세스 토큰과 사용자 프로필 정보를 한번에 받을 수 있는데 이 때 UserInfo 엔드 포인트를 설정할 수 있는 옵션이 있으며 이는 userInfoEndpoint()로 등록
    1. 사용자 권한 매핑
    2. 커스텀 OAuth2User 설정
    3. OAuth 2.0 UserService
    4. OpenId connect 1.0 UserService
    5. OAuth2
  5. SecurityConfigure에서 oauth2Login 부분에 해당 객체 userService 메소드로 등록시켜주기
  6. DefaultOAuth2UserService의 loadUser 메소드를 통해 code를 통해 구상한 정보나 token을 통해 응답받은 회원 정보를 받을 수 있다. (구글로부터 받은 userRequest 데이터에 대한 후처리 함수)
  7. processOAuth2User 메소드 (커스텀 생성)를 만들어서 기존 OAuth2User 타입의 인증 객체 형태를 UserDetails 객테 타입으로 변형한다.
  8. 받는 userRequest 부분에서 getAttribute 정보를 바탕으로 강제 회원가입을 시키는 방식으로 sns oauth2 회원가입 프로세스를 진행한다.
  9.  
import java.util.Map;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
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 com.cos.securityex01.config.auth.PrincipalDetails;
import com.cos.securityex01.config.oauth.provider.FaceBookUserInfo;
import com.cos.securityex01.config.oauth.provider.GoogleUserInfo;
import com.cos.securityex01.config.oauth.provider.NaverUserInfo;
import com.cos.securityex01.config.oauth.provider.OAuth2UserInfo;
import com.cos.securityex01.model.User;
import com.cos.securityex01.repository.UserRepository;

@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {

   @Autowired
   private UserRepository userRepository;

   // userRequest 는 code를 받아서 accessToken을 응답 받은 객체
   @Override
   public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
      OAuth2User oAuth2User = super.loadUser(userRequest); // google의 회원 프로필 조회

      // code를 통해 구성한 정보
      System.out.println("userRequest clientRegistration : " + userRequest.getClientRegistration());
      // user의 accesstoken
      //System.out.println("userRequest = " + userRequest.getAccessToken());
      // 받은 실제 유저 정보 attribute
      //System.out.println("super.loadUser(userRequest).getAttributes() = " + super.loadUser(userRequest).getAttributes());
      // token을 통해 응답받은 회원정보
      System.out.println("oAuth2User : " + oAuth2User);
   
      return processOAuth2User(userRequest, oAuth2User);
   }

   private OAuth2User processOAuth2User(OAuth2UserRequest userRequest, OAuth2User oAuth2User) {

      // Attribute를 파싱해서 공통 객체로 묶는다. 관리가 편함.
      OAuth2UserInfo oAuth2UserInfo = null;
      if (userRequest.getClientRegistration().getRegistrationId().equals("google")) {
         System.out.println("구글 로그인 요청~~");
         oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes());
      } else if (userRequest.getClientRegistration().getRegistrationId().equals("facebook")) {
         System.out.println("페이스북 로그인 요청~~");
         oAuth2UserInfo = new FaceBookUserInfo(oAuth2User.getAttributes());
      } else if (userRequest.getClientRegistration().getRegistrationId().equals("naver")){
         System.out.println("네이버 로그인 요청~~");
         oAuth2UserInfo = new NaverUserInfo((Map)oAuth2User.getAttributes().get("response"));
      } else {
         System.out.println("우리는 구글과 페이스북만 지원해요 ㅎㅎ");
      }

      //System.out.println("oAuth2UserInfo.getProvider() : " + oAuth2UserInfo.getProvider());
      //System.out.println("oAuth2UserInfo.getProviderId() : " + oAuth2UserInfo.getProviderId());
      Optional<User> userOptional = 
            userRepository.findByProviderAndProviderId(oAuth2UserInfo.getProvider(), oAuth2UserInfo.getProviderId());
      
      User user;
      if (userOptional.isPresent()) {
         user = userOptional.get();
         // user가 존재하면 update 해주기
         user.setEmail(oAuth2UserInfo.getEmail());
         userRepository.save(user);
      } else {
         // user의 패스워드가 null이기 때문에 OAuth 유저는 일반적인 로그인을 할 수 없음.
         user = User.builder()
               .username(oAuth2UserInfo.getProvider() + "_" + oAuth2UserInfo.getProviderId())
               .email(oAuth2UserInfo.getEmail())
               .role("ROLE_USER")
               .provider(oAuth2UserInfo.getProvider())
               .providerId(oAuth2UserInfo.getProviderId())
               .build();
         userRepository.save(user);
      }

      return new PrincipalDetails(user, oAuth2User.getAttributes());
   }
}

 

 

Oauth 방식

  1. 타입
    1. 코드 받기(인증 됬다는 것)
    2. 엑세스 토큰(권한)
    3. 사용자 프로필 정보 가져옴
    4. 그 정보를 토대로 회원가입을 자동으로 진행시키기도 함
  2. 코드를 받지 않으면
    1. 엑세스 토큰+사용자 프로필 정보를 한번에 받는다 (spring oauth2 client dependency 특징)

 

8강 Authentication 객체가 가질 수 있는 2가지 타입

  • 구글 로그인 버튼 클릭 → 구글 로그인 창
  • 로그인을 완료 → cod(oauth-client 라이브러리)를 리턴 → AccessToken 요청
  • userRequest 정보 → 회원 프로필을 받아야함(loadUser함수 호출) → 회원 프로필 받아줌

Authentication 객체 타입 변환 (UserDetails 타입으로 )

  • 일반 로그인시 Authentication.getPrincipal()은 Object 타입 반환
  • 이 객체 앞에 (UserDetails)를 넣어 타입 변환시 UserDetails 타입으로 반환
  • Authentication Parameter 앞에 @AuthenticationPrincipal 어노테이션을 추가하면 자동으로 UserDetails 타입으로 변환해줌
  • @AuthenticationPrincipal 어노테이션은 OAuth2User객체 타입또한 다운캐스팅을 지원한다.

스프링 시큐리티 로그인 유저 정보 구조

  • Spring Security는 Security session을 가지고 있다.
  • Security Session에 들어갈 수 있는 객체는 오직 Authentication 객체 밖에 없다. 이 객체는 Controller에서 DI를 할 수 있다.
  • Authentication에 들어갈 수 있는 타입은 UserDetails과 OAuth2User 타입이 있다.
  • UserDetails 타입은 일반 로그인 시에, OAuth2User 타입은 OAuth 로그인시에 들어간다.
  • 필요할 때 로그인 유저 정보를 꺼내써야 하는데 통합되지 않아 불편한 점이 있다.
  • 이를 위해 커스텀 class를 만들어서 UserDetails와 OAuth2User를 implements를 받아서 구현하면 Controller에서 통합된 채로 사용할 수 있다.

oauth 로그인 판별 과정

  • registrationId로 어떤 oatuh로 로그인 했는지 확인

 

9강 구글 로그인 및 자동 회원가입 진행 완료

  1. loadUser 부분에 회원가입 관련 메소드를 추가한다.
  2. 해당 User의 Provider(공급 제공자, google인지 facebook인지 등)을 확인한다.
  3. 해당 User가 DB에 존재하는지 provider와 providerId로 확인한다.
    1. 해당 유저가 있으면 email만 업데이트
    2. 해당 유저 없으면 provider로부터 받은 정보를 토대로 db에 등록 진행
  4. User 정보를 가진 UserDetails 객체 반환
  • domain class에 default constructor 만들어야지 오류 없음

 

10강 페이스북 로그인 완료

  1. 페이스북 개발자 페이지 들어가서 앱 만들기
  2. 로그인 정보 클릭 후 도메인 등록 및 기본 설정 완료하고 앱 ID 및 시크릿 코드 복붙
  3. /oauth2/authorization/facebook으로 로그인 버튼 만들기
  4. 구글과 페이스북은 고유값 반환이 sub, id 라는 점에서 다르다.
  5. provider 별로 다른 info를 제공하기 때문에 각 정보에 맞는 객체를 만들고 통합해야한다.
  6. 각 Provider class 객체 변수에 attribute 값을 맞게 반환하여 저장한다.

 

11강 네이버 로그인 완료

스프링 시큐리티 oauth 방식이 여러개 있다.

(code Grant Type으로 구현함)

  1. 네이버 앱 등록 및 콜백 path 수동 등록해줘야함
  2. 네이버는 Provider가 아니기 때문에 (oauth2-client에 provider 등록 안됨) application.properties에 수동으로 provider 등록을 해줘야 함