티스토리 뷰

📌 스프링부트 3.0 이상부터 시큐리티 설정 방법이 바뀌었습니다.

Spring Boot 3.0 부터 Spring Security 6.0.0 이상의 버전이 적용되면서 아예 삭제되고 변경된 설정 방식들이 있습니다.

이전의 방식과 다른 점은 반환 값이 있고 Bean으로 등록한다는 점 입니다.

SecurityFilterChain을 반환하고 Bean으로 등록함으로써 컴포넌트 기반의 보안설정이 가능해졌습니다.

 

 


 

1️⃣ 적용 방법 : Spring initializr에서 Spring Security 추가

 

Gradle과 Maven에 따라 다른데 제 프로젝트의 경우 Gradle 기반이라 해당 방법으로 시큐리티를 적용해주었습니다.

 

Gradle

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

 

Maven

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

 

 

 

✅ 프로젝트 적용 모습

 

build.grade에 아래 코드를 추가

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

 

 


 

2️⃣ WebSecurityConfig.java 클래스 생성

 

스프링 시큐리티의 인증 / 인가를 다루는 파일입니다.

위치는 다음과 같습니다.

config 패키지 아래에 WebSecurityConfig.java 클래스를 생성 해줍니다.

저의 경우는 이렇게 됩니다.

 

 


 

3️⃣ 리소스 권한 설정 ( SecurityFilterChain )

 

아래는 이전 버전의 예시 코드 입니다.

package of_f.of_f_spring.config;

import of_f.of_f_spring.config.jwt.;
import org.springframework.beans.factory.annotation.Autowired;
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.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;


@Configuration
@EnableWebSecurity //spring security 활성화
public class SecurityConfig {

    @Autowired
    private JwtTokenProvider jwtTokenProvider;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .httpBasic().disable() //http 기본 인증 비활성화
                .cors().configurationSource(corsConfigurationSource())
                .and() //cors활성화
                .csrf().disable() //jwt토큰을 사용하므로 csrf비활성화 -> localstorage에 저장시 비활성화 아니면 활성화
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)//security에서 사용하는 session 비활성화
                .and()
                .authorizeRequests() //인증절차 설정 시작
                .antMatchers("/api/v1/img/").permitAll()
                .antMatchers("/api/v1/auth/n/").permitAll() // 사용자  토큰x
                .antMatchers("/api/v1/auth/y/**").hasAnyAuthority("ROLE_USER", "ROLE_TT_ADMIN", "ROLE_ST_ADMIN") // 사용자  토큰o
                .antMatchers("/api/v1/main/").permitAll() // 모든 사용자
                .antMatchers("/api/v1/admin/").hasAnyAuthority("ROLE_TT_ADMIN") // 최고 관리자
                .antMatchers("/api/v1/store/admin/").hasAnyAuthority("ROLE_ST_ADMIN", "ROLE_TT_ADMIN") // 가맹점 관리자
                .antMatchers("/api/v1/store/").permitAll()
                .anyRequest().authenticated()  // 나머지 요청은 인증이 필요함
                .and()
                .formLogin().disable()  // security의 기본 로그인 화면을 비활성화
                .exceptionHandling().authenticationEntryPoint(new CustomAuthenticationEntryPoint())  // 사용자 인증 실패 처리
                .accessDeniedHandler(new CustomAccessDeniedHandler())  // 권한 없음 처리
                .and()
                .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class) // 사용자 인증 필터 추가
                .addFilterBefore(new JwtExceptionFilter(), JwtAuthenticationFilter.class);  // JWT 예외 처리 필터 추가
        return http.build();
    }

		// 비밀번호 해시 암호화를 위한 BCryptPasswordEncoder 빈 등록
    @Bean  
    public PasswordEncoder passwordEncoder() { //비밀번호 해쉬 암호화
        return new BCryptPasswordEncoder();
    }

		// CORS(Cross-Origin Resource Sharing) 설정을 위한 빈 등록
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();

        configuration.addAllowedOrigin("http://localhost:3000");
        configuration.addAllowedMethod("");
        configuration.addAllowedHeader("*");
        configuration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

}

 

  • .antMatchers("/api/v1/main/").permitAll()   ⇒   모든 사용자에게 접근 허용
  • .antMatchers("/api/v1/auth/y/**").hasAnyAuthority("ROLE_USER", "ROLE_TT_ADMIN", "ROLE_ST_ADMIN")   ⇒   "ROLE_USER", "ROLE_TT_ADMIN", "ROLE_ST_ADMIN" 이 세개의 권한이 있어야 요청(접근) 가능하다.

 

👇👇👇

 

최근 버전에서는 달라진 변경사항이 있습니다.

아래는 최신 버전의 예시 코드 입니다.

package com.example.demo.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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			.authorizeHttpRequests((requests) -> requests
				.requestMatchers("/test", "/test2/**").permitAll()  // 해당하는 페이지들을 모두 허용한다.
				.anyRequest().authenticated()
			)
			
			.formLogin((form) -> form.disable())    // 버전에 따라 변경된 부분
			.logout((logout) -> logout.permitAll());

		return http.build();
	}

	@Bean
	public UserDetailsService userDetailsService() {
		UserDetails user =
			 User.withDefaultPasswordEncoder()
				.username("user")
				.password("password")
				.roles("USER")
				.build();

		return new InMemoryUserDetailsManager(user);
	}
}

 

먼저 antMatchers() requestMatchers() 로 변경된 것을 발견하실 수 있습니다.

 

 


 

 

@EnableWebSecurity

