본문 바로가기
[Spring]/Spring Security

당신의 첫 프로젝트를 위한 스프링 시큐리티(1) - SecurityConfig 구성하기

by 황원용 2024. 7. 8.
728x90
📌 이번 글에서는 프로젝트 생성부터 SecurityConfig의 초기 설정을 설명합니다.

🤗 저의 스프링 시큐리티 구현은 아래와 같은 시나리오를 기준으로 합니다.
- 프론트 엔드와 백엔드가 나뉘어 진행되는 프로젝트를 기반으로 하여 스프링 시큐리티 설정에서 로그인 페이지에 대한 설정을 따로 하지 않음
- JWT 토큰 인증 방식을 사용함
- 토큰 관리에 redis를 이용함

 

🙉 이전 글 보기

첫 번째 글부터 정독하시면 보다 쉽게 이해하실 수 있습니다!

https://suzuworld.tistory.com/438 - 당신의 첫 프로젝트를 위한 스프링 시큐리티 톺아보기

 

📖 목차

스프링 시큐리티 톺아보기

SecurityConfig 구성하기 (현재 글)

 

⚙️프로젝트 생성

  • 스프링부트 3.x, 자바 17로 테스트 코드를 작성합니다.
  • 의존성을 참고하시어 프로젝트를 생성해 주세요.

 

🚗 프로젝트 실행해 보기

  • 생성된 프로젝트를 바로 실행해 봅시다.
  • 스프링 시큐리티가 적용되어 기본적으로 사용될 비밀번호가 생성되었음을 확인할 수 있습니다.

 

  • 이후 프로젝트에서 설정한 포트번호에 /login이라는 엔드포인트로 접속하면 시큐리티에서 제공하는 기본 로그인 폼 페이지가 나옴을 확인할 수 있습니다.
  • 기본 값은 username(id)은 user, password는 실행할 때 콘솔에 나오는 비밀번호입니다. 
    • 저의 경우에는 위의 사진에 나온 것과 같이 Using generated security password: c151d88c-71ab-4628-8628-ebd64dd0ea36입니다.
    • 만약 기본 username과 비밀번호를 변경하고 싶다면, application.properties 파일이나 application.yml 파일에 사용자 정의 설정을 추가할 수 있습니다.
  • 로그인을 하면 기본적인 스프링부트 에러페이지가 나옵니다.
    • 당연하죠? 아직 아무것도 만들지 않았으니까요.

 

🛠️ 시큐리티 Config

  • 본격적으로 시큐리티 설정을 해봅시다. 

@Configuration

  • 스프링 설정 클래스임을 나타내어 스프링에게 알립니다.

@EnableWebSecurity

  • 웹 시큐리티를 활성화합니다.

@RequiredArgsConstructor

  • 클래스의 모든 final이 붙은 필드에 대해 생성자를 자동으로 생성해 주는 기능을 합니다.
  • 이를 통해 불필요한 코드를 줄이고, 코드의 가독성을 높일 수 있습니다.
  • 다음 글에서 사용합니다.

extends SecurityConfigurerAdapter

  • ScurityConfigurerAdapter는 스프링 시큐리티의 설정을 정의할 때 사용하는 추상 클래스입니다.
  • 이 클래스를 상속받아 필요한 설정을 오버라이드하여 구현합니다.

<DefaultSecurityFilterChain, HttpSecurity>

  • 제네릭 타입 <DefaultSecurityFilterChain, HttpSecurity>는 설정할 시큐리티 필터 체인과 설정 객체를 지정합니다.
  • DefaultSecurityFilterChain : 기본 시큐리티 필터 체인으로, 여러 시큐리티 필터들이 포함되어 있습니다.
  • HttpSecurity : HTTP 보안 설정을 구성하는 객체로 특정 URL 패턴에 대한 보안 설정을 정의할 수 있습니다.

 

