Spring Security와 OAuth2를 이용한 소셜 로그인

구본식·2022년 7월 27일
10

1. Spring Security란?

Spring 기반의 어플리케이션의 인증과 권한 부여을 담당하는 스프링 프레임워크 하위 프레임워크이다.

Spring Security는 "인증체크", "권한부여"에 대한 부분을 Filter 의 흐름으로 처리한다.

2. Spring Security와 OAuth2 client library의 과정

test

2.1 Spring Security 동작 방식

  1. 사용자가 로그인 정보와 함께 요청을 보낸다.
  2. AuthenticationFilter가 해당 요청을 가로채고 ,가로챈 정보를 통해 UsernamePasswordAuthenticationToken의 인증용 객체를 생성한다.
  3. AuthenticationManager의 구현체인 ProviderManager에게 생성한 UsernamePasswordToken 객체를 전달한다.
  4. AuthenticationManager는 등록된 AuthenticationProvider(들)을 조회하여 인증을 요구한다.
  5. UserDetailsService를 호출하고 사용자 정보를 넘겨준다.
  6. 넘겨받은 사용자 정보를 통해 UserDetails 객체를 만든다
  7. AuthenticationProvider(들)은 UserDetails를 넘겨 받고 사용자 정보와 비교한다.
  8. 인증이 완료되면 권한등을 포함하여(UserDetails) Authentication 객체를 반환한다.
  9. 다시 최초의 AuthenticationFilter에 Authentication 객체가 반환된다.
  10. Authentication 객체를 SecurityContext 세션영역에 저장한다.

2.2 Spring Security OAuth2 동작 방식

OAuth 프로토콜의 동작 방식?
test

  1. 사용자가 접근 및 이용을 시도한다.
  2. 서비스(Cilent)는 플랫폼(카카오,네이버등)의 Authoriztion Sever로 client-id를 사용하여 로그인 페이지를 요청
  3. 로그인 페이지에 입력한 아이디, 패스워드를 통해 Authoriztion Sever로 Authoriztion code를 발급받는다.
    (각 플랫폼에 사용자가 입력한 redirect uri로)
  4. Authoriztion code 통해 Authoriztion Sever로 사용자 정보를 받아오기 위한 토큰인 Access Token을 받는다.
  5. Access Token을 사용하여 필요한 사용자 정보를 받아온다.

temp
기본적인 동작 방식은 Spring Scurity와 동일 위의 순서에서의 차이점은

5. UserDetailsService를 호출하고 사용자 정보를 넘겨준다
=> DefaultOAuth2UserService 타입의 객체에게 전달해준다.(loadUser 메소드 호출)

6. 넘겨받은 사용자 정보를 통해 UserDetails 객체를 만든다
=> OAuth2User 타입의 객체를 만든다.

Authentication객체에는 "UserDatails" 타입의 객체와 "OAuth2User" 타입의 객체가 들어갈수 있다.

3. OAuth2 client library를 이용한 Soical Login

OAuth2 client library란?

  • OAuth 프로토콜을 사용하는(카카오톡, 구글, 네이버, 페이스북 등) 방식를 편리하게 사용할수 있게 기능을 제공해주는 라이브러리
  • OAuth2 Client library가 Spring Security OAuth2 프로토콜을 이용할때 Provider(들)을 기본적으로 제공해어서 편리하게 도와준다.(google, facebook, github등) 하지만 kakao, naver 를 사용할때에는 별도의 provider를 생성해주어야 된다.
  • OAuth2 client library가 제공되는 서비스 플랫폼에 한해서 약속된 규칙에 의거하여 코드를 받는uri, 토큰 받는 uri, 사용자 정보요청 uri등을 만들어 받아와준다.

3.1 build.gradle 설정

plugins {
	id 'org.springframework.boot' version '2.7.2'
	id 'io.spring.dependency-management' version '1.0.12.RELEASE'
	id 'java'
}