WebSecurityConfigurerAdapter를 상속받은 config 클래스에 @EnableWebSecurity 어노테이션을 달면 SpringSecurityFilterChain이 자동으로 포함됩니다.

기존 방식은 WebSecurityConfigurerAdapter를 상속받아 구현했었는데, 현재는 Deprecated 되어 SecurityFilterChain을 @Bean으로 등록해서 사용해야합니다.

 

 

✅ SpringSecurityFilterChain 이란?

Spring Security에서 보안 관련 필터 체인을 제공하는 역할을 합니다.

.authorizeRequests().formLogin() 등을 사용하여 필터 체인을 구성하고, 이러한 설정을 통해 SpringSecurityFilterChain이 적절한 보안 기능을 수행할 수 있습니다.

쉽게 말해, 리액트에서 요청을 보내면 → 스프링에서 어? 요청이 들어왔네? 하고 → 컨트롤러에 들어오기 전에 무조건 WebSecurityConfig를 먼저 거치게 한 후 → 그 다음에 컨트롤러로 가게 합니다.

 

 

antMatchers() 로 지정할 수 있는 항목  →  5 버전 이후부터는 requestMatchers() 로 변경되었습니다.

  • hasRole() or hasAnyRole() : 특정 권한을 가지는 사용자만 접근할 수 있습니다.
  • hasAuthority() or hasAnyAuthority() : 특정 권한을 가지는 사용자만 접근할 수 있습니다.
  • hasIpAddress() : 특정 아이피 주소를 가지는 사용자만 접근할 수 있습니다.
  • permitAll() or denyAll() : 접근을 전부 허용하거나 제한합니다.
  • rememberMe() : 리멤버 기능을 통해 로그인한 사용자만 접근할 수 있습니다.
  • anonymous() : 인증되지 않은 사용자가 접근할 수 있습니다.
  • authenticated() : 인증된 사용자만 접근할 수 있습니다.

 


 

💻 프로젝트 적용

 

먼저 각 기능에 대한 권한 정리를 간단하게 해보았습니다.

 

 

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// 인증절차 설정 시작 => 특정 URL에 대한 권한 설정.
			.authorizeHttpRequests((requests) -> requests	
					
				// 모두 허용
				// "/api/v1/auth/n/**" 라고 설정해줄 수도 있음. => 사용자 토큰 없이(n) 접근 허용.
				.requestMatchers("/main",
								"/register",
								"/login",
								"/registerEmail",
								"/dupIdCheck",
								"/findId").permitAll()	
				
				// 관리자=선생님(ROLE_ADMIN)만 접근 허용
				// "/api/v1/auth/admin/**" 라고 설정해줄 수도 있음.
				//.requestMatchers("/admin/**").hasRole("ADMIN") => ROLE_ 접두사가 자동으로 들어감.
				.requestMatchers("/admin/**").hasAnyAuthority("ROLE_ADMIN")
				
				// 로그인한 사용자(ROLE_USER)만 접근 허용
				// "/api/v1/auth/y/**" 라고 설정해줄 수도 있음.
				.requestMatchers("/logout",
						"/findPw",
						"/resetPw",
						"/puppy",
						"/puppyNote",
						"/puppyNoteDetail",
						"/gallery",
						"/galleryDetail",
						"/createBoard",
						"/updateBoard",
						"/deleteBoard",
						"/boardDetail",
						"/createComment",
						"/updateComment",
						"/deleteComment",
						"/userLike",
						"/dupUserLike",
						"/updateMember",
						"/updatePuppy",
						"/likeList",
						"/createList",
						"/deleteId").hasAnyAuthority("ROLE_USER")
				
				// + 최고 권한의 관리자 필요함. ( 필수는 아님. 추후 고려해보기. )
				// 나머지 요청은 인증된 사용자에게만 접근 허용
				.anyRequest().authenticated()    
			)
			
			// (버전에 따라 변경된 부분) 기본 로그인 폼을 사용하지 않도록 설정
			.formLogin((form) -> form.disable())   
			// 로그아웃은 아래 코드 말고 따로 설정할거임.
			.logout((logout) -> logout.permitAll());   // 로그아웃 허용

		return http.build();
	}

 

 

그리고 작성해본 코드입니다.

 

  • permitAll() 을 통해 사용자 토큰 없이도 모두 접근이 허용되는 리소스들을 작성해주었습니다.
  • hasAnyAuthority("ROLE_ADMIN") 부분은 관리자(선생님)만 접근 허용 가능하게 작성해주었습니다.
    • hasRole() vs hasAnyAuthority() 둘 중 어느 것을 써야하는지 고민했는데 유연성을 위해 hasAnyAuthority() 로 선택했습니다.
    • hasRole() : 권한을 체크할 때 "ROLE_" 접두사가 자동으로 추가됩니다. 즉, 실제 권한은 "ROLE_"을 포함하여 "ROLE_NAME"이 되어야 합니다.
    • hasAnyAuthority() : "ROLE_" 접두사를 추가하지 않습니다. 따라서 정확한 권한 문자열을 지정해야 합니다. 여러 권한을 동시에 체크할 수 있으며, 주어진 여러 권한 중 하나라도 사용자에게 부여되면 통과됩니다. 권한 이름 자체에 더 큰 유연성이 필요한 경우 사용됩니다.
  • hasAnyAuthority("ROLE_USER")를 통해 로그인한 사용자만 접근 허용하도록 작성해주었습니다. 
  • logout 부분은 나중에 다시 설정할 예정입니다.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함