๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

Spring๐Ÿƒ/Spring Security๐Ÿ›ก๏ธ

Spring Security

728x90

์ถœ์ฒ˜ : ๋‚ด์ผ๋ฐฐ์›€์บ ํ”„

 

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์ ์šฉ๋ฐฉ๋ฒ•

// ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ
implementation 'org.springframework.boot:spring-boot-starter-security'

Config ํŒจํ‚ค์ง€์— WebSecurityConfig ํด๋ž˜์Šค ์ƒ์„ฑ

//์Šคํ”„๋ง ๋ถ€ํŠธ 2.7 ์ด์ƒ
package com.sparta.springsecurity.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.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity // ์Šคํ”„๋ง Security ์ง€์›์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•จ
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // CSRF ์„ค์ •
        http.csrf().disable();
        
        http.authorizeRequests().anyRequest().authenticated();

        // ๋กœ๊ทธ์ธ ์‚ฌ์šฉ
        http.formLogin();
        
        return http.build();
    }

}

CSRF : ์‚ฌ์ดํŠธ ๊ฐ„ ์š”์ฒญ ์œ„์กฐ, ๊ณต๊ฒฉ์ž๊ฐ€ ์ธ์ฆ๋œ ๋ธŒ๋ผ์šฐ์ €์— ์ €์žฅ๋œ ์ฟ ํ‚ค์— ์„ธ์…˜์ •๋ณด๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์›น ์„œ๋ฒ„์— ์‚ฌ์šฉ์ž๊ฐ€ ์˜๋„ํ•˜์ง€ ์•Š์€ ์š”์ฒญ์„ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ

CSRF ์„ค์ •์„ ํ•ด๋‘” ๊ฒฝ์šฐ CSRF ํ† ํฐ์ด ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ html๋กœ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

์ฟ ํ‚ค ๊ธฐ๋ฐ˜์˜ ์ทจ์•ฝ์ ์„ ์ด์šฉํ•œ ๊ณต๊ฒฉ์ด๊ธฐ ๋•Œ๋ฌธ์— REST๋ฐฉ์‹์˜ API์—์„œ๋Š” ์˜ˆ๋ฐฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

Spring Security์˜ ์ฃผ์š” ์ปดํฌ๋„ŒํŠธ

1) Filter

์š”์ฒญ์ด ๋“ค์–ด์˜ฌ ๊ฒฝ์šฐ ServletFilterChain์„ ์ž๋™ ๊ตฌ์„ฑํ•œ ํ›„ ์š”์ฒญ์„ ~Chain์—์„œ ๊ฒ€์‚ฌํ•˜๊ฒŒ ํ•œ๋‹ค ~ FilterChain์ด๋ž€ ์—ฌ๋Ÿฌ Filter์˜ ๋ฌถ์Œ.

Filter๋ž€, ํ†ฐ์บฃ๊ณผ ๊ฐ™์€ ์›น ์ปจํ…Œ์ด๋„ˆ์—์„œ ๊ด€๋ฆฌ๋˜๋Š” ์„œ๋ธ”๋ฆฟ์˜ ๊ธฐ์ˆ ์ด๋‹ค. Filter๋Š” ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์ „๋‹ฌ๋˜๊ธฐ ์ „ ํ›„์˜ URL ํŒจํ„ด์— ๋งž๋Š” ๋ชจ๋“  ์š”์ฒญ์— ํ•„ํ„ฐ๋ง์„ ํ•ด์ค€๋‹ค. -> CSRF, XSS ๋“ฑ์˜ ๋ณด์•ˆ ๊ฒ€์‚ฌ๋ฅผ ํ†ตํ•ด ์˜ฌ๋ฐ”๋ฅธ ์š”์ฒญ์ด ์•„๋‹ ๊ฒฝ์šฐ, ๊ทธ ์š”์ฒญ์„ ์ฐจ๋‹จํ•œ๋‹ค. == Spring Security๋Š” Filter๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ/์ธ๊ฐ€๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋‹ค.

2) ๊ธฐ๋ณธ Filter : AbstractAuthenticationProcessingFilter -> ์‚ฌ์šฉ์ž์˜ credential์„ ์ธ์ฆ

- FIlterChain์—์„œ DelegatingFilterProxy : ๋‹ค๋ฅธ beanFilter์—๊ฒŒ 

 

3) UsernamePasswordAuthenticationFilter

๊ธฐ๋ณธ Filter๋ฅผ ์ƒ์†

Form ํ˜•์‹ Login์„ ์‚ฌ์šฉํ•  ๋•Œ username๊ณผ password๋ฅผ ํ™•์ธํ•˜์—ฌ ์ธ์ฆ

