Spring Security
- Spring MVC 기반 애플리케이션의 인증(Authentication)과 인가(Authorization or 권한 부여) 기능을 지원하는 보안 프레임워크
다양한 유형(폼 로그인 인증, 토큰 기반 인증, OAuth 2 기반 인증, LDAP 인증)의 사용자 인증 기능 적용
애플리케이션 사용자의 역할(Role)에 따른 권한 레벨 적용
애플리케이션에서 제공하는 리소스에 대한 접근 제어
민감한 정보에 대한 데이터 암호화
SSL 적용 (HTTP'S')
일반적으로 알려진 웹 보안 공격 차단
Principal(주체)
Authentication(인증)
Credential(신원 증명 정보)
Authorization(인가 또는 권한 부여)
Authentication(인증)
이 정상적으로 수행된 사용자에게 하나 이상의 권한(authority)을 부여하여 특정 애플리케이션의 특정 리소스에 접근할 수 있게 허가하는 것Authentication(인증)
이후에 수행되어야 하며 권한은 일반적으로 역할(Role) 형태로 부여Access Control(접근 제어)
자세한 실행 코드와 메서드 역할에 대한 설명은 Github에서 확인 가능
InMemory User로 인증하기 -> 커스텀 로그인 페이지 지정하기 -> request URI에 접근 권한 부여 -> 관리자 권한을 가진 사용자 정보 추가 -> 로그인 한 사용자 아이디 표시 및 사용자 로그아웃 기능 추가
위와 같은 단계로 폼 로그인 인증 방식을 구현해 봤다.
package com.codestates.config;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration // Spring Security Configuration 적용 = 인증 방식, 웹 페이지에 대한 접근 권한 설정 가능
public class SecurityConfiguration {
// HttpSecurity클래스 = HTTP 요청에 대한 보안 설정을 구성하기 위한 핵심 클래스
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 권한부여
http
.csrf().disable() // CSRF 공격에 대한 Spring Security에 대한 설정을 비활성화 (로컬 환경에서 테스트하기 때문)
.formLogin() // 기본적인 인증 방법을 폼 로그인 방식으로 지정
.loginPage("/auths/login-form") // 만들어 둔 커스텀 로그인 페이지를 사용하도록 설정
.loginProcessingUrl("/process_login") // 로그인 인증 요청을 수행할 요청 URL을 지정
.failureUrl("/auths/login-form?error") // 로그인 실패시 뜨게 할 화면 지정
.and()
.logout() // 로그아웃 설정을 위한 LogoutConfigurer 를 리턴
.logoutUrl("/logout") // 로그아웃을 수행하기 위한 request URL을 지정 ("/" = 홈, 메인화면)
.logoutSuccessUrl("/") // 로그아웃 이후 리다이렉트 할 URL 지정
.and() // Spring Security 보안 설정을 메서드 체인 형태로 구성
.exceptionHandling().accessDeniedPage("/auths/access-denied") // 권한이 없는 사용자가 특정 request URI에 접근할 경우 발생하는 에러를 처리하기 위한 페이지 설정
.and()
.authorizeHttpRequests(authorize -> authorize // request URI에 대한 접근 권한을 부여
.antMatchers("/orders/**").hasRole("ADMIN") // ADMIN을 부여받은 사용자만 /orders로 시작하는 모든 URL에 접근할 수 있게 설정 (order 하위 URL 모두 접근 가능, *한개만 쓰면 /Order의 하위 URL depth가 1인 URL만 허용
.antMatchers("/members/my-page").hasRole("USER") // USER Role을 부여받은 사용자만 /members/my-page URL에 접근할 수 있음
.antMatchers("⁄**").permitAll() // 앞에서 지정한 URL 이외의 나머지 모든 URL은 Role에 상관없이 접근이 가능
);
return http.build();
}
@Bean
public UserDetailsManager userDetailsService() { // 정보 추가
UserDetails userDetails = // user 정보를 포함하는 인터페이스
User.withDefaultPasswordEncoder() // 디폴트 패스워드 인코더를 이용해 사용자 패스워드를 암호화
.username("wjwee9@gmail.com") // username 설정
.password("1111") // 비번 설정
.roles("USER") // 역할을 USER로 설정
.build();
UserDetails admin = // 관리자 권한을 갖는 사용자 추가
User.withDefaultPasswordEncoder()
.username("admin@gmail.com")
.password("2222")
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(userDetails, admin); // 위에서 설정한 userDetails을 구현해서 UserDetailsManager객체로 만들어 리턴
}
}
Spring Boot 기반의 애플리케이션에 Spring Security를 적용하기 위해 build.gradle에 spring-boot-starter-security 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
// Spring Security 적용하기 위해 추가
}
Spring Security Configuration을 적용하면 인증 방식과 웹 페이지에 대한 접근 권한을 설정할 수 있다.
더 구체적인 URL 경로부터 접근 권한을 부여한 다음 덜 구체적인 URL 경로에 접근 권한을 부여해야 한다!! (권한부여 순서 중요!)
Spring Security를 이용한 보안 설정은 HttpSecurity를 파라미터로 가지고, SecurityFilterChain 을 리턴하는 Bean을 생성하면 된다.
HttpSecurity를 통해 Spring Security에서 지원하는 보안 설정을 구성할 수 있다.
로컬 환경에서 Spring Security를 테스트하기 위해서는 CSRF 설정을 비활성화해야 한다.
InMemoryUserDetailsManager 를 이용해 데이터베이스 연동없이 테스트 목적의 InMemory User를 생성할 수 있다.
Spring Security에서는
SimpleGrantedAuthority
를 사용해 Role 베이스 형태의 권한을 지정할 때ROLE_ + 권한 명
형태로 지정해 주어야 한다. 그렇지 않을 경우 적절한 권한 매핑이 이루어지지 않는다.//예시 public List<GrantedAuthority> createAuthorities(List<String> roles) { // DB에 저장된 Role을 기반으로 권한 정보 생성 return roles.stream() .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) .collect(Collectors.toList()); } // SimpleGrantedAuthority 객체를 생성할 때 생성자 파라미터로 넘겨주는 값이 “ USER" 또는 “ADMIN"으로 넘겨주면 안 되고 // “ROLE_USER" 또는 “ROLE_ADMIN" 형태로 넘겨주어야 한다.
Spring Security에서는 Clickjacking 공격을 막기 위해 기본적으로 frameOptions() 기능이 활성화 되어 있으며 디폴트 값은
DENY
다.
http.headers().frameOptions().sameOrigin()
.frameOptions().sameOrigin()
= 동일 출처로부터 들어오는 request만 페이지 렌더링을 허용
UserDetailsService
인터페이스를 implements 하는 구현 클래스는loadUserByUsername(String username)
이라는 추상 메서드를 구현해야 한다.// loadUserByUsername() 구현 예시 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Optional<Member> optionalMember = memberRepository.findByEmail(username); Member findMember = optionalMember.orElseThrow(() -> new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND)); // orElseThrow() => 값이 null이면 오류 던지는 메서드 Collection<? extends GrantedAuthority> authorities = authorityUtils.createAuthorities(findMember.getEmail()); return new User(findMember.getEmail(), findMember.getPassword(), authorities); }
UserDetails
인터페이스
UserDetails
는UserDetailsService
인터페이스에 의해 로드(load)되어 인증을 위해 사용되는 핵심 User 정보를 표현하는 인터페이스다.UserDetails
인터페이스의 구현체는 Spring Security에서 보안 정보 제공을 목적으로 직접 사용되지는 않고,Authentication
객체로 캡슐화 되어 제공된다.
- Spring Security에서 사용되는 인증 정보를 담고 있는 객체
- 포함하는 정보는 아래와 같다.
- Principal: 인증된 사용자의 주체를 나타내는 객체
- Credentials: 인증에 사용된 자격증명 정보
- Authorities: 인증된 사용자가 가지고 있는 권한 정보를 나타내는 객체
- Details: 인증과 관련된 추가 정보를 포함하는 객체
@ElementCollection
@ElementCollection
애너테이션을 추가하면 User 권한 정보와 관련된 별도의 엔티티 클래스를 생성하지 않아도 간단하게 매핑 처리가 된다.
Custom AuthenticationProvider에서
AuthenticationException
이 아닌 다른 종류의Exception
이 발생할 경우에는 꼭 AuthenticationException을 rethrow 하도록 코드를 구성해야 한다.
Spring Security는 사용자의 크리덴셜(Credential, 자격증명을 위한 구체적인 수단)을 암호화하기 위한 PasswordEncoder를 제공하며, PasswordEncoder는 다양한 암호화 방식을 제공하며, Spring Security에서 지원하는 PasswordEncoder의 디폴트 암호화 알고리즘은 bcrypt이다.
패스워드 같은 민감한(sensitive) 정보는 반드시 암호화되어 저장되어야 한다.
Spring Security에서 SimpleGrantedAuthority
를 사용해 Role 베이스 형태의 권한을 지정할 때 ROLE_ + 권한 명
형태로 지정해 주어야 한다.
(위에 정리해 둔거 참고)
Spring Security에서는 Spring Security에서 관리하는 User 정보를 UserDetails로 관리한다.
UserDetails
는 UserDetailsService
에 의해 로드(load)되는 핵심 User 정보를 표현하는 인터페이스다.
UserDetailsService
는 User 정보를 로드(load)하는 핵심 인터페이스다.일반적으로 Spring Security에서는 인증을 시도하는 주체를 User(비슷한 의미로 Principal도 있음)라고 부른다. Principal은 User의 더 구체적인 정보를 의미하며, 일반적으로 Username을 의미한다.
Custom UserDetailsService를 사용해 로그인 인증을 처리하는 방식은 Spring Security가 내부적으로 인증을 대신 처리해주는 방식이다.
AuthenticationProvider는 Spring Security에서 클라이언트로부터 전달받은 인증 정보를 바탕으로 인증된 사용자인지를 처리하는 Spring Security의 컴포넌트이다.
크리덴셜(Credential)
- 해당 사용자를 증명하기 위한 구체적인 수단
- 일반적으로 사용자의 패스워드가 크리덴셜에 해당
서블릿 필터(Servlet Filter)
- 웹 애플리케이션에서 HTTP 요청과 응답에 대한 전처리 및 후처리를 수행하는 중간 컴포넌트
- 자바에서 제공하는 API
- javax.servlet 패키지에 인터페이스 형태로 정의되어 있음
서블릿 필터는 아래 그림과 같이 하나 이상의 필터들을 연결해 필터 체인(Filter Chain)을 구성할 수 있다.
생성한 필터들은 각각의 작업을 수행한 뒤, HttpServlet을 통해 DispatcherServlet에 요청이 전달되며, 반대로 DispatcherServlet에서 전달한 응답에 대해서도 역시 작업을 수행할 수 있다.
Spring Security의 필터
- 클라이언트의 요청을 중간에서 가로챈 뒤, 보안에 특화된 작업을 처리하는 역할을 한다.
DelegatingFilterProxy
- Bean으로 등록된 Spring Security의 필터를 사용하는 시작점
- 보안과 관련된 어떤 작업을 처리하는 것이 아니라 서블릿 컨테이너 영역의 필터와 ApplicationContext에 Bean으로 등록된 필터들을 연결해주는 브릿지 역할
FilterChainProxy
- Spring Security에서 보안을 위한 작업을 처리하는 필터의 모음
- Spring Security의 Filter를 사용하기 위한 진입점
- FilterChainProxy부터 Spring Security에서 제공하는 보안 필터들이 필요한 작업을 수행한다.
Servlet FilterChain은 요청 URI path를 기반으로 HttpServletRequest를 처리한다. 따라서 클라이언트가 서버 측 애플리케이션에 요청을 전송하면 서블릿 컨테이너는 요청 URI의 경로를 기반으로 어떤 Filter와 어떤 Servlet을 매핑할지 결정한다.
Filter는 Filter Chain 안에서 순서를 지정할 수 있으며 지정한 순서에 따라서 동작하게 할 수 있다.
Filter Chain에서 Filter의 순서는 매우 중요하며 Spring Boot에서 여러 개의 Filter를 등록하고 순서를 지정하기 위해서는 다음과 같은 두 가지 방법을 적용할 수 있다.
@Order
애너테이션을 추가하거나 Orderd
인터페이스를 구현해서 Filter의 순서를 지정할 수 있다.FilterRegistrationBean
을 이용해 Filter의 순서를 명시적으로 지정할 수 있다.import javax.servlet.*;
import java.io.IOException;
public class FirstFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException { // 생성한 Filter에 대한 초기화 작업을 진행하는 메서드
}
@Override
public void doFilter(ServletRequest request, // 해당 Filter가 처리하는 실질적인 로직을 구현하는 메서드
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
// 다음 필터로 넘어가기 전에 전처리 작업 수행
chain.doFilter(request, response); // 다음 필터 작업 수행
// 후처리 작업 수행
}
@Override
public void destroy() { // Filter가 컨테이너에서 종료될 때 호출되는 메서드
// 주로 Filter가 사용한 자원을 반납하는 처리 등의 로직을 작성
}
}
위에서 언급한 filter 등록 + 순서지정
을 할 수 있는 방법 중 FilterRegistrationBean
을 이용한 방법을 직접 해봤다.
두개의 필터를 만들어 아래와 같이 원하는 순서대로 작동하도록 registrationBean.setOrder()
메서드를 이용해 필터 작동 순서를 지정했다.
@Configuration은 Spring에서 Bean을 정의하기 위한 Annotation입니다. 이 Annotation을 사용하면 자바 기반의 설정 클래스를 만들 수 있습니다. @Configuration은 자바 기반의 설정 클래스를 만들 때 사용되며, 이 클래스 내부에서 @Bean 어노테이션을 이용하여 Bean을 등록할 수 있습니다.
@Configuration을 사용하면 자바 기반의 설정 파일을 만들 수 있습니다. 자바 기반의 설정 파일은 XML 파일보다 가독성이 높고, 더욱 간편하게 Bean을 정의할 수 있습니다. 또한, @Configuration은 스프링 컨테이너에 의해 처리되기 때문에, 다양한 환경에서 빈을 생성할 수 있습니다.
@Configuration 클래스는 다음과 같은 특징을 가집니다.
@Configuration은 클래스에 사용되는 Annotation이며, @Bean은 메소드에 사용되는 Annotation입니다. @Configuration 클래스 내부에서 @Bean 어노테이션을 이용하여 Bean을 등록할 수 있습니다.
@Configuration을 사용한 예시를 보겠습니다.
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserServiceImpl();
}
@Bean
public UserRepository userRepository() {
return new UserRepositoryImpl();
}
}
위의 코드에서 AppConfig 클래스는 @Configuration 어노테이션을 가지고 있습니다. 이 클래스 내부에서는 @Bean 어노테이션을 이용하여 UserService와 UserRepository Bean을 등록하고 있습니다.
@Configuration은 자바 기반의 설정 파일을 만들기 위한 Annotation입니다. 이를 사용하면 XML 파일보다 더욱 간편하게 Bean을 정의할 수 있습니다. 또한, @Configuration은 다양한 환경에서 빈을 생성할 수 있기 때문에, 스프링에서 많이 사용되는 Annotation 중 하나입니다.
세션 고정 공격(Session Fixation Attack)은 인증된 사용자의 세션 ID를 탈취하여 공격자가 세션 ID를 이용해 인증을 우회하여 원하는 작업을 수행하는 공격 기법입니다. 이 공격 기법은 인터넷 뱅킹, 쇼핑몰 등에서 중요한 역할을 하기 때문에 매우 위험합니다.
세션 고정 공격의 주요 목적은 인증된 세션 ID를 탈취하는 것입니다. 이를 통해 공격자는 인증된 사용자의 권한으로 원하는 작업을 수행할 수 있습니다. 이를 이용해 공격자는 사용자 계정을 탈취하거나 중요한 정보를 탈취할 수 있습니다.
다음은 세션 고정 공격을 예방하는 방법입니다.
세션 ID 생성: 무작위로 세션 ID를 생성하여 사용합니다. 이렇게 하면 공격자가 세션 ID를 추측하기 어렵습니다.
새로운 세션 ID 할당: 로그인, 로그아웃, 권한 변경 등과 같은 중요한 동작이 수행될 때마다 새로운 세션 ID를 할당합니다. 이렇게 하면 공격자가 기존 세션 ID를 이용해 공격할 수 없습니다.
IP 주소 확인: 인증된 사용자의 IP 주소를 확인하여 다른 IP 주소에서 로그인 시도가 있을 경우 세션을 무효화합니다. 이렇게 하면 공격자가 다른 장소에서 로그인할 경우 세션을 탈취할 수 없습니다.
세션 고정 공격은 매우 위험한 공격 기법 중 하나입니다. 따라서 이를 예방하기 위해 적극적인 대응이 필요합니다. 세션 ID 생성, 새로운 세션 ID 할당, IP 주소 확인 등과 같은 방법으로 공격을 예방할 수 있습니다.
최근 웹사이트 보안 이슈 중 하나인 클릭재킹 공격에 대해 알아보자. 클릭재킹 공격은 웹사이트를 방문하는 사용자가 의도하지 않은 클릭을 유도하여 해커가 원하는 악성코드를 실행시키는 공격 기법이다. 이를 통해 해커는 사용자의 브라우저를 제어하거나 개인정보를 탈취할 수 있다.
UI 위조형 클릭재킹은 사용자가 원래 클릭하려고 했던 버튼, 링크, 이미지 등을 흉내내어 사용자를 속이는 기법이다. 예를 들어, 클릭하려고 했던 버튼 위에 미세한 크기로 다른 버튼을 덮어놓은 후 사용자가 그 버튼을 클릭하도록 유도한다.
페이지 위조형 클릭재킹은 사용자가 클릭하려고 했던 링크를 다른 페이지로 유도하여 사용자를 속이는 기법이다. 예를 들어, 사용자가 A 사이트에서 링크를 클릭하려고 하지만, 실제로는 B 사이트로 연결되도록 한다.
CAPTCHA는 자동화된 스크립트나 봇의 공격을 막기 위해 사용되는 방법 중 하나이다. 사용자가 사람임을 인증하기 위해 이미지나 퍼즐 등의 문제를 풀도록 하는 방식이다.
콘텐츠 미리보기 기능은 사용자가 클릭하기 전에 해당 페이지의 콘텐츠를 미리 확인할 수 있는 기능이다. 이를 통해 사용자는 클릭해야 할 링크인지를 판단할 수 있으며, 클릭재킹 공격을 방지할 수 있다.
HTTP 요청 메소드 제한은 클릭재킹 공격을 방지하기 위해 사용되는 방법 중 하나이다. HTTP 요청 메소드 중 GET, POST, HEAD를 제한함으로써, 클릭재킹 공격을 방어할 수 있다.
Referer 검증은 클릭재킹 공격을 방지하기 위해 사용되는 방법 중 하나이다. Referer 헤더를 검증하여 사용자가 의도한 사이트에서 링크를 클릭했는지 여부를 판단할 수 있다.