[ JAVA ]/JAVA Spring Security

[ Security ] Spring Security 6.x 설정 방법 알아가기

환이s 2024. 8. 19. 11:13
728x90


Intro

 

안녕하세요. 환이s입니다👋

이전 회사에서 프로젝트 안정화 기간을 끝내고 이직을 결심하게 돼서 이직 준비하느라 한동안 블로그 업로드를 못하고 있었는데요..😅

 

현재는 이직에 성공해서 이번 달 말에 프로젝트에 투입 예정입니다!..

제가 첫 이직 준비를 겪으면서 정말 많은 생각을 하게 되었는데.. 결과적으로 봤을 땐 지금 시기에는 이직이 쉽지 않은 거 같아요..!

 

추후에 회고록 작성할 때 보따리를 풀 예정이라서 이직 관련해서는 여기까지만 말씀드리고

본론으로 들어가자면 제가 이번에 과제형 코딩 테스트를 진행하면서 Spring Security를 사용했는데

6.x 버전대부터 추가/삭제된 부분이 많다 보니 포스팅을 올려보려고 합니다..!

 

Spring Security에 대해 알아보시는 분들은 아래 포스팅을 참고해 보시면 도움이 될 거 같아요🙂

 

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

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

drg2524.tistory.com


Spring Security - 설정

 

위 포스팅이나 제가 Security 기반으로 기능 구현한 포스팅을 보시면 Spring Boot 2.x 버전에 맞는 Security 4.x 버전을 응용해서 코드 구현을 했었습니다.

 

먼저 이전 버전 Security Config 파일을 보여드리자면

@Configuration
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록이 된다.
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
//secured 어노테이션 활성화 , preAuthorize 어노테이션 활성화
public class SecurityConfig { // 스프링 필터 역할