group = 'socialLogin'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-web'

	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'mysql:mysql-connector-java'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'


	implementation 'org.springframework.boot:spring-boot-devtools'
	implementation 'org.springframework.boot:spring-boot-starter-security'

	// oauth2-client 라이브러리
	implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

}

tasks.named('test') {
	useJUnitPlatform()
}

기본적으로 Spring Security 와 OAuth2-client 라이브러리를 추가한다.


3.2 application.yml 설정

naver, kakao 등 OAuth2-client 라이브러리가 제공하지 않는 provider는 직접 등록해주어야된다.


spring:
  jpa:
    hibernate:
      ddl-auto: create #create update none
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    show-sql: true

  security:
    oauth2:
      client:
        registration:
          google:
            client-id: xxx
            client-secret: xxx
            scope:
              - email
              - profile

          facebook:
            client-id: xxx
            client-secret: xxx
            scope:
              - email
              - public_profile

          naver:
            client-id: xxx
            client-secret: xxx
            scope:
              - name
              - email
			#- profile_image
            client-name: Naver
            authorization-grant-type: authorization_code
            redirect-uri: http://localhost:8880/login/oauth2/code/naver #코드를 받는 uri(naver 의 콜백 uri)

          kakao:
            client-id: xxx
            client-secret: xxx
            scope:
			# - profile_nickname
              - account_email
            client-name: Kakao
            authorization-grant-type: authorization_code
            client-authentication-method: POST #카카오는 필수 파라미터를 POST로 요청해야됌!
            redirect-uri: http://localhost:8880/login/oauth2/code/kakao #코드를 받는 uri(naver 의 콜백 uri)


        provider: #네이버의 provider는 등록되어 있지 않아 사용자가 등록해야된다.
          naver:
            authorization-uri: https://nid.naver.com/oauth2.0/authorize #네이버 로그인 창을 받을수 있다
            token-uri: https://nid.naver.com/oauth2.0/token #토큰을 받는 uri
            user-info-uri: https://openapi.naver.com/v1/nid/me #프로필 주소를 받는 uri
            user-name-attribute: response #회원 벙보를 json 형태로 받는데 response 라는 키값으로 네이버가 리턴해줌

          kakao:
            authorization-uri: https://kauth.kakao.com/oauth/authorize
            token-uri: https://kauth.kakao.com/oauth/token
            user-info-uri: https://kapi.kakao.com/v2/user/me
            user-name-attribute: kakao_account 

		 # 스프링 OAuth2-client 라이브러리의 provider 에는 구글,페이스북,트위터가 속해있어 redirect-uri , authorization-grant-type 를 적어주지 않아도 자동으로 고정적으로 등록되어 있다.(적어줘도 된다.) ex) /login/oauth2/facebook, google 로 고정
         # 하지만 네이버나 카카오톡은 Provider 가 아니기 때문에 적어주어야된다. 원하는대로 uri 를 지정할수 있다, 고정되어 있지 않다.

(1) Client-id, Client-secret
: 각 플랫폼에서 발급받은 코드를 넣는다.

(2) scope

: 각 플랫폼의 사용자 동의체크에서 사용자 정보중 필요한 정보의 필드를 넣는다.

예시)
test

(3) authorization-grant-type
: 권한 부여 방식의 종류로서 code, token을 사용하는 방식

(4) redirect-uri
: 각 플랫폼에서 입력한 code를 받는 uri

(5) 필요한 플랫폼 provider 정의
: OAuth2-client 라이브러리가 제공하지 않는 provider는 위와 같이 필요한 정보들을 입력해준다.


3.3 domain (User)

import lombok.*;

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity @Data
@NoArgsConstructor
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Long id;

    private String username;
    private String password;
    private String email;

    @Enumerated(EnumType.STRING)
    private Role role; //ADMIN, MANAGER, USER
    //private String role;

    private String provider; //어떤 OAuth인지(google, naver 등)
    private String provideId; // 해당 OAuth 의 key(id)

    private LocalDateTime createDate;

    @Builder
    public User(String username, String password, String email, Role role, String provider, String provideId, LocalDateTime createDate) {
        this.username = username;
        this.password = password;
        this.email = email;
        this.role = role;
        this.provider = provider;
        this.provideId = provideId;
        this.createDate = createDate;
    }
}