⚙️ SecurityFilterChain

  • 구글링을 통해 나오는 WebSecurityConfigurerAdapter를 상속하고 configure 메서드를 오버라이딩하는 방식은 스프링 부트 3.x 이상부터 스프링 시큐리티 6 이상 버전이 적용되면서 deprecated 되었습니다. 따라서 현재는 @Bean으로 등록하여 구현하는 방식을 사용하시면 됩니다.
  • 각종 설정은 HttpSecurity를 통해 이뤄집니다. 

 

👨🏻‍🔬 내부 설정

  • 위 사진과 같이 HttpSecurity를 이용하여 각종 설정을 추가할 수 있습니다.
  • 이곳에서 csrf, oauth2, 각종 필터, 핸들러, 로그인 페이지 등 다양한 시큐리티 관련 설정을 할 수 있습니다.
    • 이 예시에서는 구현에 필요한 최소한의 기능만을 다룹니다.
    • oauth2나 로그인페이지에 대한 추가 설정이 필요하면 구글링을 통해 찾아보시면 생각보다 쉽게 하실 수 있습니다.
    • 핵심은 전체적인 동작원리에 대한 이해라고 생각합니다.

 

  • 일단 위 설정에 대해 알아봅시다.
  • 위 내용이 전부는 아닙니다. 처음에 다 추가하는 것보다 순서대로 추가하는 것이 좋을 것 같아서 다른 설정은 해당 내용이 나올 때 추가하겠습니다.

 

✨ 람다(Lambda)

  • 스프링 시큐리티 6.1 이후부터는 시큐리티 설정을 구성할 때 람다 DSL을 이용합니다.
  • 람다식은 익명 함수를 간단하게 표현하는 방법입니다.
  • ::나 ->와 같은 식의 표현을 처음 보신 분들은 별 거 없고 그냥 함수 표현 방식이 람다라는 것이니 넘어가셔도 됩니다.
  • 궁금하신 분들은 아래 글을 참고해 주세요.

https://docs.spring.io/spring-security/reference/migration-7/configuration.html

 

Configuration Migrations :: Spring Security

The Lambda DSL is present in Spring Security since version 5.2, and it allows HTTP security to be configured using lambdas. You may have seen this style of configuration in the Spring Security documentation or samples. Let us take a look at how a lambda co

docs.spring.io

 

 

csrf(AbstractHttpConfigurer::disable)
  • csrf(사이트 간 요청 위조)는 세션이나 쿠키 인증 방식의 취약점을 노린 공격 방식입니다.
  • 여기에서는 JWT 토큰 인증 방식을 사용할 것이므로 해당 공격에 대한 방어가 필요 없어 설정을 비활성화하는 것입니다.
  • 이 부분에 대한 설명만으로 글 여러 개를 작성이 가능할 정도로 방대한 분량이므로 잘 요약 정리된 블로그 글을 소개해드리겠습니다.

https://junhyunny.github.io/information/security/spring-boot/spring-security/cross-site-reqeust-forgery/

 

CSRF(Cross-Site Request Forgery) Attack and Defence

<br /><br />

junhyunny.github.io

 

.httpBasic(AbstractHttpConfigurer::disable)
  • HTTP Basic 인증은 사용자 이름과 비밀번호를 Base64로 인코딩하여 HTTP 헤더에 포함시켜 서버에 전송하는 방식으로 이것 역시 
  • JWT 토큰 인증 방식을 사용하기 때문에 비활성화합니다.

 

.formLogin(AbstractHttpConfigurer::disable)
  • 스프링 시큐리티가 기본으로 제공하는 로그인 폼 기능을 비활성화합니다.
    • 위에서 제가 보여드린 기본 로그인 페이지를 말합니다.
    • 저희는 백엔드 영역만 만들 거니까 필요 없습니다.

 

