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

[ Spring Boot ] Spring Security - 기본 개념 및 예제

by 환이s 2023. 9. 25.


취준 활동을 끝내고 백엔드 개발자로 경력을 쌓기 위한 회사를 찾기 위해 이곳저곳 면접을 다니고, 회사 소스 코드 파악 및 프로젝트 투입 준비를 하다 보니 블로그를 소홀하게 관리했네요.. 오늘부터 다시 마음 잡고 시작해보려고 합니다.

 

대부분의 시스템에서는 회원의 관리를 하고 있고, 그에 따른 인증(Authentication)과 인가(Authorization)에 대한 처리를 해주어야 하는데, 오늘은 Spring에서 별도의 프레임워크를 제공하는 Spring Security에 대한 개념부터 Validation-Check 로직 구현까지 Security 포스팅을 작성해 보겠습니다.

 

입사 후 코딩 실력 체크를 위한 3가지의 과제를 진행하면서 기획 부분부터 잡고 시작했던 security 기능에 대해 자세히 파해쳐 봅시다.

 


Spring Security란?

 

Spring Security는 구글링을 통해서 대체적으로 개념에 대해 자세히 정리되어 포스팅된 라이브러리라고 생각하는데, Spring 기반의 애플리케이션의 보안(인증, 권한, 인가 등)을 담당하는 Spring 하위 프레임워크입니다.

 

더 자세히는 Spring Security를 통해서 Fiter 흐름에 따라 처리하고 있다고 말할 수도 있는데, Filter는 Dispatcher Servlet으로 가기 전에 적용되므로 가장 먼저 URL 요청을 받지만, Interceptor는 Dispatcher와 Controller사이에 위치한다는 점에서 적용 시기의 차이가 있습니다.

 

Spring Security는 보안과 관련해서 체계적으로 많은 옵션을 제공해 주기 대문에 개발자 입장에서는 일일이 보안 관련 로직을 작성하지 않아도 된다는 장점이 있습니다.

 

 

Spring Security는 위 사진처럼 각각의 역할에 맞는 작업을 처리하는 여러 개의 필터들이 체인 형태로 구성되어 순서에 따라 순차적으로 수행하는데,  그전에 먼저 알고 넘어가야 하는 인증 관련 절차에 대해 알아야 합니다.

 

Authorization(인증), Authentication(인가)

 

Spring Security는 기본적으로 인증(Authorization) 절차를 거친 후 인가(Authentication) 절차를 진행하는데, 인가 과정에서 해당 리소스에 대한 접근 권한이 있는지 확인을 합니다.

 

  • Authorization(인증) : 해당 사용자가 본인이 맞는지를 확인하는 절차

 

  • Authentication(인가) : 인증된 사용자가 요청한 자원에 접근 가능한 지 결정하는 절차

 

이러한 인증(Authorization)과, 인가(Authentication)를 위해 ID는 Principal로, PassWord는 Credential를 사용하는 Credential 기반의 인증 방식을 사용합니다.

 

여기서 말하는 Principal은 접근 주체로서, 보호받는 Resource에 접근하는 대상을 의미하고, Credential은 Resource에 접근하는 대상의 비밀번호를 의미합니다.

 

그렇다면 순차적으로 어떻게 수행될까요?? 

간단하게 순서를 정리하자면 다음과 같습니다.

 

  1. 사용자가 아이디 비밀번호로 로그인을 요청합니다.
  2. AuthenticationFilter에서 UsernamePasswordAuthenticationToken을 생성하여 AuthenticaionManager에게 전달합니다.
  3. AuthenticaionManager는 등록된 AuthenticaionProvider(들)을 조회하여 인증을 요구합니다.
  4. AuthenticaionProvider는 UserDetailsService를 통해 입력받은 아이디에 대한 사용자 정보를 DB에서 조회합니다.
  5. 입력받은 비밀번호를 암호화하여 DB의 비밀번호화 매칭되는 경우 인증이 성공된 UsernameAuthenticationToken을 생성하여 AuthenticaionManager로 반환해 줍니다.
  6. AuthenticaionManager는 UsernameAuthenticaionToken을 AuthenticaionFilter로 전달합니다.
  7. AuthenticationFilter는 전달받은 UsernameAuthenticationToken을 LoginSuccessHandler로 전송하고, SecurityContextHolder에 저장합니다.

 

 

 개념에 대해 더 깊게 들어가면 Security의 주요 모듈마다 하나씩 알아가야 하지만, 그러기엔 제공해 주는 모듈이 너무 많고 용도에 따라서 사용 여부가 나눠지므로 생략하겠습니다.

 

장점만 보자면 Security는 보안 부분에서 꼭 필요한 라이브러리로 생각이 들지만, 보안 계층을 추가함으로써 오버헤드가 발생할 수 있고, 모든 기능을 사용하지 않는 경우에도 설정해야 하는 가능성과 커스터마이징을 하려면 복잡한 설정이 필요할 수 있습니다. 

 