3.4 Repository (UserRepository)

소셜로그인 구현이 목표기 때문에 간단하게 구성

import org.springframework.data.jpa.repository.JpaRepository;
import socialLogin.socialLoginTest.domain.User;


public interface UserRepository extends JpaRepository<User, Long> {

    public User findByUsername(String username);
}

3.5 SecurityConfig

import lombok.RequiredArgsConstructor;
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.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import socialLogin.socialLoginTest.config.oauth.PrincipalOauthUserService;

@Configuration
@EnableWebSecurity //시큐리티 활성화 -> 기본 스프링 필터 체인에 등록
public class SecurityConfig  {

    @Autowired
    private PrincipalOauthUserService principalOauthUserService;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/user/**").authenticated()
                .antMatchers("/manager/**").hasAuthority("MANAGER")
                .antMatchers("/admin/**").hasAuthority("ADMIN")
                .anyRequest().permitAll()

                .and()
                .formLogin()
                .loginPage("/loginForm") //미인증자일경우 해당 uri를 호출
                .loginProcessingUrl("/login") //login 주소가 호출되면 시큐리티가 낚아 채서(post로 오는것) 대신 로그인 진행 -> 컨트롤러를 안만들어도 된다.
                .defaultSuccessUrl("/")

                .and()
                .oauth2Login()
                .loginPage("/loginForm")
                .defaultSuccessUrl("/")
                .userInfoEndpoint()
                .userService(principalOauthUserService);//구글 로그인이 완료된(구글회원) 뒤의 후처리가 필요함 . Tip.코드x, (엑세스 토큰+사용자 프로필 정보를 받아옴)


        return http.build();
    }
}

OAuth2 프로토콜 방식을 사용하지 않는 방식(formLogin(), 일반 로그인) 에서는 Spring Security가 "/loginForm" uri로 이동시킴

Spring Security Filter가 자동으로 "/login" post방식으로 오는 uri를 낚아채어 UserDetalisService타입의 객체의 loadUserByUsername 메소드를 호출해둔다.

OAuth2 프로톨을 방식을 사용할때에는(oauth2Login()) DefalutOAuth2UserService(여기선 PrincipalOauthUserService가 해당 타입을 상속함) 타입의 loadUser 메소드를 호출한다.


3.6 PrincipalDetails (UserDtalis, OAuth2User를 구현한 객체)

import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.util.StringUtils;
import socialLogin.socialLoginTest.domain.Role;
import socialLogin.socialLoginTest.domain.User;

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

// 시큐리티가 "/login" 주소 요청이 오면 낚아 채서 로그인을 진행해준다.
// 로그인을 진행이 완료가 되면 시큐리티 session을 만들어준다.(Security Session(Session안에 특정영역))
// 해당 세션안에는 Authentication 타입객체가 들어간다.
// Authentication 은 UserDetails 타입 객체가 들어갈수 있다.
// UserDetails 안에 use(사용자)를 가지고 있는다.

@Data
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;
    }

    /**
     * OAuth2User 인터페이스 메소드
     */
    @Override
    public Map<String, Object> getAttributes() {
        return attributes;
    }


    /**
     * UserDetails 인터페이스 메소드
     */
    // 해당 User의 권한을 리턴하는 곳!(role)
    // SecurityFilterChain에서 권한을 체크할 때 사용됨
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collection = new ArrayList();
        collection.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {

                return String.valueOf(user.getRole());
            }
        });
        return collection;
    }

    @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() {

        //우리사이트!! 1년 동안 사용하지 않으면 휴면 계정으로 바꾼다.
        // 현재 시간 - 마지막 로그인 시간 => 1년을 초기하면 return false로 바꾼다.
        // 이러한 로직을 여기 넣는다.
        return true;
    }

    @Override
    public String getName() {
        return null;
    }
}