-> ์ธ์ฆ์— ์‹คํŒจํ•œ๋‹ค๋ฉด ๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค

Chain์—์„œ ์š”์ฒญ๊ณผ ํ•จ๊ป˜ ๋“ค์–ด์˜จ ์ •๋ณด๊ฐ€ ๋งž๋Š”์ง€ ํ™•์ธ
Filter๊ฒฐ๊ณผ์— ๋”ฐ๋ฅธ ์ง„ํ–‰

-> UsernamePasswordAuthenticationFilter๋ฅผ ํ†ตํ•ด ์ธ์ฆ๋ชฉ์ ์˜ UsenamePasswordAuthenticationToken์„ ์ƒ์„ฑํ•˜๊ณ ,

์ธ์ฆ ๊ณผ์ •์„ AuthenticaionManager๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฐ์ฒด์— ์œ„์ž„

4) SecrityContextHolder : ์ธ์ฆ์„ ์‹œ๋„ํ•œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์ €์žฅํ•œ๋‹ค.

~ SecruityContext๋Š” Authentication ๊ฐ์ฒด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

// ์˜ˆ์‹œ์ฝ”๋“œ
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
context.setAuthentication(authentication);

SecurityContextHolder.setContext(context);

5) Authentication : ํ˜„์žฌ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฉฐ SecurityContext์—์„œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๊ณ  ๋‹ค์Œ 3๊ฐ€์ง€์˜ ์ •๋ณด(ํ•„๋“œ, ์ธ์Šคํ„ด์Šค)๋ฅผ ๊ฐ€์ง„๋‹ค.

Principal : ์‚ฌ์šฉ์ž๋ฅผ ์‹๋ณ„ํ•œ๋‹ค. Username/ Password ๋ฐฉ์‹์œผ๋กœ ์ธ์ฆํ•  ๋•Œ ๋ณดํ†ต UserDetails ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

Credential : ์ฃผ๋กœ ๋น„๋ฐ€๋ฒˆํ˜ธ, ๋Œ€๋ถ€๋ถ„ ์‚ฌ์šฉ์ž ์ธ์ฆ์— ์‚ฌ์šฉํ•œ ๋‹ค์Œ ๋น„์–ด์žˆ๋Š” ์ƒํƒœ๋กœ ๋น„์šด๋‹ค.

Authorities : ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ถ€์—ฌํ•œ ๊ถŒํ•œ์„ GrantedAuthority๋กœ ์ถ”์ƒํ™”ํ•˜์—ฌ ์‚ฌ์šฉํ•œ๋‹ค.

<UserDetails>
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
UserRoleEnum role = user.getRole();
    String authority = role.getAuthority();
    System.out.println("authority = " + authority);

    SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority);
    Collection<GrantedAuthority> authorities = new ArrayList<>();
    authorities.add(simpleGrantedAuthority);

    return authorities;
}

Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

UsernamePasswordAuthenticationToken์€ Authenticaion์„ implementsํ•œ AbstractAuthenticationToken์˜ ํ•˜์œ„ ํด๋ž˜์Šค๋กœ, ์ธ์ฆ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.

UserDetailService๋Š” username/password ์ธ์ฆ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•  ๋•Œ ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•˜๊ณ  ๊ฒ€์ฆํ•œ ํ›„ UserDetails๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

Customํ•˜์—ฌ ๋นˆ์œผ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค. ->

UserDetailService์—์„œ ๋ฐ˜ํ™˜ํ•œ, ๊ฒ€์ฆ๋œ UserDetails๋Š”

UsernamePasswordAuthenticationToken ํƒ€์ž…์˜ Authenticaion์„ ๋งŒ๋“œ๋Š”๋ฐ ์‚ฌ์šฉ๋˜๋ฉฐ ๊ทธ ์ธ์ฆ ๊ฐ์ฒด๋Š” SecurityContextHoleder์— ์ €์žฅ๋œ๋‹ค. Customํ•˜์—ฌ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค. 

 

WebSecurityConfig

// ์Šคํ”„๋ง ๋ถ€ํŠธ 2.7.6์œผ๋กœ ์„ค์ •ํ•  ๊ฒƒ : 3.0.1์—์„œ๋Š” ์ž‘๋™ํ•˜์ง€ ์•Š๋Š” ์ฝ”๋“œ
package com.sparta.springsecurity.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.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity // ์Šคํ”„๋ง Security ์ง€์›์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•จ
public class WebSecurityConfig {

		@Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        // h2-console ์‚ฌ์šฉ ๋ฐ resources ์ ‘๊ทผ ํ—ˆ์šฉ ์„ค์ •
        return (web) -> web.ignoring()
                .requestMatchers(PathRequest.toH2Console())
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // CSRF ์„ค์ •
        http.csrf().disable();
        
