ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링부트+JPA - 인스타그램 클론 코딩 14 - OAuth2 (페이스북 로그인)
    스프링/스프링부트+JPA - 인스타 2021. 10. 20. 13:25

    이지업 최주호 강사님 강의 참조

     

     

    01 OAuth2 진행을 위한 세팅

     

    • OAuth2 메이븐
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>

    → 혹은 처음 프로젝트 세팅시에 OAuth를 선택하면 된다. 

     

     

    • 페이스북 개발자 센터에서 API 받기 
    1. 로그인 후 앱만들기 클릭 → 앱 유형 선택에서 없음 선택 → 앱 이름 설정후 다음 → 'Facebook 로그인 설정 클릭'
    2. 웹 선택 → 사이트 URL 설정 (http://localhost:8080) → 모두 다음 누른 후 왼쪽편에 설정 기본 설정 클릭 → 앱ID/시크릿 코드를 이용하여 스프링부트에 등록 (YML파일에 등록)
      scop는 각 회사마다 다르므로 필히 따로 확인해서 적자
    3. 이렇게 등록을 하고나면 아래와 같은 오류가 뜨는데 시큐리티에서 설정을 좀 해줘야 한다
    4. SecurityConfig로 이동하여 아래 코드를 추가
      oauth2Login() → 내가 만든 form 로그인도 하는데 oauth2로그인도 사용 하겠다라는 의미
      userInfoEndpoint() → oauth2로 로그인시에 최종 응답을 회원정보로 바로 받도록 해줌
      userService() → 최종 응답을 받을 서비스 클래스를 넣어주면 된다.

    5. 최종 응답을 받을 서비스 세팅

      @Service
      public class OAuth2DetailsService extends DefaultOAuth2UserService{}​



    6.  다시 SecurityConfig로 돌아가 세팅 마무리

     

    • OAuth2DetailsService 의 역할
    1. 우리 플래폼에서 페이스북 로그인을 하고싶을때 '페이스북 로그인' 버튼을 누리고 아이디와 비번을 치게되면 페이스북으로 이 로그인요청이 우리플랫폼에서 페이스북으로 간다
    2. 이때 페이스북에서 요청에대한 인증을 하고 인증이 정상적으로 처리되면 응답을 하게 되는데 이때 이 응답을 처리하는곳이 OAuth2DetailsService
    3. OAuth2DetailsService가 복잡한 OAuth2의 개념을 알아서 처리를하고 우리는 OAuth2DetailsService으로 받은 회원 정보를 가지고 처리를 하면 된다. 

     

     

     

    02 OAuth2 의 진행 개념

    1. OAuth는 Open Auth의 약자로 인증을 오픈해준다는 말이다. 보통 어느 플랫폼에서 클라이언트가 회원가입을 하게되면 그 플랫폼의 회원가입을 타고 가입 하고 로그인 또한 하게 된다.
    2. 이 OAuth2를 써서 이 회원가입과 로그인처리를 타 플랫폼(페이스북/카카오톡/네이버 등)에게 양도를 하게되면 클라이언트는 자기가 쓰고있는 플랫폼을 통해 회원가입 및 로그인 처리를 한다
    3. 클라이언트가 회원가입 혹은 로그인을 타 플랫폼(페이스북)을 통해 요청을 하면 페이스북 플랫폼은 먼저 이 클라이언트가 자기 플랫폼에 가입이 되어있는지 확인을 한다
    4. 확인이 되면 정상 회원가입을 처리하게 된다. 그 이후 원플랫폼(P1)에서 페이스북으로 로그인 요청을 다시 하면 아이디와 비번을 먼저 확인하는게 아니라 페이스북이 들고 있는 앱 리스트에서 P1플랫폼이 등록이 되어 있는지 먼저 확인을 한다. 
    5. 이후 페이스북 DB에 이 클라이언트(ID/PW)가 등록되어있는지 확인을 한다. 확인이 되면 인증이 뜨게되고 이 인증 코드를 P1 플랫폼으로 돌려 준다
    6. 이제 P1플랫폼에서는 인증 코드를 받았지만 여전히 클라이언트의 정보가 필요하다. 그래서 인증 코드를 들고 P1은 페이스북에 ACCESS TOKEN을 요청하여 클라이언트의 정보에 접근 가능하도록 허락을 받는다. 이 정보가 위에서 세팅을한 SCOPE를 통해 넘어오는 정보들이다. 
    7. 받아온 정보들로 회원가입 및 로그인을 시키면 되는 것. OAuth2가 위의 6개의 과정을 담고있고 우리는 정보만 받아와서 회원가입/로그인만 하면 되는 엄청나게 편한 라이브러리!

     

     

     

     

    03 페이스북 로그인 

     

    • signin.jsp가서 페이스북 로그인 창 설정
      <!-- Oauth 소셜로그인 -->
      <div class="login__facebook">
      <button onclick="javascript:location.href='/oauth2/authorization/facebook'">
      <i class="fab fa-facebook-square"></i>
      <span>Facebook으로 로그인</span>
      </button>
      </div>
      <!-- Oauth 소셜로그인end -->​
      
      
      <button onclick="javascript:location.href='/oauth2/authorization/facebook'">
      주소의 경우는 스프링부트에서 지정해주는 방식이니 반드시 따라 해야 작동 한다

    굿!
    이미 로그인이 되어있는 사용자를 로그아웃하면 이런 화면으로 바뀐다

     

     

    • OAuth2DetailsService 마무리
      마무리하기전에 OAuth2DetailsService 가 받아오는 정보를 한번 확인하고 넘어가자
      @Service
      public class OAuth2DetailsService extends DefaultOAuth2UserService{
      	
      	@Override
      	public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
      		//System.out.println("OAuth2DetailsService의 loadUser 타나요????????????????");
      		OAuth2User oAuth2User = super.loadUser(userRequest);
      		System.out.println(oAuth2User.getAttributes());
      		return null;
      	}
      }​

    이런정보를 이렇게 쉽게 받아와주니 얼마나 편한가!

     

    • OAuth2DetailsService에 유저정보를 담아서 처리 하자
      @RequiredArgsConstructor
      @Service
      public class OAuth2DetailsService extends DefaultOAuth2UserService {
      
      	private final UserRepository userRepository;
      
      	@Override
      	public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
      		// System.out.println("OAuth2DetailsService의 loadUser 타나요????????????????");
      		OAuth2User oAuth2User = super.loadUser(userRequest);
      		// System.out.println(oAuth2User.getAttributes());
      
      		Map<String, Object> userInfo = oAuth2User.getAttributes();
      
      		String username = "facebook_" + (String) userInfo.get("id");
      		String password =  new BCryptPasswordEncoder().encode(UUID.randomUUID().toString());
      		String email = (String) userInfo.get("email");
      		String name = (String) userInfo.get("name");
      
      		User userEntity = userRepository.findByUsername(username);
      
      		if (userEntity == null) {
      			User user = User.builder()
      					.username(username)
      					.password(password)
      					.email(email)
      					.name(name)
      					.role("ROLE_USER")
      					.build();
      			return new PrincipalDetails(userRepository.save(user), oAuth2User.getAttributes());
      		} else {
      			return new PrincipalDetails(userEntity);
      		}
      	}
      }​
      - oAuth2User에서 넘어오는 정보들은 MAP형태로 오기때문에 이 정보를 쓰려면 MAP으로 받아야 한다
      - 회원가입을 시키기위해서 유저를 만들어야 DB에 넣을 수 있기때문에 넘어오는 정보를 바탕으로 우리 플래폼에서 썻던 signupDto에 맞게 똑같이 받도록 세팅한다
      - 그리고 회원가입을 무한으로 하면 안되기때문에 userEntity를 만들어 DB에서 비교하도록 세팅하고 없으면 DB에 새롭게 인서트, 있으면 회원가입이 안되도록 설정
      - 비밀번호 해시처리시에 따로 BCryptPasswordEncoder()를 전역변수로 놓으면 빈 사이클 에러가 걸리므로 지역변수 대신에 직접 패스워드를 딸때에만 쓰도록 해야 한다.
      - 마지막에 PrincipalDetails로 처리하는 과정은 다음과 같다

     

    • PrincipalDetails 세팅
      @Data
      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;
      	}
      	
      	public PrincipalDetails(User user, Map<String, Object> attributes) {
      		this.user = user;
      	}
      
      	@Override
      	public Collection<? extends GrantedAuthority> getAuthorities() {
      		Collection<GrantedAuthority> collector = new ArrayList<GrantedAuthority>();
      		collector.add(()->{return user.getRole();});
      		return collector;
      	}
      
      	@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 Map<String, Object> getAttributes() {
      		return attributes;
      	}
      
      	@Override
      	public String getName() {
      		// TODO Auto-generated method stub
      		return (String) attributes.get("name");
      	}
      }​
      - Implements를 UserDetails와 OAuth2User를 동시에 하여 둘다 PrincipalDetails가 둘 다 사용 가능하도록 한다
      - 우리 플랫폼에서 가입을 하게 되면 UserDetails에 담겨서 처리가 되어 '@AuthenticationPrincipal PrincipalDetails principalDetails' 로 우리 플랫폼 유저 정보를 꺼내 오게 되고 
      - 타 플랫폼 가입을 하면 OAuth2User에 담기게 되어서 타플랫폼이라도 자유자재로 '@AuthenticationPrincipal PrincipalDetails principalDetails' 로 유저정보들을 들고 올 수 있게 된다
      - 그러므로 마지막에 리턴을 'PrincipalDetails'로 할 수 있는 것!

     

     

    • 테스트
      테스트시에 User.java에 보면 username 길이가 20으로 제한되어있다는 오류가 뜬다 100으로 바꾸고 테스트

    굿!

     

    잘 뜬다!

     

     

    길고 긴 인스타그램 클론코딩이 끝났다!!!!

     

     

     

    ** 혹시나 나처럼 구독정보를 클릭했을때 아래와 같은 오류가 뜬다면 (구독정보가 빈칸으로 나옴) subscribeDto에서 subscribeState/equalUserState 의 타입을 BigInteger로 변경하면 해결 된다!!

     

Designed by Tistory.