앞서 스프링 시큐리티 세션 영역에는 Authentication객체가 들어가게 되고 Authentication객체안에는
UserDetails, OAuth2User객체가 들어갈 수 있다.

컨트롤러에서 필요한 Authentication객체를 가져올때 일반 로그인이든 OAuth2 로그인이든 상관없이 동일한 객체를 가져올수 있게 하기 위해서 함께 상속받았다.


3.7 PrincipalService (UserDetailService를 상속함)

import lombok.RequiredArgsConstructor;
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 socialLogin.socialLoginTest.domain.User;
import socialLogin.socialLoginTest.repository.UserRepository;

// 시큐리티에서 설정에서 LoginProcessingUrl("/login");
// "/login" 요청이 오면 자동으로 UserDetailsService 타입으로 loC 되어있는  loadUserByUsername 함수가 실행된다.!
// Authentication 객체로 만들어준다

@Service
@RequiredArgsConstructor
public class PrincipalService implements UserDetailsService {

    private final UserRepository userRepository;

    //시큐리티 session => Authentication => UserDetails
    // 여기서 리턴 된 값이 Authentication 안에 들어간다.(리턴될때 들어간다.)
    // 그리고 시큐리티 session 안에 Authentication 이 들어간다.
    //함수 종료시 @AuthenticationPrincipal 어노테이션이 만들어진다.
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User findUser = userRepository.findByUsername(username);
        if(findUser!=null) {
            return new PrincipalDetails(findUser);
        }

        return null;
    }
}

loadUserByUsername 메소드는 사용자의 패스워드 인증이 정상적으로 처리된 사용자의 이름을 반환해준다.

PrincipalDetails(UserDetails 상속)객체를 반환해주므로 Authentication 타입객체로 만들어질수 있고 스프링 시큐리티 세션안에 들어갈수 있게 된다.

주의!) 사용자의 아이디를 입력을 받는 name의 값을 "username" 으로 받아야 된다.


3.8 PricipalOauthUserSevice (DefaultOAuth2UserService을 상속)

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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 socialLogin.socialLoginTest.config.auth.PrincipalDetails;
import socialLogin.socialLoginTest.config.oauth.provider.*;
import socialLogin.socialLoginTest.domain.Role;
import socialLogin.socialLoginTest.domain.User;
import socialLogin.socialLoginTest.repository.UserRepository;

import java.time.LocalDateTime;
import java.util.Map;

@Service
public class PrincipalOauthUserService extends DefaultOAuth2UserService {

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    private UserRepository userRepository;

    //구글로 부터 받은 userRequest 데이터에 대한 후처리되는 함수
    //함수 종료시 @AuthenticationPrincipal 어노테이션이 만들어진다.
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        //"registraionId" 로 어떤 OAuth 로 로그인 했는지 확인 가능(google,naver등)
        System.out.println("getClientRegistration: "+userRequest.getClientRegistration());
        System.out.println("getAccessToken: "+userRequest.getAccessToken().getTokenValue());
        System.out.println("getAttributes: "+ super.loadUser(userRequest).getAttributes());
        //구글 로그인 버튼 클릭 -> 구글 로그인창 -> 로그인 완료 -> code를 리턴(OAuth-Clien라이브러리가 받아줌) -> code를 통해서 AcssToken요청(access토큰 받음)
        // => "userRequest"가 감고 있는 정보
        //회원 프로필을 받아야하는데 여기서 사용되는것이 "loadUser" 함수이다 -> 구글 로 부터 회원 프로필을 받을수 있다.


        /**
         *  OAuth 로그인 회원 가입
         */
        OAuth2User oAuth2User = super.loadUser(userRequest);
        OAuth2UserInfo oAuth2UserInfo =null;

