본문 바로가기
카테고리 없음

Spring Security + JWT를 이용한 로그인/회원가입 구현

by asdft 2024. 1. 19.

JWT (Json Web Token) 이란?

  • JWT는 웹 애플리케이션에서 사용자 인증 및 정보 교환을 위한 토큰 기반 인증 방식입니다. JWT는 토큰 자체에 사용자의 클레임(claim)정보를 포함하고 있으며, 이를 통해 서버간의 신뢰성 있는 정보 교환을 가능하게 합니다.
  • 서버의 상태를 유지하지 않고도(Stateless) 토큰을 사용하여 인증을 처리할 수 있습니다.
  • 토큰을 사용하여 클라이언트와 서버간 인증을 할 수 있습니다.
Claim 이란?

클레임이란 사용자 정보나 데이터속성등을 의미한다. 클레임 기반 토큰은 토큰안에 정보를 담을 수 있는 특징이 있다.
클레임 기반 토큰은 아래예시와 같이 정보를 담고 있다. jwt는 클레임토큰중 가장 대표적인 것이다.
payload에서는 claim이라는 property를 key-value형태로 저장한다.

ex) { "sub": "12345" } 에서 sub 는 subject claim 이다.  

 

Header + Payload + Signature

Header: Signature를 해싱하기 위한 알고리즘 정보들이 담겨있다.

Payload: 서버와 클라언트가 주고받는, 사스템에서 실제로 사용될 정보에 대한 내용들을 담고 있다.

Signature: 토큰의 유효성 검증을 위한 문자열. 이 문자열을 통해 서버에서는 이 토큰이 유효한 토큰인지를 검증할 수 있다.

 

JWT 장점

- 중앙의 인증서버, 데이터 스토어에 대한 의존성이 없다. 시스템 수평 확장에 유리.

- Base64 URL Safe Encoding을 사용 > URL, Cookie, Header 모두 사용 가능. 범용성이 좋음.

 

JWT 단점

- Payload의 정보가 많아지면 네트워크 사용량 증가, 데이터 설계 고려 필요.

- 토큰이 클라이언트에 저장되어 서버에서 클라인언트의 토큰을 조작할 수 없음.


 

sessionManagement(sessionManager -> sessionManager
    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    
// .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
 Jwt를 사용하기 때문에 session을 stateless로 설정한다. 
 stateless로 설정 시, Spring Security는 세션을 사용하지 않는다.

 

.csrf(AbstractHttpConfigurer::disable)
// API를 작성하는데 프런트가 정해져있지 않기 때문에 CSRF설정은 우선 꺼놓는다.

.httpBasic(AbstractHttpConfigurer::disable)
// httpBasic 방식 대신 Jwt를 사용하기 때문에 disable로 설정

.formLogin(AbstractHttpConfigurer::disable)
// formLogin 방식 대신 Jwt를 사용하기 때문에 disable로 설정
http
	.cors(withDefaults()) // 1
    
    
    
    
  @Value("${cors.allow-origins}")	
  private List<String> corsOrigins;

  // 2
  @Bean
  CorsConfigurationSource corsConfigurationSource() {	
    CorsConfiguration corsConfiguration = new CorsConfiguration();
    corsConfiguration.setAllowedOriginPatterns(corsOrigins);
    corsConfiguration.setAllowedHeaders(List.of("*"));
    corsConfiguration.setMaxAge(3600L);
    corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST", "DELETE", "PUT", "PATCH"));
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", corsConfiguration);
    return source;
  }

1번 부분을 먼저 보겠습니다. 저 곳에서 CORS 설정을 추가하는 것입니다. .cors(withDefaults())일 경우, corsConfigurationSource라는 이름으로 등록된 Bean을 이용합니다.

2번 부분으로 내려가보겠습니다. 이 곳은 CorsConfigurationSource Bean 생성을 통해 구체적인 CORS 정책을 설정하는 곳입니다. 

 

.setAllowedOriginPatterns( )

: 허용할 도메인 목록. 출처(Origin)에 대해 스크립트 기반의 HTTP 통신을 허용하도록 설정합니다.  프론트가 따로 정해지지 않은 경우, *로 설정해 일단 모든 출처에 대해 허용하겠다는 의미를 내포하고 있다고 보면 됩니다.

 

.setAllowedHeaders(List.of("*"))

: 허용할 헤더 목록


.setMaxAge(3600L)

: preflight request 요청 결과를 캐시할 수 있는 시간을 나타냅니다. Preflight에 관한 내용은 아래 내용을 참고하자.

 

.setAllowedMethods(Arrays.asList("GET", "POST", "DELETE", "PUT", "PATCH"))

: 허용할 메서드 목록. 파라미터로 지정한 HTTP Method에 대한 HTTP 통신을 허용합니다.

 

Preflight란?
사전적의미 : 미리 보내는 것.
기본적으로 브라우저는 cross-origin 요청을 전송하기 전에 OPTIONS메소드로 preflight를 전송한다.

이 때, Response로 Access-Control-Allow-Origin과 Access-Control-Allow-Methods가 넘어오는데 이는
서버에서 어떤 origin과 어떤 method를 허용하는지 브라우저에게 알려주는 역할을 한다.
브라우저가 결과를 성공적으로 확인하고 나면 가능한 origin들로부터 cross-origin 요청을 보내서 그 이후 과정을 진행한다.

Preflight 관련 설정

그렇다면, cross-origin 요청을 보낼 때마다 preflight 요청 작업을 거쳐야 하는가?  그건 아니다.

서버 설정을 통해서 preflight 결과의 캐시를 일정 기간동안 저장시킬 수 있다.

이 캐시 정보가 살아있는 시간 동안은 preflight 를 생략하고 바로 요청 전송이 가능하다.

 

 

 

참고)

cors관련 설정을 포함한 필터.
기본적으로 서버 또는 지정된 특정 도메인의 요청만 허용하지만 
프런트가 정해져있지 않기 때문에 모든 도메인을 허용하는 방식으로 설정.

*위의 예시가 프런트가 정해져 해당 도메인만을 허용하는 방식임.

  @Bean
public CorsFilter corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOriginPattern("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");
    source.registerCorsConfiguration("/**", config);
    return new CorsFilter(source);
}

.authorizeRequests( ) : HttpServletRequest를 사용하는 요청들에 대한 접근제한을 설정하겠다

.antMatchers(api path).permitAll( ) : "api path 주소"에 대한 요청은 인증없이 접근을 허용하겠다

.anyRequest( ).authenticated( ) : 나머지 요청들은 모두 인증되어야 한다

 

 

 

 

 

출처: https://jake-seo-dev.tistory.com/77 [제이크서 위키 블로그:티스토리]