Spring 3.x Security Jwt 적용하기 1/3

이원찬·2024년 8월 2일
0

Spring

목록 보기
12/13

이 글은 스프링 3.x.x 에서 동작하는 시큐리티 설정입니다.

먼저 스프링 시큐리티가 동작하는 방식에 대해 살펴보자

위 그림을 참고하며 구현했지만 100% 따르는 것은 아니다.

한번 살펴보자

  1. 클라이언트가 서버에게 HTTP 요청을 한다.

  2. 서블릿이 컨트롤러에게 요청을 전달하기전 Spring 필터가 요청을 가로챈다.

  3. 우리가 구현 할 커스텀 필터 JwtAuthFilter 가 요청에 대한 인가를 판단한다.

    • 이 구현에서는 JwtAuthFilter가 토큰에 대한 인증에 실패해도 요청을 거절하지 않습니다.
    • Jwt 인증로직은 커스텀 JwtService 구현체에서 직접 로직을 작성합니다
  4. JwtAuthFilter 는 UserRepository를 이용한 데이터 베이스 연동으로 토큰에 해당하는 User를 찾고 User와 함께 token의 인증 책임을 JwtService에게 넘긴다.

  5. JwtService는 토큰이 유효한지 검증한다.

    • 올바은 JWT 인지 (서명이 올바른지)
    • 만료되지 않은 토큰인지
    • 토큰에 있는 유저의 인증 데이터가 데이터베이스에 존재하는지

    인증이 끝난 JwtService는 JwtAuthFilter에게 토큰의 검증 결과를 응답한다.

  6. JwtAuthFilter는 시큐리티에게 이 사용자는 인증된 사용자라고 SecurityContextHolder를 업데이트 한다.

  7. 이 SecurityContextHolder는 Controller에 그대로 전달 되고 Controller에서 정해진 규격에 응답을 응답하게 되는 것이다!

    • 만약 인증이 되지 않았다면? ( 다른곳에서 기술한다. )

초기 설정

먼저 의존성을 설치하자

// 24.07.31 기준 6.3.1 버전의 시큐리티가 설치된다.
implementation 'org.springframework.boot:spring-boot-starter-security'

// 24.07.31 기준 가장 최신 버전의 jjwt 라이브러리이다.
// 토큰 생성, 검증 등에 사용된다.
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3'

시큐리티의 의존성은 위 하나이지만 본인이 사용하는 데이터베이스에 맞는 의존성과 JPA를 사용중이라면 JPA 의존성을 같이 추가한다.

아래는 본인이 사용하는 모든 의존성이다.

implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// web
implementation 'org.springframework.boot:spring-boot-starter-web'
// mysql
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.mysql:mysql-connector-j'
// lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

// spring security
implementation 'org.springframework.boot:spring-boot-starter-security:3.3.2'

// swaggerDoc
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'

// jjwt
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3'

이렇게 시큐리티 의존성을 추가 하기만 해도 모든 API 요청에 인증이 필요하게 된다.

인증없이 API 요청을 날리면 아래와같이 401 에러와 함께 응답한다.

브라우저로 접속하면 아래처럼 로그인 UI 가 보이기도 한다.

만약 이게 싫다면 모든 기본 설정을 무효화 시킬수 있다.

import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})

위 처럼 SecurityAutoConfiguration.class 옵션을 넣어 무효화 가능하다.

하지만 본 포스팅은 무효화하지 않고 진행합니다.

먼저 간단하게 저 로그인창에 로그인을 하여 브라우저를 이용해 Get요청을 해보자

의존성을 추가한다음 어플리케이션을 실행하면 표준출력 화면에 아래와 같이 default 비밀번호가 뜬다

그리고 브라우저에 가서 user와 위 비밀번호를 입력하면

잘 나온다!

왜 그런지는 아래 내용을 한번 보자

Security 의존성만으로 모든 요청에 인증이 필요한 이유!

시큐리티 설정 커스터마이징

위에서 중요하다고 설명한 defaultSecurityFilterChain 를 (필터체인) 을 커스텀하여 설정을 커스텀 해보자

@EnableWebSecurity
public class SecurityConfig {
    @Bean
    SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http
                .authorizeExchange(exchanges -> exchanges
                        .anyExchange().authenticated()
                )
                .httpBasic(withDefaults())
                .formLogin(withDefaults());
        return http.build();
    }
}

위와같은 설정파일을 만들자

이제 커스텀을 해보자 모든 “/” 요청에 대해 권한을 뚫어주자!

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
            // csrf 설정을 비활성화
            .csrf(AbstractHttpConfigurer::disable)
            // http 기본 인증 해제
            .httpBasic(AbstractHttpConfigurer::disable)
            // form 기반 인증 해제
            .formLogin(AbstractHttpConfigurer::disable)
            // 세션 생성 정책 설정 (STATELESS: 세션을 사용하지 않음)
            .sessionManagement(
                    authorize -> authorize
                            .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(
                    authorize -> authorize
                            // 모든 요청에 대해 허용
                            .requestMatchers("/**")
                            .permitAll()
            );
            return http.build();
            
    }

이 상태에서 요청을 날려보면

잘나온다. 성공적으로 모든 요청에 인증을 해제 했다.

모든 요청의 인증을 해제 하는이유?

Config 파일에서는 요청의 엔드포인트 별로 인증 권한 여부를 수정할수있게 되어있다.

본인이 생각하기에 엔드포인트 별로 인증권한을 부여하는것은 설계적으로 매우 불쾌한 일이라 생각했고 일단 모든 인증요청을 열어놓은 뒤 포스팅 뒤에 고민한 결과에 대해 기술합니다.

다음 포스트에선 jwt 로직과 필터 로직을 구현하겠습니다.

참고 자료
https://ict-nroo.tistory.com/118
https://www.toptal.com/spring/spring-security-tutorial
https://sjh9708.tistory.com/170
https://www.youtube.com/watch?v=KxqlJblhzfI
https://velog.io/@dh1010a/Spring-Spring-Security를-이용한-로그인-구현-스프링부트-3.X-버전-1

profile
소통과 기록이 무기(Weapon)인 개발자

0개의 댓글