이러한 단점은 Spring Security를 사용할 때 고려해야 할 사항이지만, 단점들을 극복하고 강력한 보안을 구현하는 데 도움을 주는 다양한 자료와 커뮤니티 지원이 있습니다. 따라서 프로젝트의 요구 사항과 복잡성에 따라 Spring Security를 선택하는 것이 중요합니다.

 


Spring Security 적용 방법

 

먼저 start.spring.io 사이트에서 Spring Security 라이브러리를 Dependencies 추가합니다.

 

 

별도로 추가해야 하는 상황이라면 

 

[ Gradle ]

implementation 'org.springframework.boot:spring-boot-starter-security'

 

[ Maven ]

 <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

 

각 필드 관련 도구에 맞춰서 추가하시면 됩니다.

 

라이브러리 추가를 했다면 update 및 Reload 해주시고 Config 파일을 생성해서 적용을 해주면 되는데, 대체적으로 Security 강의를 보면 WebSecurityConfigurerAdapter를 상속시켜서 사용하는 방식으로 배우게 될 겁니다.

 

하지만 Spring Security 5.7.0-M2부터 해당 클래스는 컴포넌트 기반의 보안 설정을 권장한다는 이유로 Deprecated 처리되었습니다.

 

아래 코드가 이전에 사용된 방식입니다.

 

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
         
        // configure HTTP security...
         
    }
 
    @Override
    public void configure(WebSecurity web) throws Exception {
         
        // configure Web security...
         
    }      
}

 

위 코드처럼 WebSecurityConfigurerAdapter를 상속시켜서 Configure 메서드를 @Override 후 각 보안 관련 코드 처리를 했던 방식이었는데, 이제는 Spring에서 권장하지 않는 방식이므로 SecurityFilterChain을 사용해야 합니다.

 

사용 방법으로는

@Configuration
public class SecurityConfiguration {
         
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    
        // configure HTTP security...
     
    }
     
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
         
         // configure Web security...
         
    }
         
}

 

기존의 방식과는 다르게 빈으로 등록한다는 점입니다. SecurityFilterChain을 반환하고 빈으로 등록함으로써 컴포넌트 기반의 보안 설정이 가능해집니다.

 

실제 코드 적용 사례로 알아보자면

 

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig{
	private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
	
	@Autowired
	private PrincipalDetailsService principalDetails;

	// security 로그인 실패 핸들러 DI
	@Autowired
	private  AuthenticationFailureHandler customFailHandler;
	
	@Autowired
	private BCryptPasswordEncoder encodePwd;
	
	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
		return http
				.csrf() // csrf 선언  = 어디에 쓸꺼야 ? 
				.ignoringAntMatchers("/uploadSummernoteImageFile")
				.ignoringAntMatchers("/user/**")
				.ignoringAntMatchers("/auth/**")
				.ignoringAntMatchers("/admin/**")// csrf token Post 403 error Exception filter  
				.and()
				.authorizeRequests()
				.antMatchers("/include/**","/temcss/**","temjs/**").permitAll()
				.antMatchers("/").authenticated()
				.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
				.antMatchers("/manager/**").access("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
				.anyRequest().permitAll()
				.and()
				.formLogin()// 나는 폼 로그인을 사용할거야 !
				.loginPage("/auth/loginForm") // 근데 난 로그인 폼이 있어  이 경로야 !
				.usernameParameter("userid") // 근데 난 username이라고 선언 안해. 별도로  name 선언 했어
				.passwordParameter("pwd") // 동일해 
				.loginProcessingUrl("/auth/login") // security login // 로그인 요청 들어왔어 !! true / false ?
				.failureHandler(customFailHandler) // login Fail // false 
				.defaultSuccessUrl("/") // true
				.and()
				.logout()
				.logoutSuccessUrl("/")
				.invalidateHttpSession(true) // HTTP session reset
				.and()
				.build();
		
	}
	
	@Bean
	public DaoAuthenticationProvider daoAuthenticationProvider() {
		DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
		authenticationProvider.setUserDetailsService(principalDetails);
		authenticationProvider.setPasswordEncoder(encodePwd);
		authenticationProvider.setHideUserNotFoundExceptions(false);
		
		return authenticationProvider;
	}

 

위 코드처럼 SecurityFilterChain을 빈으로 등록해서 적용하면 됩니다.

(위 코드에서 적용한 함수들은 각 포스팅을 통해서 설명드릴 예정입니다..!)

 

 


마치며 

 

오늘은 Spring Security의 개념 및 적용 예제에 대해 알아봤습니다.

다음 포스팅에서 뵙겠습니다.

728x90