[Spring - Security] OAuth2 클라이언트와 Security 기초 인증 / 인가 처리 (Feat - Kakao Login && Local Login) - 1

2024. 3. 7. 10:48Spring

 

코드 버전

 

Spring boot: 3.2.1

Java: JDK 17.0.9

Gradle - Groovy

 

의존성
//security
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-security'
    
//jwt
implementation 'io.jsonwebtoken:jjwt:0.12.3'
implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'

 

 

진행 방식

 

 

클라이언트에서 로그인 요청을 보내면 서버에서는 로그인 폼을 카카오 서버에서 받아 클라이언트에게 반환한다. 

로그인 후 서버에서는 Redirect URL을 통해 Token 값을 반환 받는다.

반환 받은 토큰 값을 가지고 다시 카카오 서버에 사용자 정보를 요청

 

Spring Security 이용한 인증/인가

전달 받은 사용자 정보를 이용해 회원 가입과 Jwt 토큰 발급을 통해 Spring Security 세션에 사용자 정보를 저장

Jwt 토큰을 클라이언트에 전달한다.

 

동작 방식별 작성 코드

 

 

Spring Security - 1편

  • SecurityConfig 
  • CorsMvcConfig 

 

Jwt - 2편

  • JWTFilter 
  • JWTUtil 
  • + LoginFilter
  • OAuth2UserDetailsService
  • OAuth2UserDetails → dto (패키지)  PrincipalDetail (OAuth2, Local 통합 구현) 생성
  • OAuth2SuccessHandler
  • OAuth2FailHandler
  • OAuth2Response
Spring Security applicaion.yml 환경변수 설정

 

기존 application.yml 파일

spring:
  security:
    oauth2:
      client:
        registration:
          kakao:
            client-authentication-method: client_secret_post
            client-name: kakao
            client-id: ${Kakao_Client_ID}
            client-secret: ${Kakao_Client_SECRET}
            authorization-grant-type: authorization_code
            provider: kakao
            redirect-uri: ${Kakao_Redirect-URI}
            scope:
              - profile_nickname
              - account_email
        provider:
          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: id

 

 

git에 업로드되지 않는 API-KEY.properties

Kakao_Client_ID = ....
Kakao_Client_SECRET = ....
Kakao_Redirect-URI = ....
SECRET_KEY = ....

 

별도의 application-API-KEY.properties 파일을 생성해 클라우드 서버에 올리면 안되는 값을 작성 후 호출

 

Kakao_Client_ID = REST API 키
Kakao_Client_SECRET = 보안 -> 카카오 로그인 활성화 -> Client Secret 발급
Kakao_Redirect-URI = 카카오 로그인 -> Redirect URL 작성 
SECRET_KEY = JWT 암호화키 

 

client-authentication-method: client_secret_post = 일반 POST (Google, Naver) 의 경우 카카오 로그인에서는 Cors 오류가 발생해 정상적인 결과를 얻을 수없다.

https://devtalk.kakao.com/t/spring-security-oauth2-401-no-body/127960/6

 

Spring security OAuth2 카카오 로그인 401 [no body] 에러 질문드립니다

좀더 빨리 찾았으면 좋았을텐데 다 해결하고 찾았네요. Spring security 5.6 이후로 변경되었나 봅니다. (POST → client_secret_post) 로 변경됐네요. https://docs.spring.io/spring-security/reference/5.6.0-RC1/servlet/oauth2/

devtalk.kakao.com

 