.authorizeHttpRequests((authorizeRequests) -> authorizeRequests
                .requestMatchers("/login").permitAll()
                .anyRequest().authenticated())
  • HTTP 요청에 대한 인가 규칙을 설정합니다.
    • 한 프로젝트에서 백엔드 파트는 여러 api를 만들 것이고, 클라이언트는 여러 페이지를 만들어 api를 붙이잖아요. 이때 백엔드에서 만든 여러 api 엔드포인트에 어딘가는 권한이 필요할 것이고, 어딘가는 권한이 필요 없을 텐데요.
    • 예를 들면 로그인한 상태로 접근 요청을 해야 하는 api와 그렇지 않은 api를 구분하는 설정을 하는 부분입니다.
  • 람다식을 사용하여 HTTP 요청에 대한 인가 규칙을 설정합니다.
  • reuestMatcher는 요청 api 엔드포인트를 적습니다.
  • 저의 경우에는 /login이라는 엔드포인트는 누구나 접근 가능하게 설정했습니다.
    • 로그인할 페이지를 로그인 사용자만 접근가능하게 한다면 아무도 로그인 할 수 없겠죠?
  • 로그인 외 요청은 로그인 사용자만 이용 가능한 굉장히 폐쇄적인 애플리케이션입니다ㅋ.ㅋ

 

🤔 특정 엔드포인트에 대한 자세한 설정

  • 특정 엔드포인트에 대해 굉장히 자세한 접근 권한 등을 설정할 수 있습니다.
  • 저도 전부 사용해보지는 않았지만 메서드 명으로 기능을 짐작할 수 있습니다.
    • 예를 들면 permitAll은 해당 경로는 누구나 접근 가능하다. demyAll은 누구도 접근할 수 없다. 뭐 그런 거겠죠?

 

📑 대표적인 패턴 경로 매칭

.requestMatchers("/admin") // /admin 경로와 정확히 일치하는 요청
.requestMatchers("/admin/*") // /admin/ 하위의 한 수준 경로와 매칭, 예: /admin/user
.requestMatchers("/admin/**") // /admin/ 하위의 모든 경로와 매칭, 예: /admin/user/edit
.requestMatchers("/*.html") // 루트 디렉토리의 모든 .html 파일 요청과 매칭
.requestMatchers("/admin/{regex:[a-z]+}") // 정규 표현식을 사용한 매칭
.requestMatchers(HttpMethod.GET, "/admin/**") // GET 요청에 대해 /admin/ 하위의 모든 경로와 매칭
  • 위와 같이 접근 경로에 대한 패턴 매칭을 통해 커스텀하게 설정이 가능합니다.

 

😄 예시

.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasRole("USER")
.requestMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
  • 이런 식으로 설정하면 됩니다.

.requestMatchers("/admin/**").hasRole("ADMIN")

  • /admin이 포함된 모든 엔드포인트는 로그인 사용자 중 ADMIN이라는 Role을 가진 사람만이 접근 가능하다는 설정입니다.

.requestMatchers("/user/**").hasRole("USER")

  • /user가 포함된 모든 엔드포인트는 로그인 사용자 중 USER라는 Role을 가진 사람만이 접근 가능하다는 설정입니다.

.requestMatchers("/", "/home").permitAll()

  • "/"나 "/home"은 모두 접근 가능한 엔드포인트라는 설정입니다.

.anyRequest().authenticated()

  • 이외의 모든 요청은 특별한 Role 없이 로그인한 사용자는 모두 접근 가능하다는 설정입니다.

 

.sessionManagement(sessionManagement -> sessionManagement
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
  • 스프링 시큐리티가 세션을 생성하거나 유지하지 않도록 설정하는 부분입니다.
  • 이 부분 역시 JWT 토큰 인증 방식을 사용하기 때문에 세션을 만들지 않도록 설정합니다.

 

return http.build();
  • 위에서 설정한 구성을 빌드하고 리턴합니다.

 

🏃🏻 다음으로 넘어가기

  • 현재까지의 설정으로는 아무것도 할 수가 없겠죠.
  • 이제부터는 이 설정을 적용받은 필터를 추가하고 로그인이나 로그아웃 등의 작업을 구현해야 합니다.
  • 위와 같이 addFilter와 같은 메서드 등으로 필터를 적용하고, 순서를 정할 수 있습니다.
  • 다음 글에서는 로그인 인증을 처리하는 필터를 만들어보겠습니다.

 

 

 

참고

뤼튼

https://velog.io/@sehwan24/Lamda-DSL을-이용한-HttpSecurity-WebSecurity-구성

 

728x90