Spring Legacy와 Spring Boot를 사용해서 Spring Security 세팅을 하는 포스팅입니다. 앞으로는 Spring Boot 위주로 설명할 예정이지만, 과거 학습 내용인 Legacy Spring을 먼저 소개하고 Spring Boot로 진행 예정입니다.
앞으로는 Spring Boot로 진행할 예정이기에, Spring Legacy로 진행하는 것은 어떻게 진행하는지 간단히 짚고 넘어가겠습니다.
Maven으로 의존성 추가해주기 위해 pom.xml에 다음과 같은 것을 적어줍니다.
<!-- spring-security-core -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.5.2</version>
</dependency>
<!-- spring-security-web -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.5.2</version>
</dependency>
<!-- spring-security-config -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.5.2</version>
</dependency>
spring security를 사용해주기 위해서는 빈 등록을 해주어야됩니다. 우선, spring security에 있는 BCryptPasswordEncoder
를 사용하기 위해서는 다음과 같이 등록을 해주면 됩니다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-5.5.xsd">
<bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="bcryptPasswordEncoder"/>
</beans>
웹 서블릿과 관련된 빈이 등록되는 곳입니다. 여기서 필요한 것은 Interceptor를 설정할건데, 여기에 걸리는 url들은 전부 LoginInterceptor를 거쳐서 작업해주도록 할겁니다.
<interceptors>
<interceptor>
<mapping path="/admin"/>
<mapping path="/my/**"/>
<beans:bean class="com.example.spring.common.intercepter.LoginIntercepter" id="LoginIntercepter"/>
</interceptor>
</interceptors>
여기서 /admin
과 /my
로 시작하는 경로들은 모두 LoginInterceptor를 거치도록 할 예정입니다. 그러기 위해서는 LoginInterceptor를 class에 적은 경로에 만들어 줘야됩니다.
package com.spring.staez.common.intercepter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
public class LoginIntercepter implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//return : true => 기존요청흐름대로 진행(Controller로 이동)
//return : false => 요청 중단 후 반환
HttpSession session = request.getSession();
if (session.getAttribute("loginUser") != null) {
return true;
} else {
session.setAttribute("alertMsg", "로그인 후 이용가능한 서비스입니다.");
response.sendRedirect(request.getContextPath() + "/loginForm.me");
return false;
}
}
}
LoginInterceptor는 HandlerInterceptor 인터페이스를 구현합니다. 역할 및 메소드는 다음과 같습니다.
Spring Legacy에서 Maven으로 의존성 추가해주기 위해 pom.xml이 있다면, Spring Boot에는 build.gradle이 있습니다. 다음과 같이 의존성 설정을 해줍니다.
dependencies {
// implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-mustache'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
앞서 servlet-context, LoginInterceptor, spring-security 등의 내용들을 Spring Boot에서는 @Configuration
을 붙여서 설정해줍니다. 이 때 로그인 필터 등등도 설정을 같이 해줍니다.
아래는 코드이고, 각각의 요소에 대해서는 아래에 설명하겠습니다.
package com.example.testsecurity.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.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((auth) -> auth
.requestMatchers("/", "/login").permitAll()
.requestMatchers("/admin").hasRole("ADMIN")
.requestMatchers("/my/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated())
.formLogin((auth) -> auth.loginPage("/login")
.loginProcessingUrl("/loginProc")
.permitAll())
.csrf((auth)->auth.disable());
return http.build();
}
}
기존 별도의 xml 파일로 빈 등록을 진행했었는데, Spring Boot에서는 config에서 빈 등록을 진행하는 모습입니다.
SecurityFilterChain
인터페이스는 HttpSecurity
객체를 파라미터로 가지는데, 이는 builder 패턴으로 각종 필터 및 기준들을 설정 가능합니다.
여기서 있는 설정은 authorizeHttpRequests
, formLogin
, csrf
입니다.
이곳에서는 요청에 대한 인가를 설정하고 있습니다. 기본적으로 파라미터 안에는 람다 표현식이 들어가야 됩니다.
각각의 url들을 어떻게 처리할지에 대한 기능도 존재합니다.
formLogin은 Spring Security에서 제공하는 인증 방식입니다. 역시 람다식이 들어가야 하고 사용할 수 있는 기능들은 아래와 같습니다.
다시 코드를 보면,
.formLogin((auth) -> auth.loginPage("/login")
.loginProcessingUrl("/loginProc")
.permitAll())
이 부분인데, 이 부분은 아래와 같이 해석 가능합니다.
모든 접속이 허용된 사용자 정의 로그인 페이지 url은 "/login"이고, Form tag에서 action url은 "/loginProc"로 설정하였습니다.
CSRF(Cross site Request forgery) 는 사이트간 위조 요청으로 공격자가 인증된 브라우저에 저장된 쿠키의 세션 정보를 활용하여 웹 서버에 사용자가 의도하지 않은 요청을 전달하는 것을 의미합니다. 이를 disabled 해주는데, 왜 활성화하지 않고 비활성화해서 허용하는 것일까?
Spring Security 공식 문서에는 다음과 같이 설명하고 있습니다.
When should you use CSRF protection? Our recommendation is to use CSRF protection for any request that could be processed by a browser by normal users. If you are creating a service that is used only by non-browser clients, you likely want to disable CSRF protection.
non-brower clients만을 위한 서비스이면 이를 disabled 해도 좋다고 합니다. 요즘 RestAPI를 사용하는 서비스의 경우 session 인증이 아닌 token 인증 방식을 사용합니다. token 인증 방식은 session 처럼 서버에 저장하는 방식이 아니기 때문에 굳이 불필요한 csrf 코드들을 작성할 필요가 없습니다.