[Spring Security] SecurityConfig

주재완·2024년 6월 27일
0

Spring Security

목록 보기
2/3
post-thumbnail

Spring Legacy와 Spring Boot를 사용해서 Spring Security 세팅을 하는 포스팅입니다. 앞으로는 Spring Boot 위주로 설명할 예정이지만, 과거 학습 내용인 Legacy Spring을 먼저 소개하고 Spring Boot로 진행 예정입니다.

Spring Legacy

앞으로는 Spring Boot로 진행할 예정이기에, Spring Legacy로 진행하는 것은 어떻게 진행하는지 간단히 짚고 넘어가겠습니다.

pom.xml

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.xml

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>

servlet-context.xml

웹 서블릿과 관련된 빈이 등록되는 곳입니다. 여기서 필요한 것은 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에 적은 경로에 만들어 줘야됩니다.

LoginInterceptor

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 인터페이스를 구현합니다. 역할 및 메소드는 다음과 같습니다.

  • Controller가 실행되기 전/후에 낚아채서 실행된다.
  • 로그인 유/무판단, 회원권한체크
  • preHandle(전처리) : DispatcherServlet이 컨트롤러를 호출하기 전에 낚아채는 영역
  • postHandle(후처리) : 컨트롤러에서 요청 후 DispatcherServlet으로 view정보를 리턴하는 순간 낚아채는 영역

Spring Boot

build.gradle

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'
}
  • jpa의 경우에는 쿼리를 직접 작성하지 않고 DB와 연결해서 데이터 등을 작성하는 ORM입니다. 현재 단계에서는 사용하지 않으므로 잠시 주석처리 해둡니다.
  • mustache는 JSP와 같이 HTML을 만들어주는 서버 템플릿 엔진입니다. SpringBoot에서 mustache에서 공식적으로 지원하고 있습니다.
  • 당연히 web & security 하고 있으므로 spring-web 과 spring-security 의존성 역시 추가를 해주어야 합니다.
  • lombok은 getter, setter 등을 간단히 어노테이션으로 작성할 수 있는 라이브러리입니다.
  • 마지막으로 test를 적용할 라이브러리들 (spring-test, spring-security-test, junit)을 추가해줍니다.

SecurityConfig

앞서 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();
    }
}

BCryptPasswordEncoder

기존 별도의 xml 파일로 빈 등록을 진행했었는데, Spring Boot에서는 config에서 빈 등록을 진행하는 모습입니다.

SecurityFilterChain

SecurityFilterChain 인터페이스는 HttpSecurity 객체를 파라미터로 가지는데, 이는 builder 패턴으로 각종 필터 및 기준들을 설정 가능합니다.

여기서 있는 설정은 authorizeHttpRequests, formLogin, csrf 입니다.

authorizeHttpRequests

이곳에서는 요청에 대한 인가를 설정하고 있습니다. 기본적으로 파라미터 안에는 람다 표현식이 들어가야 됩니다.

  • requestMatchers() : 해당하는 url들에 대한 조건을 처리합니다.
  • anyRequest() : 나머지 모든 url들에 대한 조건을 처리합니다. 마치 if ~ else 같은 느낌입니다.

각각의 url들을 어떻게 처리할지에 대한 기능도 존재합니다.

  • permitAll() : 모두 허용
  • hasRole("ADMIN") : "ADMIN" 권한만 허용
  • hasAnyRole("ADMIN", "USER") : "ADMIN", "USER"에 해당하면 권한 허용
  • authenticated() : 인증이 필요

formLogin

formLogin은 Spring Security에서 제공하는 인증 방식입니다. 역시 람다식이 들어가야 하고 사용할 수 있는 기능들은 아래와 같습니다.

  • loginPage(“/login.html") : 사용자 정의 로그인 페이지
  • defaultSuccessUrl("/home) : 로그인 성공 후 이동 페이지
  • failureUrl("/login.html?error=true“) : 로그인 실패 후 이동 페이지
  • usernameParameter("username") : 아이디 파라미터명 설정
  • passwordParameter(“password”) : 패스워드 파라미터명 설정
  • loginProcessingUrl(“/login") : 로그인 Form Action Url
  • successHandler(loginSuccessHandler()) : 로그인 성공 후 핸들러
  • failureHandler(loginFailureHandler()) : 로그인 실패 후 핸들러
  • permitAll() : 로그인 창 자체는 특별한 인증 없어도 누구나 접속할 수 있어야 됩니다. 그래서 모든 접속을 허용하도록 permitAll()을 붙여줍니다.

다시 코드를 보면,

                .formLogin((auth) -> auth.loginPage("/login")
                        .loginProcessingUrl("/loginProc")
                        .permitAll())

이 부분인데, 이 부분은 아래와 같이 해석 가능합니다.

모든 접속이 허용된 사용자 정의 로그인 페이지 url은 "/login"이고, Form tag에서 action url은 "/loginProc"로 설정하였습니다.

csrf

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 코드들을 작성할 필요가 없습니다.

참고

profile
언제나 탐구하고 공부하는 개발자, 주재완입니다.

0개의 댓글

관련 채용 정보