    @Autowired
    private PrincipalOauth2UserService principalOauth2UserService;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/user/**").authenticated() // 인증만 되면 들어갈 수 있는 주소!!
                .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
                .anyRequest().permitAll()
                .and()
                .formLogin()
                .loginPage("/loginForm")
                .loginProcessingUrl("/login")// login 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행해준다.
                .defaultSuccessUrl("/")
                .and()
                .oauth2Login()
                .loginPage("/loginForm") //구글 로그인이 완료된 뒤의 후처리가 필요함. Tip. 코드x,(액세스토큰 + 사용자프로필정보 o)
                .userInfoEndpoint()
                .userService(principalOauth2UserService)
                .and().and().build();

    }
}

 

위 코드를 확인해 보시면

이전 버전대에서는 Builder 형식으로 추가적인 설정을 해줬어야 했고 설정을 추가할 때마다 and()를 활용해서

추가적인 설정을 이어가는 구절이 보이실 거예요.

 

물론 더 자세하게 하나씩 설명을 드리고 싶지만 이전에 다뤘기 때문에 생략하려고 합니다.

이번 포스팅은 변경된 부분을 설정하는 글이니까요.

 

위 코드 기반으로 버전대에 맞춰서 사용하셔도 동작은 됩니다.

하지만 Spring Boot 2.x 버전을 이제 제공을 안 하다 보니 3.x 버전으로 프로젝트를 생성해야 하는데..

문제는 Spring Boot 버전이 올라가면서 자연스럽게 Security 버전도 올라가기 때문에

 

위 설정 코드를 사용하실 수는 없습니다.

 

Security에서는 5.2.x 버전부터 Lambda DSL 방식으로 설정할 수 있도록 변경이 이루어진 적이 있습니다.

 

따라서, 예로 authorizeRequests()에서 Lambda를 사용해서 표현할 수 있도록 되었습니다.

코드를 먼저 보여드리자면 다음과 같습니다.

 

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
            .csrf(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers("/user/**").authenticated() // 인증된 사용자만 접근 가능
                    .requestMatchers("/admin/**").hasRole("ADMIN") // ROLE_ADMIN 권한 필요
                    .requestMatchers("/login","/loginForm","/joinForm","/basic/idCheck","/join").permitAll()
                    .requestMatchers("/css/**", "/js/**", "/img/**","/trakker_video/**").permitAll()
                    .anyRequest().authenticated()
            )
            .formLogin(formLogin -> formLogin
                    .loginPage("/loginForm")
                    .loginProcessingUrl("/auth/login") // 로그인 처리 URL
                    .failureHandler(customFailureHandler)
                    .defaultSuccessUrl("/") // 로그인 성공 후 리다이렉트 URL
            )
            .oauth2Login(oauth2Login -> oauth2Login
                    .loginPage("/loginForm")
                    .userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint
                            .userService(principalOauth2UserService) // OAuth2 로그인 사용자 서비스
                    )
                    .failureUrl("/loginForm?error=true")
                    .defaultSuccessUrl("/")

            )
            .logout(logout -> logout
                    .logoutUrl("/logout") // 로그아웃 요청을 처리할 URL 설정
                    .logoutSuccessUrl("/loginForm") // 로그아웃 성공 시 리다이렉트할 URL 설정
                    .addLogoutHandler((request, response, authentication) -> { // 로그아웃 핸들러 추가 (세션 무효화 처리)
                        HttpSession session = request.getSession();
                        session.invalidate();
                    })
                    .logoutSuccessHandler((request, response, authentication) -> // 로그아웃 성공 핸들러 추가 (리다이렉션 처리)
                            response.sendRedirect("/loginForm"))
                    .deleteCookies("remember-me") // 로그아웃 시 쿠키 삭제 설정
                    .invalidateHttpSession(true) // HTTP session reset
            );


    return http.build();
}

 

차이점이 보이시나요?

Spring Boot 3.x 버전 즉, Security 6.x 버전부터는 기존에 적용했던 csrf(), formLogin()와 같이 인수를 갖지 않는 메서드들은 전부 Deprecated 되었습니다.

 

Security 개발진 측에서 해당 방식의 메서드들이 가독성을 떨어트리는 부분이고,

설정에 대한 해석이 한눈에 들어오지 않는다는 판단을 통해 결정 난 것입니다.

 

다음 코드의 내부를 보시면 알 수 있습니다.

	/**
	 * Adds the Security headers to the response. This is activated by default when using
	 * {@link EnableWebSecurity}. Accepting the default provided by
	 * {@link EnableWebSecurity} or only invoking {@link #headers()} without invoking
	 * additional methods on it, is the equivalent of:
	 *
	 * <pre>
	 * &#064;Configuration
	 * &#064;EnableWebSecurity
	 * public class CsrfSecurityConfig {
	 *
	 * 	&#064;Bean
	 * 	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	 * 		http
	 * 			.headers()
	 * 				.contentTypeOptions()
	 * 				.and()
	 * 				.xssProtection()
	 * 				.and()
	 * 				.cacheControl()
	 * 				.and()
	 * 				.httpStrictTransportSecurity()
	 * 				.and()
	 * 				.frameOptions()
	 * 				.and()
	 * 			...;
	 * 		return http.build();
	 * 	}
	 * }
	 * </pre>
	 *
	 * You can disable the headers using the following:
	 *
	 * <pre>
	 * &#064;Configuration
	 * &#064;EnableWebSecurity
	 * public class CsrfSecurityConfig {
	 *
	 * 	&#064;Bean
	 * 	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	 * 		http
	 * 			.headers().disable()
	 * 			...;
	 * 		return http.build();
	 * 	}
	 * }
	 * </pre>
	 *
	 * You can enable only a few of the headers by first invoking
	 * {@link HeadersConfigurer#defaultsDisabled()} and then invoking the appropriate
	 * methods on the {@link #headers()} result. For example, the following will enable
	 * {@link HeadersConfigurer#cacheControl()} and
	 * {@link HeadersConfigurer#frameOptions()} only.
	 *
	 * <pre>
	 * &#064;Configuration
	 * &#064;EnableWebSecurity
	 * public class CsrfSecurityConfig {
	 *
	 * 	&#064;Bean
	 * 	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	 * 		http
	 * 			.headers()
	 * 				.defaultsDisabled()
	 * 				.cacheControl()
	 * 				.and()
	 * 				.frameOptions()
	 * 				.and()
	 * 			...;
	 * 		return http.build();
	 * 	}
	 * }
	 * </pre>
	 *
	 * You can also choose to keep the defaults but explicitly disable a subset of
	 * headers. For example, the following will enable all the default headers except
	 * {@link HeadersConfigurer#frameOptions()}.
	 *
	 * <pre>
	 * &#064;Configuration
	 * &#064;EnableWebSecurity
	 * public class CsrfSecurityConfig {
	 *
	 * 	&#064;Bean
	 * 	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	 * 		http
	 * 			.headers()
	 * 				 .frameOptions()
	 * 				 	.disable()
	 * 				 .and()
	 * 			...;
	 * 		return http.build();
	 * 	}
	 * }
	 * </pre>
	 * @return the {@link HeadersConfigurer} for further customizations
	 * @throws Exception
	 * @deprecated For removal in 7.0. Use {@link #headers(Customizer)} or
	 * {@code headers(Customizer.withDefaults())} to stick with defaults. See the <a href=
	 * "https://docs.spring.io/spring-security/reference/migration-7/configuration.html#_use_the_lambda_dsl">documentation</a>
	 * for more details.
	 * @see HeadersConfigurer
	 */
	@Deprecated(since = "6.1", forRemoval = true)
	public HeadersConfigurer<HttpSecurity> headers() throws Exception {
		return getOrApply(new HeadersConfigurer<>());
	}

 

간단하게 해석해 보자면 6.1.x 버전 이후로 Deprecated 했고,

7.0 버전에서 삭제된다고 합니다.

그리고 header(Customizer) 또는 Customizer.withDefaults()를 사용하라고 합니다.

 

즉, Lambda DSL 방식을 통한 설정으로 전체적인 코드 방식이 달라졌고,

기본 옵션으로 사용하려면 매개변수에 Customizer.withDefaults()를 사용하면 됩니다.

 

마지막으로 위 코드 기반으로 변경된 부분을 코드로만 알려드리고 마무리하겠습니다.


Spring Security - 변경된 설정 방식 알아가기

 

 

[  csrf() ]

.csrf().disable()

 

 

6.x 버전에 적용할 때는 아래 코드로 변형

.csrf(AbstractHttpConfigurer::disable)

 

 


 

[ antMatchers() ]

.antMatchers("/user/**").authenticated() // 인증만 되면 들어갈 수 있는 주소!!
.antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")

 

이전 버전대에서는. andMatchers().... andMatchers()... 만 선언해서 특정 리소스에 대한 접근 권한 설정을 줄 수 있었습니다.

 

위 구조를 Lambda DSL로 변경하면 다음과 같습니다.

.authorizeHttpRequests(authorize -> authorize
        .requestMatchers("/user/**").authenticated() // 인증된 사용자만 접근 가능
        .requestMatchers("/admin/**").hasRole("ADMIN") // ROLE_ADMIN 권한 필요
        .requestMatchers("/login","/loginForm","/joinForm","/basic/idCheck","/join").permitAll()
        .requestMatchers("/css/**", "/js/**", "/img/**","/trakker_video/**").permitAll()
        .anyRequest().authenticated()
)

 

Security 6.x 버전부터는  .andMatchers()가 없어시고 requestMatchers() 를 사용하도록 변경되었습니다.

 


 

[ formLogin() ]

.formLogin()
.loginPage("/loginForm")
.loginProcessingUrl("/login")// login 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행해준다.
.defaultSuccessUrl("/")

 

Lambda DSL로 변경 후

.formLogin(formLogin -> formLogin
        .loginPage("/loginForm")
        .loginProcessingUrl("/auth/login") // 로그인 처리 URL
        .failureHandler(customFailureHandler)
        .defaultSuccessUrl("/") // 로그인 성공 후 리다이렉트 URL
)

 


[ oauth2Login() ]

.oauth2Login()
.loginPage("/loginForm") //구글 로그인이 완료된 뒤의 후처리가 필요함. Tip. 코드x,(액세스토큰 + 사용자프로필정보 o)
.userInfoEndpoint()
.userService(principalOauth2UserService)

 

Lambda DSL로 변경 후 

.oauth2Login(oauth2Login -> oauth2Login
        .loginPage("/loginForm")
        .userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint
                .userService(principalOauth2UserService) // OAuth2 로그인 사용자 서비스
        )
        .failureUrl("/loginForm?error=true")
        .defaultSuccessUrl("/")

)

 


마치며

 

오늘은 Spring Security 6.x 버전 설정 방법에 대해 알아봤습니다.

Lambda DSL로 변경하면서 코드가 깔끔해지는게 보이실까요?

 

물론 저도 이전 버전대 방식으로 설정을 자주 했다보니

처음에는 엄청 헷갈리더라고요..😅

 

익숙해지려면 시간이 좀 걸리실 수 있지만

시간을 투자한 만큼 나만의 기술로는 반드시 만들 수 있을 거 같습니다🙂

 

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

 

 

728x90