				http.authorizeRequests()
                .antMatchers("/h2-console/**").permitAll()
                .antMatchers("/css/**").permitAll()
                .antMatchers("/js/**").permitAll()
                .antMatchers("/images/**").permitAll()
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                .anyRequest().authenticated();

        // ๋กœ๊ทธ์ธ ์‚ฌ์šฉ
        http.formLogin();
        
        return http.build();
    }

}

* ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์˜ ์ธ์ฆ : Session๋ฐฉ์‹์„ ์‚ฌ์šฉ

 

UserService ์•ˆ์—์„œ loadUserByUsername ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•œ ๊ฐ’์„ UserDetails ํƒ€์ž…์˜ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ ๋’ค ํ• ๋‹นํ•œ๋‹ค.
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
๊ทธ ๊ฐ์ฒด์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ํ™œ์šฉํ•˜์—ฌ Authentication ํƒ€์ž…์˜ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());

 

Custom ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ์Šคํ”„๋ง์— ํ†ต์ง€ํ•˜๋ฉด, ์Šคํ”„๋ง์€ ๋””ํดํŠธ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ƒ์„ฑํ•˜์ง€
์•Š๊ณ , ๊ธฐ์กด์˜ UserDetails, UserDetailsService๋ฅผ ์‚ฌ์šฉํ•œ ์ธ์ฆ ๋˜ํ•œ ์ง„ํ–‰ํ•˜์ง€ ์•Š๋Š”๋‹ค.

-> DB์— ํšŒ์›๊ฐ€์ž…์— ์„ฑ๊ณตํ•œ User ์ •๋ณด๋ฅผ ์ €์žฅ ๋ฐ ๋กœ๊ทธ์ธ ํ• ๋•Œ User ๊ฐ์ฒด๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ UserDetails ์ •๋ณด๋ฅผ ๋งŒ๋“ค๊ณ , UserDetails ์ •๋ณด๋ฅผ ํ™œ์šฉํ•œ ํ•„ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด ์‚ฌ์šฉ์ž ์ธ์ฆ ๊ณผ์ •์„ ๊ฑฐ์น˜๋Š” ๊ธฐ๋Šฅ์„ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

 

๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์ž…๋ ฅ ๊ฐ’ ๊ทธ๋Œ€๋กœ DB์— ์ €์žฅ๋˜์–ด์„  ์•ˆ๋œ๋‹ค. ~

์•”ํ˜ธํ™”๋ฅผ ๊ฑฐ์ณ DB์— ์ €์žฅํ•ด์•ผ ํ•ด์ปค๊ฐ€ DB๋ฅผ ํ„ธ์–ด๋„ ๊ณ„์ • ์ •๋ณด๋ฅผ ๋ชจ๋‘ ํš๋“ํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์—

 

์ ์‘ํ˜• ๋‹จ๋ฐฉํ–ฅ ํ•จ์ˆ˜๋กœ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ €์žฅํ•˜๋Š” ๊ฒƒ์ด ์ถ”์„ธ : ์Šคํ”„๋ง๋„ ์‚ฌ์šฉํ•˜๋Š” ํ•จ์ˆ˜

-> ์–‘๋ฐฉํ–ฅ ์•”ํ˜ธ ์•Œ๊ณ ๋ฆฌ์ฆ˜ & ๋‹จ๋ฐฉํ–ฅ ์•”ํ˜ธ ์•Œ๊ณ ๋ฆฌ์ฆ˜

// ์‚ฌ์šฉ์˜ˆ์‹œ
// ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ
if(!passwordEncoder.matches("์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ", "์ €์žฅ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ")) {
		   throw new IllegalAccessError("๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.");
 }
  • boolean matches(CharSequence rawPassword, String encodedPassword);
    • rawPassword : ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ
    • encodedPassword : ์•”ํ˜ธํ™”๋˜์–ด DB ์— ์ €์žฅ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ

1. ์‚ฌ์šฉ์ž๋Š” ํšŒ์›๊ฐ€์ž…์„ ์ง„ํ–‰

2. ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์ €์žฅํ•  ๋•Œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™”ํ•˜์—ฌ ์ €์žฅ

3. ์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ์ธ์„ ์ง„ํ–‰

4. ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์ •๋ณด๋ฅผ ํ†ตํ•ด ์ €์žฅ๋œ ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๊ฐ€์ ธ์™€ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์•”ํ˜ธ์™€ ๋น„๊ต

5. ์‚ฌ์šฉ์ž ์ธ์ฆ์ด ์„ฑ๊ณตํ•˜๋ฉด ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ JWT ํ† ํฐ์„ ์ƒ์„ฑํ•˜์—ฌ Header์— ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์‚ฌ์šฉ์ž๋Š” ์ด๋ฅผ ์ฟ ํ‚ค์ €์žฅ์†Œ์— ์ €์žฅ

