Spring Security session방식 OAUTH2로그인- 일반FORM회원가입 설정(3)

2025. 2. 26. 17:05Springboot/security

전체코드 : https://github.com/gks930620/spring_securty_all

 

동작방식을 이해하고, 카카오개발자 센터에 애플리케이션 등록(+ 앱키,redirect-uri 등)을 완료했다면

이제 구현을 해보자.

 

개념

사용자 입장에서 우리사이트에서 따로 회원가입을 안하고 카카오로 로그인을 하지만

카카오로 로그인을 처음 했을 때 우리사이트에 회원가입한 것처럼  DB에 넣어야한다.

단지 email, nickname 등을 카카오에서 가지고 올 뿐.

즉,  카카오로그인사용자는 우리사이트의 회원가입과정만 진행하지 않는거지

내부적으로는 카카오로그인 했을 때 우리서버DB에 가입되고 

일반FORM회원이랑 똑같이 사이트를 이용하게된다.

 

 

 

 

 

세팅파일

application.yml

dotenv:
  enabled: true  # .env파일설정.    KAKAO_CLIENT_ID 직접 쓰려면 없어도됨.
spring:
  datasource:
    url: jdbc:h2:mem:security
    driver-class-name: org.h2.Driver
    username: sa
    password:
  h2:
    console: # H2 DB를 웹에서 관리할 수 있는 기능
      enabled: true           # H2 Console 사용 여부
      path: /h2-console       # H2 Console 접속 주소
  jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        show_sql: true
        format_sql: true
        default_batch_fetch_size: 100
    open-in-view: false
  devtools:
    livereload:
      enabled: true
    freemarker:
      cache: false
    restart:
      enabled: true
  thymeleaf:
    cache: false


  security:
    oauth2:
      client:
        registration:
          kakao:
            client-id: ${KAKAO_CLIENT_ID}  # 카카오 REST API 키
            #client-secret: ${KAKAO_CLIENT_SECRET}  # 선택 (일반적으로 필요 없음)
            redirect-uri: http://localhost:8080/login/oauth2/code/kakao # 카카오 로그인 후 리디렉트될 주소
            authorization-grant-type: authorization_code  # OAuth2 인증 방식 (코드 방식)
            scope:
             - profile_nickname
             - account_email  # 요청할 사용자 정보 (이메일, 닉네임)


        provider:
          kakao:
            authorization-uri: https://kauth.kakao.com/oauth/authorize  # 카카오 로그인 URL  (인증서버)
            token-uri: https://kauth.kakao.com/oauth/token  # 액세스 토큰 요청 URL (인증서버)
            user-info-uri: https://kapi.kakao.com/v2/user/me  # 사용자 정보 요청 URL (리소스서버)
            user-name-attribute: id  # OAuth2User에서 사용할 식별 값. 왠만하면 oauth에서 제공하는대로사용하자. 카카오는 id, 구글은 sub
logging:
  level:
    org.hibernate.SQL: debug
    org.hibernate.type: trace

 

 

 

SecurityConfig

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    @Bean
    public PasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }


    private final CustomUserDetailsService customUserDetailsService;
    private final CustomOAuth2UserService customOAuth2UserService;


    @Bean
    public SecurityFilterChain filterChain1(HttpSecurity http) throws Exception{
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/h2-console/**").permitAll() // H2 콘솔 접근 허용
            )
            .csrf(csrf -> csrf.ignoringRequestMatchers("/h2-console/**")) // H2 콘솔 CSRF 비활성화
            .headers(headers -> headers.frameOptions(frame -> frame.disable())); // H2 콘솔을 iframe에서 허용
        // 이 설정은 내부 H2DB 사용을 위한 설정일뿐 의미없음.


        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/images/**").permitAll()  // 정적 자원 허용
            );


        http.authorizeHttpRequests((auth) -> auth
            .requestMatchers("/join/**", "/login").permitAll()
            .requestMatchers("/admin").hasAuthority("ADMIN")
            .requestMatchers("/my/**").hasAnyAuthority("ADMIN", "USER")
            .anyRequest().authenticated()
        );

        http.formLogin(auth -> auth
            .loginPage("/login")
            .loginProcessingUrl("/loginProc")  //기본값은 /login  (POST방식)
            .defaultSuccessUrl("/")
            .failureUrl("/login?error=true") // 로그인 실패 시 다시 로그인 페이지로.  기본값이 /login?error 지만  명시적으로.
            .permitAll()
        );

        
        http.userDetailsService(customUserDetailsService); //명시적으로 등록. 일반 form 로그인사용자처리

        http.oauth2Login(oauth2 -> oauth2
            .loginPage("/login")       // 한 페이지에서 폼 카카오 다 할거면 form이랑 같은게 좋음
            .defaultSuccessUrl("/") // 로그인 성공 후 이동할 URL
            .userInfoEndpoint(userInfo -> userInfo
                .userService(customOAuth2UserService) // 명시적.   OAuth2 사용자 로그인 처리
            )
        );
        http.logout((auth) -> auth.logoutUrl("/logout")
            .logoutSuccessUrl("/"));

        http.csrf((auth) -> auth.disable());   //csrf 적용X
        return http.build();
    }
}


 

 

 

 

 

 

일반 FORM 회원 가입

DB설정

UserEntity

@Entity
@Getter
@Setter
public class UserEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;   //JPA 기본 ID Long 


    @Column(unique = true)
    private String username;   //spring이 entity의 필드랑 파라미터를 비교하는건 아님.   security에서 username이 기본이지만, 사용자입장에선 ID
    private String password;  //spring이  entity의 필드랑 파라미터를 비교하는건 아님

    private String email;
    private String nickname;

    private String provider;  // Oauth2Provider이름. 일반FORM로그인,카카오로그인 구별용.  꼭 필요한 필드는 아님.


    private List<String> roles=new ArrayList<>();

}

 

 

 

 

 

userRepository


public interface UserRepository extends JpaRepository<UserEntity, Long> {
    public   UserEntity findByUsername(String username);
}

 

 

 

JoinDTO

@Setter
@Getter
public class JoinDTO {
    private String username;
    private String password;
    private String email;
    private String nickname;
}

 

 

 

JoinController

@Controller
@RequestMapping("/join")
@RequiredArgsConstructor
public class JoinController {

    private  final JoinService joinService;

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

    @ResponseBody
    @PostMapping("/join")
    public  String joinPost(JoinDTO joinDTO){
        joinService.joinProcess(joinDTO);
        return "회원가입 완료!";
    }
}

 

 

 

JoinService

@Service
public class JoinService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    public void joinProcess(JoinDTO joinDTO) {
        //db에 이미 동일한 username을 가진 회원이 존재하는지?
        UserEntity find = userRepository.findByUsername(joinDTO.getUsername());
        if(find!=null) {
            System.out.println("이미 있는 ID입니다.");
            return ;
        }
        UserEntity user = new UserEntity();
        user.setUsername(joinDTO.getUsername());
        user.setPassword(passwordEncoder.encode(joinDTO.getPassword()));  //DB에 저장될 때는 반드시 encoding되서 저장되어야한다.
        user.setNickname(joinDTO.getNickname());
        user.setEmail(joinDTO.getEmail());
        user.getRoles().add("USER");     //hasAuthority("USER") 에 맞게 USER로 세팅
        user.setProvider(null);
        userRepository.save(user);
    }
}