        if(userRequest.getClientRegistration().getRegistrationId().equals("google")) {
            oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes());
        }
        else if(userRequest.getClientRegistration().getRegistrationId().equals("facebook")) {
            oAuth2UserInfo = new FacebookUserInfo(oAuth2User.getAttributes());
        }
        else if(userRequest.getClientRegistration().getRegistrationId().equals("naver")) {
            oAuth2UserInfo = new NaverUserInfo((Map)oAuth2User.getAttributes().get("response"));
        }
        else if(userRequest.getClientRegistration().getRegistrationId().equals("kakao")) {
            oAuth2UserInfo = new KakaoUserInfo((Map)oAuth2User.getAttributes().get("kakao_account"),
                                String.valueOf(oAuth2User.getAttributes().get("id")));
        }
        else{
            System.out.println("지원하지 않은 로그인 서비스 입니다.");
        }

        String provider = oAuth2UserInfo.getProvider(); //google , naver, facebook etc
        String providerId = oAuth2UserInfo.getProviderId();
        String username = provider + "_" + providerId;
        String password =  bCryptPasswordEncoder.encode("테스트"); //중요하지 않음 그냥 패스워드 암호화 하
        String email = oAuth2UserInfo.getEmail();
        Role role = Role.USER;

        User userEntity = userRepository.findByUsername(username);
        //처음 서비스를 이용한 회원일 경우
        if(userEntity == null) {
            LocalDateTime createTime = LocalDateTime.now();
            userEntity = User.builder()
                    .username(username)
                    .password(password)
                    .email(email)
                    .role(role)
                    .provider(provider)
                    .provideId(providerId)
                    .createDate(createTime)
                    .build();

            userRepository.save(userEntity);
        }

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

OAuth2-client 라이브러리가 code단계 처리후 OAuth2UserRequest객체에 엑세스 토큰, 플랫폼 사용자 고유 key값을 반환해준다.

OAuth2UserRequest객체의 상위타입인 OAuth2User객체에 요청한 사용자 정보를 반환해준다.

요청한 사용자 정보에 대한 Response의 형식이 다르다.

OAuth2-client가 지원해주는 provider(구글, 페이스북등)는 Map형태로 key,Object로 반환해준다.

OAuth2-client가 지원해주지 않은 provider(네이버, 카카오톡)는 json형태로 반환해준다.

  • Naver의 경우
  • Kakao의 경우
    test

PrincipalDetails(OAuth2User 상속)객체를 반환해주므로 Authentication 타입객체로 만들어질수 있고 스프링 시큐리티 세션안에 들어갈수 있게 된다.


3.9 OAuth2UserInfo

public interface OAuth2UserInfo {

    String getProviderId();
    String getProvider();
    String getEmail();
    String getName();

}

public class FacebookUserInfo implements OAuth2UserInfo {
    private Map<String, Object> attributes;

    public FacebookUserInfo(Map<String, Object> attributes) {
        this.attributes = attributes;
    }

    @Override
    public String getProviderId() {
        return String.valueOf(attributes.get("id"));
    }

    @Override
    public String getProvider() {
        return "facebook";
    }

    @Override
    public String getEmail() {
        return String.valueOf(attributes.get("email"));
    }

    @Override
    public String getName() {
        return String.valueOf(attributes.get("name"));
    }
}

public class GoogleUserInfo implements OAuth2UserInfo {
    private Map<String, Object> attributes;

    public GoogleUserInfo(Map<String, Object> attributes) {

        this.attributes = attributes;
    }

    @Override
    public String getProviderId() {
        return String.valueOf(attributes.get("sub"));
    }

    @Override
    public String getProvider() {
        return "google";
    }

    @Override
    public String getEmail() {
        return String.valueOf(attributes.get("email"));
    }

    @Override
    public String getName() {
        return String.valueOf(attributes.get("name"));
    }
}

public class NaverUserInfo implements OAuth2UserInfo {
    private Map<String, Object> attributes;

    public NaverUserInfo(Map<String, Object> attributes ) {
        this.attributes = attributes;
    }

    @Override
    public String getProviderId() {
        return String.valueOf(attributes.get("id"));
    }