6. ์‚ฌ์šฉ์ž๋Š” ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ๊ณผ ๊ฐ™์€ ์š”์ฒญ์„ ์ง„ํ–‰ํ•  ๋•Œ ๋ฐœ๊ธ‰๋ฐ›์€ JWT ํ† ํฐ์„ ๊ฐ™์ด ๋ณด๋‚ด๊ณ  ์„œ๋ฒ„๋Š” ์ด๋ฅผ ๋น ๋ฅด๊ฒŒ ์ธ์ฆ ํ•˜๊ณ  ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ์ˆ˜ํ–‰

 

+ Custom Filter ์ถ”๊ฐ€

// Custom Filter ๋“ฑ๋กํ•˜๊ธฐ
http.addFilterBefore(new CustomSecurityFilter(userDetailsService, passwordEncoder()), UsernamePasswordAuthenticationFilter.class);
// ๊ฐ์ฒด ํŒŒ๋ผ๋ฏธํ„ฐ ์ˆœ์„œ๋Œ€๋กœ : principal, credentials, authorities

+ ๋กœ๊ทธ์ธ Post ๋ฉ”์„œ๋“œ : UserController + @AuthenticationPrincipal : ์ธ์ฆ ๊ฐ์ฒด์˜ principal ์ธ์Šคํ„ด์Šค์˜ ๊ฐ’์„ ๊ฐ€์ ธ์˜จ๋‹ค.

 

@Secured

: ๊ถŒํ•œ ์„ค์ • ๋ฐฉ๋ฒ•

-> ์ปจํŠธ๋กค๋Ÿฌ์— ํ•ด๋‹น ๋ฉ”์„œ๋“œ ์œ„์— ์–ด๋…ธํ…Œ์ด์…˜ ํ‘œ์‹œ

// (๊ด€๋ฆฌ์ž์šฉ) ๋“ฑ๋ก๋œ ๋ชจ๋“  ์ƒํ’ˆ ๋ชฉ๋ก ์กฐํšŒ
// MySelectShop ์˜ˆ์‹œ
    @Secured("ROLE_ADMIN")
    @GetMapping("/api/admin/products")
    public List<Product> getAllProducts() {
        return productService.getAllProducts();
    }

์ ‘๊ทผ ์ œํ•œ ํŽ˜์ด์ง€ ์„ค์ •

1) TestController ์ƒ์„ฑ

2) UserController์— "/forbidden" Post ๋ฉ”์„œ๋“œ ์ƒ์„ฑ

3) WebSecurityConfig

// ์ ‘๊ทผ ์ œํ•œ ํŽ˜์ด์ง€ ์ด๋™ ์„ค์ •
http.exceptionHandling().accessDeniedPage("/api/user/forbidden");

4) TestApi์— @Secured๋กœ ๊ถŒํ•œ ์„ค์ •

 

401, 403 Error ExceptionHandling

1) SecurityExceptionDto ์ƒ์„ฑ : ์˜ค๋ฅ˜ ๋ฐœ์ƒ์‹œ ์ฝ”๋“œ์™€ ๋ฉ”์„ธ์ง€๋ฅผ ์ปค์Šคํ…€ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ฒ ๋‹ค.

2) CustomAccessDeniedHandler ์ƒ์„ฑ

3) CustomAuthenticationEntryPoint ์ƒ์„ฑ

4) WebSecurityConfig

// ์ ‘๊ทผ ์ œํ•œ ํŽ˜์ด์ง€ ์ด๋™ ์„ค์ •
// http.exceptionHandling().accessDeniedPage("/api/user/forbidden");        
		
// 401 Error ์ฒ˜๋ฆฌ, Authorization ์ฆ‰, ์ธ์ฆ๊ณผ์ •์—์„œ ์‹คํŒจํ•  ์‹œ ์ฒ˜๋ฆฌ
http.exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint);

// 403 Error ์ฒ˜๋ฆฌ, ์ธ์ฆ๊ณผ๋Š” ๋ณ„๊ฐœ๋กœ ์ถ”๊ฐ€์ ์ธ ๊ถŒํ•œ์ด ์ถฉ์กฑ๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ
http.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler);

403 (Forbidden) ํด๋ผ์ด์–ธํŠธ ์˜ค๋ฅ˜ ์ƒํƒœ. ์„œ๋ฒ„์— ์š”์ฒญ์ด ์ „๋‹ฌ๋˜์—ˆ์ง€๋งŒ, ๊ถŒํ•œ ๋•Œ๋ฌธ์— ๊ฑฐ์ ˆ๋จ.

728x90