SecurityConfig

 

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig   {

    private final CustomOAuth2UserService customOAuth2UserService;
    private final JwtUtil jwtUtil;
    private final CustomSuccessHandler customSuccessHandler;
    private final CustomFailHandler customFailHandler;

    @Bean
    public SecurityFilterChain securityFilterChains(HttpSecurity http) throws Exception {

        http
                .cors(corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() {
                    @Override
                    public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {

                        CorsConfiguration configuration = new CorsConfiguration();

                        configuration.setAllowedOrigins(Collections.singletonList("http://localhost:3000"));
                        configuration.setAllowedMethods(Collections.singletonList("*"));
                        configuration.setAllowCredentials(true);
                        configuration.setAllowedHeaders(Collections.singletonList("*"));
                        configuration.setMaxAge(3600L);

                        configuration.setExposedHeaders(Collections.singletonList("Set-Cookie"));
                        configuration.setExposedHeaders(Collections.singletonList("Authorization"));

                        return configuration;
                    }
                }));

        //csrf disable
        http
                .csrf(AbstractHttpConfigurer::disable);

        //From 로그인 방식 disable
        http
                .formLogin(AbstractHttpConfigurer::disable);

        //HTTP Basic 인증 방식 disable
        http
                .httpBasic(AbstractHttpConfigurer::disable);

        //JWTFilter 추가
        http
                .addFilterAfter(new JwtFilter(jwtUtil), OAuth2LoginAuthenticationFilter.class);

        //oauth2
        http
                .oauth2Login((oauth2) -> oauth2
                        .userInfoEndpoint((userInfoEndpointConfig) -> userInfoEndpointConfig
                                .userService(customOAuth2UserService))
                        .successHandler(customSuccessHandler)
                        .failureHandler(customFailHandler)
                );

        //경로별 인가 작업
        http
                .authorizeHttpRequests((auth) -> auth
                        .requestMatchers("/", "/user/**").permitAll()
                        .anyRequest().authenticated());

        //세션 설정 : STATELESS
        http
                .sessionManagement((session) -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS));

        return http.build();

    }

}

 

 

 

JWT를 이용해 사용자 정보를 처리하기 때문에 csrf 비활성화 세션 또한 stateless 설정을 해줬다. 각각의 설정은 사용자의 조건에 맞춰서 수정 해야한다.

 

configuration.setExposedHeaders(Collections.singletonList("Set-Cookie")); 쿠키를 헤더에 저장
configuration.setExposedHeaders(Collections.singletonList("Authorization")); 쿠키 name 설정

 

Spring boot 버전별 구현 방식

 

스프링 부트 2.X.X ~ 2.6.X 

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
        	.authorizeRequests()
            	.antMatchers("/").authenticated()
            	.anyRequest().permitAll();

    }
}

 

WebSecurityConfig를 이용해 구현하였다.

 

스프링 부트 2.7.X ~ 3.0.X

 

public class SpringSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http
                    .authorizeHttpRequests()
                    .requestMatchers("/")
                    .anyRequest().authenticated();

        return http.build();
    }
}

 

WebSecurityConfig -> SecurityFilterChain으로 바뀜

 

스프링 부트 3.1.X ~

 

public class SpringSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http
            	.authorizeHttpRequests((auth) -> auth
              	.requestMatchers("/login", "/join").permitAll()
              	.anyRequest().authenticated()
        );

        return http.build();
    }
}

 

SecurityFilterChain 람다식 선택에서 필수로 변경

 

CorsMvcConfig

 

@Configuration
public class CorsMvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry corsRegistry) {
        corsRegistry.addMapping("/**")
                .exposedHeaders("Set-Cookie")
                .allowedOrigins("http://localhost:3000");
    }
}

 

 

 

로컬 환경에서 개발시 클라이언트 서버와 백엔드 서버 사이 웹브라우저에서 데이터를 요청하면 데이터가 보여지지 않는 CORS 오류를 허용해줘야 된다.

 

추가로 React에서 axios를 이용한 api 통신을 할 경우 

// 1. axios 전역 설정
axios.defaults.withCredentials = true; // withCredentials 전역 설정

 

해당 코드를 추가하지 않으면 쿠키 데이터가 서버로 정상적으로 넘어오지 않는 문제가 발생한다

 

https://inpa.tistory.com/entry/AXIOS-%F0%9F%93%9A-CORS-%EC%BF%A0%ED%82%A4-%EC%A0%84%EC%86%A1withCredentials-%EC%98%B5%EC%85%98

 

🍪 CORS 쿠키 전송하기 (withCredentials 옵션)

🤬 CORS를 허용했는데도 쿠키가 넘어가지 않는 현상 보통 웹을 구성할때 리액트(React)나 뷰(Vue)와 같은 라이브러리 / 프레임워크를 사용한다면 따로 프론트 서버를 실행하여 개발하게 된다. 만일

inpa.tistory.com