๊ด€๋ฆฌ ๋ฉ”๋‰ด

JiYoung Dev ๐Ÿ–ฅ

Spring Security ์ ์šฉ๊ธฐ (2) Spring Security Configuration ๋ณธ๋ฌธ

Study/Java

Spring Security ์ ์šฉ๊ธฐ (2) Spring Security Configuration

Shinjio 2024. 4. 25. 22:45

2024.04.22 - [Study/Java] - Spring Security ์ ์šฉ๊ธฐ (1) Spring Security๋ž€?

 

Spring Security ์ ์šฉ๊ธฐ (1) Spring Security๋ž€?

์ƒˆ๋กญ๊ฒŒ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•˜๋ฉด์„œ ์ ์šฉํ•ด๋ณด๊ณ  ์‹ถ์—ˆ๋˜ ๊ธฐ์ˆ  ์ค‘ ํ•˜๋‚˜์ธ Spring Security. ์–ด๋– ํ•œ ๊ธฐ์ˆ ์ด๋ฉฐ ์–ด๋– ํ•œ ์›๋ฆฌ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋™์ž‘ํ•˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•˜๋Š” ๊ฒƒ๊นŒ์ง€ ํ•˜๋‚˜ ํ•˜๋‚˜ ํฌ์Šค

danyoujeong.tistory.com

์ง€๋‚œ ํฌ์ŠคํŒ…์— ์ด์–ด ์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” Spring Security Configuration์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์•˜๋‹ค.


 

ํ•ด๋‹น ํ”„๋กœ์ ํŠธ๋Š”  Spring Boot 3.1.11์—์„œ ์ง„ํ–‰ํ•˜๋Š” ํ”„๋กœ์ ํŠธ์ด๋ฉฐ, Spring Security ์ ์šฉ์„ ์œ„ํ•ด spring-boot-starter-security ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•œ๋‹ค. 

 

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-security'
}

 

 

SpringBoot 3.1.11 ๋ฒ„์ „ ์ด๋ฏ€๋กœ security ๋ฒ„์ „์€ 6.1์ด๋‹ค. 

6.1 ๋ฒ„์ „ ๋ถ€ํ„ฐ๋Š” WebSecurityConfigureAdapter ํด๋ž˜์Šค๊ฐ€ deprecated ๋˜์–ด ํ•ด๋‹น ํด๋ž˜์Šค๋ฅผ ์ƒ์† ๋ฐ›์•„ config ๋ฉ”์†Œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋Œ€์‹  SecurityFilterChain์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์ง์ ‘ Bean์œผ๋กœ ๋“ฑ๋กํ•˜๋„๋ก ์„ค์ • ๋ฐฉ๋ฒ•์ด ๋ฐ”๋€Œ์—ˆ๋‹ค๊ณ  ํ•œ๋‹ค.

 

๊ธฐ์กด ์„ค์ • ๋ฐฉ๋ฒ• ์˜ˆ์‹œ

@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{

	@Override
    protected void configure(HttpSecurity http) throwd Exception{
    	http.crsf().disable();
    }
}

WebSecurityConfig.java

package com.shop.kns.config;

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;