    @Override
    public String getProvider() {
        return "naver";
    }

    @Override
    public String getEmail() {
        return String.valueOf(attributes.get("email"));
    }

    @Override
    public String getName() {
        return String.valueOf(attributes.get("name"));
    }
}

public class KakaoUserInfo implements OAuth2UserInfo {

    private String id;
    private Map<String, Object> kakaoAccount;

    public KakaoUserInfo(Map<String, Object> attributes, String id ) {
        this.kakaoAccount = attributes;
        this.id = id;
    }

    @Override
    public String getProviderId() {
        return id;
    }

    @Override
    public String getProvider() {
        return "kakao";
    }

    @Override
    public String getEmail() {
        return String.valueOf(kakaoAccount.get("email"));
    }

    @Override
    public String getName() {
        return null;
    }
}

3.10 Controller (테스트용도)

import com.nimbusds.jose.shaded.json.JSONObject;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import socialLogin.socialLoginTest.config.auth.PrincipalDetails;
import socialLogin.socialLoginTest.config.oauth.provider.OAuth2UserInfo;
import socialLogin.socialLoginTest.domain.Role;
import socialLogin.socialLoginTest.domain.User;
import socialLogin.socialLoginTest.repository.UserRepository;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.time.LocalDateTime;

@Controller
@RequiredArgsConstructor
public class indexController {

    private final UserRepository userRepository;
    private final BCryptPasswordEncoder passwordEncoder;

    @ResponseBody
    @GetMapping("/test/login")
    public String testLogin(
            Authentication authentication,
            @AuthenticationPrincipal PrincipalDetails userDetails) { //세션 정보 받아오기 (DI 의존성 주입)

        //방법 1
        System.out.println("/test/login =============================");
        PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
        System.out.println("authentication:" + principalDetails.getUser());


        //방법 2
        System.out.println("userDetails:" + userDetails.getUser());

        return "세션 정보 확인";
    }
    @ResponseBody
    @GetMapping("/test/oauth/login")
    public String testOAuthLogin(
            Authentication authentication,
            @AuthenticationPrincipal OAuth2User oauth
            ) { //세션 정보 받아오기 (DI 의존성 주입)

        //방법 1
        OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
        System.out.println("authentication: " + oAuth2User.getAttributes());

        //방법 2
        System.out.println("OAuth2User:" + oauth.getAttributes());

        return "OAuth 세션 정보 확인";
    }

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

    @ResponseBody
    @GetMapping("/user")
    public String user(@AuthenticationPrincipal PrincipalDetails principalDetails) {
        System.out.println("GetMapping(/user) ==========================");
        System.out.println("principalDetails: " + principalDetails );

        return "user";
    }

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

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

    //스프링 시큐리티가 낚아 챈다(post로 오는것!!)!! -> config 를 통해 해결
    @ResponseBody
    @GetMapping("/login")
    public String login(){
        return "login";
    }

    @PostMapping("/join")
    public String join(@ModelAttribute User user){
        user.setRole(Role.USER);
        //user.setRole("USER");
        user.setCreateDate(LocalDateTime.now());

        String rawPassword = user.getPassword();
        String encPassword = passwordEncoder.encode(rawPassword);

        user.setPassword(encPassword);
        userRepository.save(user);

        return "redirect:/loginForm";
    }

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

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

}

4. Test

👀 View
test

📝DB
test

일반 로그인 회원과 소셜로그인 회원을 providerId, provider의 유무로 구별하게 하였다.

profile
백엔드 개발자를 꿈꾸며 기록중💻

1개의 댓글

comment-user-thumbnail
2023년 12월 1일

스프링 시큐리티 인 액션 정독하고 OAuth 로그인과 FormLogin 통합 하는 방법 찾던 중.. 이렇게 잘 작성 된 레퍼런스를 찾아서 너무 기쁘네요. ProviderId같은 경우에도 제공자가 다들 다르게 주는거 보고 멘붕 왔는데 정말 감사합니다.

답글 달기