겉바속촉
[점프투스프링부트] 3-05. Spring Security 본문
점프투스프링부트 3-05
목표 : 스프링 시큐리티에 대해 알아보기
스프링부트는 회원가입과 로그인을 도와주는 스프링 시큐리티(Spring Security)를 사용할수 있다.
SBB도 스프링 시큐리티를 사용하여 회원가입과 로그인 기능을 만들 것이다.
그 전에 스프링 시큐리티에 대해서 간단하게 알아보고 필요한 설정도 진행해 보자.
스프링 시큐리티란?
스프링 시큐리티는 스프링 기반 애플리케이션의 인증과 권한을 담당하는 스프링의 하위 프레임워크이다.
- 인증(Authenticate)은 로그인을 의미한다.
- 권한(Authorize)은 인증된 사용자가 어떤 것을 할 수 있는지를 의미한다.
스프링 시큐리티 설치
스프링 시큐리티 사용을 위해 다음과 같이 build.gradle 파일을 수정하자.
[파일명:/sbb/build.gradle]
(... 생략 ...)
dependencies {
(... 생략 ...)
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.1.RELEASE'
}
(... 생략 ...)
스프링 시큐리티와 이와 관련된 타임리프 라이브러리를 사용하도록 설정했다.
"Refresh Gradle Project"를 수행하여 필요한 라이브러리를 설치한 후 로컬서버도 재시작 하자.
< thymeleaf-extras-springsecurity6 >
thymeleaf-extras-springsecurity6 패키지를 사용하기 위해 뒤에 3.1.1.RELEASE과 같은 버전 정보를 추가했다.
thymeleaf-extras-springsecurity6 패키지는 스프링부트가 자체적으로 관리하는 패키지이기 때문에 버전 정보가 필요없지만 현재 사용중인 스프링부트 버전인 3.0.0 버전에서는 위와 같은 버전 정보를 입력하지 않으면 오류가 발생한다.
만약 버전 정보를 제거하고 사용하더라도 오류가 없다면 버전 정보 없이 사용하기 바란다.
스프링 시큐리티 설정
스프링 시큐리티를 설치하고 로컬서버를 재시작한 후에 SBB의 질문 목록 화면에 접속해 보자.
아마도 다음과 같은 로그인 화면이 나타나서 깜짝 놀랄 것이다.
스프링 시큐리티는 기본적으로 인증되지 않은 사용자는 서비스를 사용할 수 없게끔 되어 있다.
따라서 인증을 위한 로그인 화면이 나타나는 것이다.
하지만 이러한 기본 기능은 SBB에 그대로 적용하기에는 곤란하므로 시큐리티의 설정을 통해 바로 잡아야 한다.
SBB는 로그인 없이도 게시물을 조회할 수 있어야 한다.
다음과 같이 SecurityConfig.java 파일을 작성하자.
[파일명:/sbb/src/main/java/com/mysite/sbb/SecurityConfig.java]
package com.mysite.sbb;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests().requestMatchers(
new AntPathRequestMatcher("/**")).permitAll()
;
return http.build();
}
}
@Configuration은 스프링의 환경설정 파일임을 의미하는 애너테이션이다.
여기서는 스프링 시큐리티의 설정을 위해 사용되었다.
@EnableWebSecurity는 모든 요청 URL이 스프링 시큐리티의 제어를 받도록 만드는 애너테이션이다.
@EnableWebSecurity 애너테이션을 사용하면 내부적으로 SpringSecurityFilterChain이 동작하여 URL 필터가 적용된다.
스프링 시큐리티의 세부 설정은 SecurityFilterChain 빈을 생성하여 설정할 수 있다.
다음 문장은 모든 인증되지 않은 요청을 허락한다는 의미이다.
따라서 로그인을 하지 않더라도 모든 페이지에 접근할 수 있다.
http.authorizeHttpRequests().requestMatchers(
new AntPathRequestMatcher("/**")).permitAll();
이렇게 스프링 시큐리티 설정 파일을 구성하면 이제 질문 목록, 질문 답변 등의 기능을 이전과 동일하게 사용할 수 있다.
H2 콘솔
하지만 스프링 시큐리티를 적용하면 H2 콘솔 로그인시 다음과 같은 403 Forbidden 오류가 발생한다.
403 Forbidden 오류가 발생하는 이유는 스프링 시큐리티를 적용하면 CSRF 기능이 동작하기 때문이다.
< CSRF란? >
CSRF(cross site request forgery)는 웹 사이트 취약점 공격을 방지를 위해 사용하는 기술이다.
스프링 시큐리티가 CSRF 토큰 값을 세션을 통해 발행하고 웹 페이지에서는 폼 전송시에 해당 토큰을 함께 전송하여 실제 웹 페이지에서 작성된 데이터가 전달되는지를 검증하는 기술이다.
이 오류를 해결하기 전에 질문 등록 화면을 열고 브라우저의 소스보기 기능을 이용하여 질문 등록 화면의 소스를 잠시 확인해 보자.
다음과 같은 input 엘리먼트가 폼(form) 태그 밑에 자동으로 생성된 것을 확인할 수 있다.
<input type="hidden" name="_csrf" value="0d609fbc-b102-4b3f-aa97-0ab30c8fcfd4"/>
스프링 시큐리티에 의해 위와 같은 CSRF 토큰이 자동으로 생성된다.
즉, 스프링 시큐리티는 이렇게 발행한 CSRF 토큰의 값이 정확한지 검증하는 과정을 거친다.
(만약 CSRF 값이 없거나 해커가 임의의 CSRF 값을 강제로 만들어 전송하는 악의적인 URL 요청은 스프링 시큐리티에 의해 블록킹 될 것이다.)
그런데 H2 콘솔은 이와 같은 CSRF 토큰을 발행하는 기능이 없기 때문에 위와 같은 403 오류가 발생하는 것이다.
H2 콘솔은 스프링과 상관없는 일반 애플리케이션이다.
스프링 시큐리티가 CSRF 처리시 H2 콘솔은 예외로 처리할 수 있도록 다음과 같이 설정 파일을 수정하자.
(... 생략 ...)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests().requestMatchers(
new AntPathRequestMatcher("/**")).permitAll()
.and()
.csrf().ignoringRequestMatchers(
new AntPathRequestMatcher("/h2-console/**"))
;
return http.build();
}
}
다음과 같은 설정을 추가했다.
- and() - http 객체의 설정을 이어서 할 수 있게 하는 메서드이다.
- csrf().ignoringRequestMatchers(new AntPathRequestMatcher("/h2-console/**")) - /h2-console/로 시작하는 URL은 CSRF 검증을 하지 않는다는 설정이다.
이렇게 수정하고 다시 H2 콘솔에 접속해 보자.
이제 CSRF 검증이 예외 처리되어 로그인은 수행되지만 다음처럼 화면이 깨져보인다.
이 오류가 발생하는 원인은 H2 콘솔의 화면이 frame 구조로 작성되었기 때문이다.
스프링 시큐리티는 사이트의 콘텐츠가 다른 사이트에 포함되지 않도록 하기 위해 X-Frame-Options 헤더값을 사용하여 이를 방지한다.
(clickjacking 공격을 막기위해 사용함)
이 문제를 해결하기 위해 다음과 같이 설정 파일을 수정하자.
package com.mysite.sbb;
(... 생략 ...)
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
(... 생략 ...)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests().requestMatchers(
new AntPathRequestMatcher("/**")).permitAll()
.and()
.csrf().ignoringRequestMatchers(
new AntPathRequestMatcher("/h2-console/**"))
.and()
.headers()
.addHeaderWriter(new XFrameOptionsHeaderWriter(
XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN))
;
return http.build();
}
}
위 처럼 URL 요청시 X-Frame-Options 헤더값을 sameorigin으로 설정하여 오류가 발생하지 않도록 했다.
X-Frame-Options 헤더의 값으로 sameorigin을 설정하면
frame에 포함된 페이지가 페이지를 제공하는 사이트와 동일한 경우에는 계속 사용할 수 있다.
이제 다시 H2 콘솔로 로그인하면 정상 동작하는 것을 확인할 수 있을 것이다.
'IT 일기 (상반기) > SPRING 기초' 카테고리의 다른 글
[점프투스프링부트] 3-07. 로그인 & 로그아웃 (0) | 2023.03.03 |
---|---|
[점프투스프링부트] 3-06. 회원가입 (0) | 2023.03.03 |
[점프투스프링부트] 3-04. 답변 개수 표시 (0) | 2023.03.03 |
[점프투스프링부트] 3-03. 게시물에 일련번호 추가 (2) | 2023.03.03 |
[점프투스프링부트] 3-02. 페이징 (0) | 2023.03.03 |