/**
 * Spring Security Configuration
 */
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {

    @Value("${cors.allowed.origins}")
    private String allowdOrigins;
    private static final String[] WHITE_LIST = {
            // Swagger UI v3
            "/v3/api-docs/**", "v3/api-docs/**", "/swagger-ui/**", "swagger-ui/**",
            // auth
            "/api/v1/auth/**",
            // test
            "/api/v3/api-docs/**", "/api/swagger-ui/**",
    };
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable) //csrf ๋น„ํ™œ์„ฑํ™”
                .cors(cors -> cors.configurationSource(corsConfigurationSource())) //cors ๊ตฌ์„ฑ
                .authorizeHttpRequests(authorize -> authorize //authorize(๊ถŒํ•œ)
                        .requestMatchers(WHITE_LIST).permitAll()
                        .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
                        .requestMatchers("/shop/**").permitAll()
                        .anyRequest().authenticated()
                )
                .addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class) //์‚ฌ์šฉ์ž ์ •์˜ ํ•„ํ„ฐ ์ถ”๊ฐ€
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) //์„ธ์…˜ ์ƒ์„ฑ ์ •์ฑ…
                .formLogin(login -> login //๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ ์„ค์ •
                        .loginPage("/login") //๋กœ๊ทธ์ธ ํŽ˜์ด์ง€
                        .successHandler(new SimpleUrlAuthenticationSuccessHandler("/shop")) //๋กœ๊ทธ์ธ ์ธ์ฆ ์„ฑ๊ณต ํ›„ ์ด๋™ํ•  URL
                        .permitAll()
                );
        return http.build();
    }


    /**
     * CORS ์ •์ฑ… ์„ค์ •
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource(){
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedOrigins(List.of(allowdOrigins)); //ํ—ˆ์šฉํ•  URL
        corsConfiguration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE")); //ํ—ˆ์šฉํ•  method
        corsConfiguration.setAllowedHeaders(List.of("X-Requested-With", "Content-Type", "Authorization", "X-XSRF-token")); //ํ—ˆ์šฉํ•  header
        corsConfiguration.setAllowCredentials(false); //์ž๊ฒฉ์ฆ๋ช…(์ฟ ํ‚ค, ์ธ์ฆ ํ—ค๋” ๋“ฑ) ํ—ˆ์šฉ ์—ฌ๋ถ€
        corsConfiguration.setMaxAge(3600L); //๋ธŒ๋ผ์šฐ์ € ์‘๋‹ต ์บ์‹œ ์‹œ๊ฐ„(1์‹œ๊ฐ„)

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

 

 

์ฝ”๋“œ ํ•œ ์ค„ ํ•œ ์ค„ ํ›‘์–ด ๋ณด์ž.

 

@Value("${cors.allowed.origins}")
private String allowdOrigins;

 

cors ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ url์€ ๋ณ„๋„ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ์ง€์ •ํ•˜์˜€๋‹ค. 

 

private static final String[] WHITE_LIST = {
        // Swagger UI v3
        "/v3/api-docs/**", "v3/api-docs/**", "/swagger-ui/**", "swagger-ui/**",
        // auth
        "/api/v1/auth/**",
        // test
        "/api/v3/api-docs/**", "/api/swagger-ui/**",
};

 

์ ‘๊ทผ ํ—ˆ์šฉ ๊ฒฝ๋กœ๋ฅผ ์œ„ํ•œ String[]์„ ๋ฏธ๋ฆฌ ์ •์˜ํ•˜์˜€๋‹ค.

 

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

 

์œ„์—์„œ Spring security๊ฐ€ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋˜๋ฉด์„œ ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„์ด๋‹ค. SecurityFilterChain์„ ๋ฐ˜ํ™˜ํ•˜๊ณ , ์ด๋ฅผ @Bean์œผ๋กœ ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค. 

 

.csrf(AbstractHttpConfigurer::disable) //csrf ๋น„ํ™œ์„ฑํ™”

 

REST API์—์„œ CSRF๋ฅผ disable ํ•˜๋Š” ์ด์œ ๋Š” jwt๋ฅผ ์‚ฌ์šฉํ•  ์˜ˆ์ •์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. jwt๋Š” ์„ธ์…˜ ๊ธฐ๋ฐ˜ ์ธ์ฆ๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ statelessํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„์— ์ธ์ฆ ์ •๋ณด๋ฅผ ๋ณด๊ด€ํ•˜์ง€ ์•Š๋Š”๋‹ค. REST API์—์„œ Client๋Š” ๊ถŒํ•œ์ด ํ•„์š”ํ•œ ์š”์ฒญ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์š”์ฒญ์— ํ•„์š”ํ•œ ์ธ์ฆ ์ •๋ณด๋Š”(OAuth2, jwt ํ† ํฐ ๋“ฑ)์„ ํฌํ•จ์‹œ์ผœ์•ผ ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ์ธ์ฆ์ •๋ณด๋ฅผ ์„œ๋ฒ„์— ์ €์žฅํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๊ตณ์ด ๋ถˆํ•„์š”ํ•œ csrf ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค. 

 

.authorizeHttpRequests(authorize -> authorize //authorize(๊ถŒํ•œ)
        .requestMatchers(WHITE_LIST).permitAll()
        .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
        .anyRequest().authenticated()
)

 

ํŽ˜์ด์ง€์— ๋Œ€ํ•œ ๊ถŒํ•œ ์„ค์ •์ด๋‹ค. WHITE_LIST๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์€ ๋ชจ๋‘ ํ—ˆ์šฉ์œผ๋กœ ์„ค์ •ํ•˜์˜€๋‹ค. 

 

.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()

 

์œ„์˜ ์ฝ”๋“œ๋Š” PreFlight ์š”์ฒญ์— ๋Œ€ํ•ด ๋ชจ๋‘ ํ—ˆ์šฉ ํ•˜๊ฒ ๋‹ค๋Š” ์„ค์ •์ด๋‹ค. Cors pre-flight ์š”์ฒญ์€ ์‹ค์ œ ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์ „์— ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์„œ๋ฒ„๋กœ ์ถ”๊ฐ€๋กœ ๋ณด๋‚ด๋Š” ์ถ”๊ฐ€ ์š”์ฒญ์ด๋‹ค. ์ด ์š”์ฒญ์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ˜„์žฌ ๋„๋ฉ”์ธ์—์„œ ๋‹ค๋ฅธ ์ถœ์ฒ˜(origin)๋กœ์˜ ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์ „์—, ์„œ๋ฒ„๊ฐ€ ํ•ด๋‹น ์š”์ฒญ์„ ์ˆ˜๋ฝํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•œ๋‹ค. 

 

Cors pre-flight ์š”์ฒญ์€ ์ฃผ๋กœ ์•„๋ž˜์™€ ๊ฐ™์€ ๊ฒฝ์šฐ์— ๋ฐœ์ƒํ•œ๋‹ค. 1. ๋ธŒ๋ผ์šฐ์ €์—์„œ Ajax ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ, ํ•ด๋‹น ์š”์ฒญ์ด ๊ฐ™์€ ์ถœ์ฒ˜๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์œผ๋กœ ๋ณด๋‚ด๋Š” ๊ฒฝ์šฐ2. ์š”์ฒญ์— ํŠน์ •ํ•œ HTTP ๋ฉ”์„œ๋“œ๋‚˜ ํ—ค๋”๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋Š” ๊ฒฝ์šฐ. ์˜ˆ๋ฅผ ๋“ค์–ด 'PUT', 'DELETE', 'Content-Type' ๋“ฑ

 

.addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class)

 

UsernamePasswordAuthenticationFilter ์ ์šฉ ์ „์— jwtAuthorizationFilter๋ฅผ ์ ์šฉํ•˜๋Š” ๋ถ€๋ถ„์ด๋‹ค. Spring Security์—์„œ๋Š” BasicAuthenticationFilter๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์žˆ์ง€๋งŒ, ํ•ด๋‹น ํ•„ํ„ฐ๋Š” ์‚ฌ์šฉ์ž์˜ ID์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ—ค๋”์— ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ jwt ์ธ์ฆ ๋ฐฉ์‹๊ณผ ์ถฉ๋Œํ•˜๋ฏ€๋กœ ์ด๋ฅผ disableํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. 

 

UsernamePasswordAuthenticationFilter

1. ์ฃผ๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์—ฌ ๋กœ๊ทธ์ธํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ2. ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์— ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ œ์ถœํ•˜๋ฉด, ์ด ํ•„ํ„ฐ๊ฐ€ ํ•ด๋‹น ์ •๋ณด๋ฅผ ๋ฐ›์•„์„œ Spring Security์˜ ์ธ์ฆ ๋งค๋‹ˆ์ €์—๊ฒŒ ์ „๋‹ฌํ•œ๋‹ค. ์ธ์ฆ ๋งค๋‹ˆ์ €๋Š” ์ œ๊ณต๋œ ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•˜๊ณ , ์ธ์ฆ์ด ์„ฑ๊ณตํ•˜๋ฉด ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. 

 

BasicAuthenticationFilter

1. HTTP Basic ์ธ์ฆ ์Šคํ‚ด์„ ์ฒ˜๋ฆฌํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. 2. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ Authorization ํ—ค๋”์— Base64๋กœ ์ธ์ฝ”๋”ฉ๋œ "username:password"๋ฅผ ํฌํ•จํ•˜์—ฌ ๋ณด๋‚ด๋ฉด, ์ด ํ•„ํ„ฐ๊ฐ€ ์ด๋ฅผ ์ถ”์ถœํ•˜๊ณ  ์ธ์ฆ ๋งค๋‹ˆ์ €์—๊ฒŒ ์ „๋‹ฌํ•œ๋‹ค. ์ฃผ๋กœ Restful API์—์„œ ์‚ฌ์šฉ๋˜๋ฉฐ, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ HTTP Basic ์ธ์ฆ ํ—ค๋”๋ฅผ ํฌํ•จํ•˜์—ฌ ๋ณด๋‚ด๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉํ•œ๋‹ค.

 

UsernamePasswordAuthenticationFilter๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ๋กœ๊ทธ์ธ ํ•˜๋Š” ๊ฒฝ์šฐ์— ์‚ฌ์šฉ๋˜๊ณ , BasicAuthenticationFilter๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ HTTP Basic ์ธ์ฆ์„ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉํ•œ๋‹ค. 

์ผ๋ฐ˜์ ์œผ๋กœ jwt๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž๊ฐ€ ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ œ์ถœํ•˜๋Š” ๋ฐฉ์‹์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— UsernamePasswordAuthenticationFilter๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. 

 

.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) //์„ธ์…˜ ์ƒ์„ฑ ์ •์ฑ…

 

JWT๋ฅผ ์‚ฌ์šฉํ•  ์˜ˆ์ •์ด๋ฏ€๋กœ STATELESS ์ฆ‰, ์„ธ์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒ ๋‹ค๋Š” ์„ค์ •์„ ํ•œ๋‹ค.

 

.formLogin(login -> login //๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ ์„ค์ •
        .loginPage("/login") //๋กœ๊ทธ์ธ ํŽ˜์ด์ง€
        .successHandler(new SimpleUrlAuthenticationSuccessHandler("/shop")) //๋กœ๊ทธ์ธ ์ธ์ฆ ์„ฑ๊ณต ํ›„ ์ด๋™ํ•  URL
        .permitAll()
);

 

๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ ์„ค์ •์ด๋‹ค.


์ฐธ๊ณ ์ž๋ฃŒ

https://sbl133.tistory.com/88

 

[spring] spring security๋ฅผ ์ด์šฉํ•œ JWT ๋กœ๊ทธ์ธ

๊ธฐ์กด์—๋Š” ์„ธ์…˜/์ฟ ํ‚ค ๋ฐฉ์‹์œผ๋กœ ๋กœ๊ทธ์ธ์„ ๊ตฌํ˜„ํ•˜์˜€์ง€๋งŒ ๋‹ค์ค‘ ์„œ๋ฒ„ํ™˜๊ฒฝ์—์„œ์˜ ์„œ๋ฒ„๊ฐ„ ์„ธ์…˜๊ณต์œ ๋ฌธ์ œ, ์•ฑ์œผ๋กœ ํ™•์žฅํ–ˆ์„๋•Œ clinet์˜ ํ™˜๊ฒฝ ์ฐจ์ด, Front-end์—์„œ ๋กœ์ปฌํ™˜๊ฒฝ์œผ๋กœ ํ…Œ์ŠคํŠธํ• ๋•Œ Back-end ์„œ๋ฒ„์™€์˜

sbl133.tistory.com