들어가기 앞서
본 글은 스프링 시큐리티 서적인 Spring Security in Action을 읽고 책 속에 나와 있는 예제를 공부하며 얻은 지식을 바탕으로 적은 글입니다.
이전 글
[스프링 시큐리티 무작정 따라하기] 1. Hello Spring Security
[스프링 시큐리티 무작정 따라하기] 2. 회원 관리하기
[스프링 시큐리티 무작정 따라하기] 3. 인증 구현하기
오늘의 목표
- 권한(authority)으로 endpoint로의 접근 제어하기
- 역할(roles)로 endpoint로의 접근 제어하기
- matcher 메소드를 통한 인가(authorization) 구현하기
1. 권한(authority)으로 endpoint로의 접근 제어하기
스프링 시큐리티는 권한을 GrantedAuthority 인터페이스를 통해 관리합니다. GrantedAuthority는 [스프링 시큐리티 무작정 따라하기] 2. 회원 관리하기의 2장에서 간략하게 살펴본 바 있습니다. 오늘 글에서는 GrantedAuthority와 UserDetails의 코드를 살펴보며 GrantedAuthority에 대해, 그리고 GrantedAuthority와 UserDetails의 관계에 대해 조금 더 자세히 살펴보도록 하겠습니다.
public interface GrantedAuthority extends Serializable {
String getAuthority();
}
먼저 GrantedAuthority 코드입니다. GrantedAuthority는 인터페이스로 회원의 권한을 정의하는 특정 클래스로 하여금 GrantedAuthority를 구현하게 할 수 있습니다. 스프링 시큐리티가 제공하는 GrantedAuthority의 대표적인 구현체는 SimpleGrantedAuthority로 SimpleGrantedAuthority의 개략적인 형태는 아래와 같습니다. GratnedAuthority 인터페이스를 구현한 것을 확인할 수 있습니다.
public final class SimpleGrantedAuthority implements GrantedAuthority {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final String role;
public SimpleGrantedAuthority(String role) {
Assert.hasText(role, "A granted authority textual representation is required");
this.role = role;
}
@Override
public String getAuthority() {
return this.role;
}
...
}
다음으로 살펴볼 인터페이스는 이미 이전 글에서 자세히 살펴본 적이 있는 UserDetails 인터페이스입니다. 오늘은 UserDetails를 GrantedAuthority와의 관계를 조망하기 위해서만 살펴볼 예정이므로 UserDetails에 대한 보다 자세한 정보를 원하신다면 아래의 링크를 통해 확인해 주시기 바랍니다.
https://gaebalsogi.tistory.com/80
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
...
}
위의 코드를 살펴보면 UserDetails는 내부적으로 하나 이상의 GrantedAuthority 구현체를 가질 수 있음을 알 수 있습니다. Collection 인터페이스는 List, Map, Set 중 어느 것이라도 올 수 있기 때문에 개발자는 UserDetails를 구현할 때 필요한 자료구조를 사용하여 UserDetails와 GrantedAuthority의 관계를 정의할 수 있습니다. UserDetails는 스프링 시큐리티가 사용하는 회원 객체이기 때문에 한 명의 회원은 하나 이상의 권한을 가질 수 있음을 알 수 있습니다. UserDetails의 getAuthorities 메소드는 null을 리턴할 수 없습니다. 아래는 getAuthorities가 null을 리턴할 수 없다는 부분을 알려주는 주석입니다.
/**
* Returns the authorities granted to the user. Cannot return null.
* @return the authorities, sorted by natural key (never null)
*/
Collection<? extends GrantedAuthority> getAuthorities();
위의 주석을 통해 회원은 반드시 하나 이상의 권한을 가져야 함을 알 수 있습니다.
자, 이제 새로운 스프링부트 프로젝트를 생성해보도록 하겠습니다. [스프링 시큐리티 무작정 따라하기] 1. Hello Spring Security의 글을 참고하여 스프링 부트 프로젝트를 생성하셔도 되고, 이전 프로젝트를 그대로 사용하셔도 괜찮습니다. 필요한 의존성은 Spring Web과 Spring Security 두 가지입니다.
다음으로 IDE에서 프로젝트를 여신 후, 아래의 캡처처럼 패키지, 클래스들을 세팅합니다. 이 세팅은 고정되어 있는 것은 아니기 때문에 다른 패키지 구조를 원하신다면 별도의 패키지 구조로 실습을 진행하셔도 무방합니다. 다만, 클래스들은 하나 하나 각각의 필요가 있는 클래스들이기 때문에 ProjectConfiguration, HelloController, InitData의 세 클래스는 반드시 생성하도록 합니다.
HelloController
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello!!";
}
}
HelloController에 다음과 같이 코드를 작성합니다. @RestController 에노테이션을 넣어 오늘 실습에 중요하지 않은 뷰를 별도로 만들지 않아도 되게끔 하였습니다. 위의 코드는 /hello의 경로로 접근했을 때, Hello!! 라는 문자열을 응답으로 보내는 코드입니다.
InitData
@Component
@RequiredArgsConstructor
public class InitData {
private final UserDetailsService userDetailsService;
@PostConstruct
public void saveUsers() {
UserDetails userA = User.withUsername("jinseok")
.password("1234")
.authorities("WRITE", "READ", "UPDATE")
.build();
UserDetails userB = User.withUsername("suchon")
.password("1234")
.authorities("READ")
.build();
UserDetailsManager userDetailsService = (UserDetailsManager) this.userDetailsService;
userDetailsService.createUser(userA);
userDetailsService.createUser(userB);
}
}
다음으로 InitData 클래스입니다. InitData에 대해서는 이전 글에서도 사용한 적이 있기 때문에 크게 설명하지 않고 넘어가도록 하겠습니다. 주목할 부분은 saveUsers 메소드 내부의 authorities 메소드입니다. 위의 코드에서는 jinseok이라는 회원이 WRITE, READ, UPDATE의 권한을, suchon이라는 회원이 READ의 권한을 가지고 있다는 것을 알 수 있습니다. User 객체는 스프링 시큐리티가 제공하는 기본 UserDetails 구현체이며 GrantedAuthority 구현체로 SimpleGrantedAuthority를 사용합니다. SimpleGrantedAuthority는 내부에 role라는 이름의 문자열 필드를 가지고 있어, 문자열 배열을 제공하는 것으로 각각의 권한을 생성할 수 있습니다.
2명의 회원
- jinseok <- WRITE, READ, UPDATE
- suchon <- READ
ProjectConfiguration
@Configuration
@EnableWebSecurity
public class ProjectConfiguration {
@Bean
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
오늘 실습의 목표인 접근 제어 설정을 처리할 설정 클래스인 ProjectConfiguration의 코드를 다음과 같이 작성합니다. 위의 코드는 단순히 UserDetailsService와 PasswordEncoder의 빈을 주입하는 코드입니다. 아래에서 하나하나 접근 제어 설정을 추가하도록 하겠습니다.
본격적인 실습에 앞서 접근제어를 담당하는 메소드들을 살펴보도록 하겠습니다. 관련 정보는 아래와 같습니다.
- permitAll() : 경로 상의 모든 접근을 승인합니다. 로그인하지 않은 회원도 접근할 수 있습니다.
- denyAll() : 경로 상의 모든 접근을 승인하지 않습니다.
- hasAuthority(String authority) : 매개변수로 주어진 권한만 접근 승인합니다. 만약 hasAuthority("READ")와 같이 코드를 작성하면 READ 권한을 가진 회원만 접근할 수 있습니다.
- hasAnyAuthority(String... authorities) : 매개변수로 주어진 하나 이상의 권한을 접근 승인합니다.
- access(String attribute) : access는 조금 독특한 메소드로 Spring Expression Language(SpEL)을 매개변수로 받습니다. 위의 설명한 네 메소드보다 조금 더 세밀한 권한 설정이 가능하지만 상대적으로 읽기 어렵기 때문에 꼭 필요한 경우에만 사용하는 것이 권장됩니다.
아래는 위에 설명한 다섯 가지의 메소드를 각각 구현한 코드 스니펫입니다. 하나하나 구현해보며 사용법을 익히고 프로젝트를 돌려가며 테스트 해보시기 바랍니다.
permitAll()
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.anyRequest().permitAll();
return http.build();
}
모든 경로에 대해 접근 승인을 하는 설정입니다. localhost:8080/hello로 접근했을 때, 로그인 하지 않아도 접근이 된다는 것을 확인할 수 있습니다.
denyAll()
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.anyRequest().denyAll();
return http.build();
}
모든 경로에 대해 접근 승인을 불허합니다. 로그인에 성공하더라도 인덱스페이지, /hello 경로에 접근할 수 없음을 알 수 있습니다.
hasAuthority(String authority)
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.anyRequest().hasAuthority("WRITE");
return http.build();
}
모든 경로에 대해 WRITE라는 권한을 가진 회원에게만 접근을 승인합니다. WRTIE 권한을 가진 회원으로 로그인 시 /hello 경로에 접근 가능하지만 WRITE 권한을 가지고 있지 않다면 접근이 허용되지 않습니다.
hasAnyAuthority(String... authorities)
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.anyRequest().hasAnyAuthority("WRITE", "READ");
return http.build();
}
모든 경로에 대해 WRITE와 READ 권한을 가진 회원에게만 접근을 승인합니다.
access(String attribute)
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.anyRequest().access("hasAuthority('WRITE') and !hasAuthority('DELETE')");
return http.build();
}
access 메소드의 매개변수에는 SpEL이 들어갈 수 있습니다. 위의 코드는 WRTIE 권한을 가지고 있지만 DELETE 권한을 가지고 있지 않은 회원에게만 접근을 승인하는 코드입니다. access 메소드는 hasAuthority, hasAnyAuthority를 사용할 수 없는 복잡한 경우에 요긴하게 사용할 수 있지만 코드의 가독성이 떨어지기 때문에 리펙토링 하기 어려워 진다는 단점이 있습니다. hasAuthority, hasAnyAuthority를 우선적으로 사용하되, 경우에 따라 access를 사용하는 방법이 권장됩니다.
2. 역할(roles)로 endpoint로의 접근 제어하기
다음으로 살펴볼 요소는 역할(role)입니다. 역할은 하나 이상의 권한을 포함할 수 있는 포괄적인 개념으로, 권한이 집합 내의 개별적인 요소라면, 역할은 집합에 해당하는 단위라고 생각하셔도 무방합니다. 스프링 시큐리티 내에서의 모든 역할을 접두사(prefix)로 ROLE_ 이라는 키워드를 갖는다는 특징이 있습니다.
예를 들어 한 개의 게시판이 존재하는 사이트가 있다고 가정해보겠습니다. 게시판에서 회원이 행할 수 있는 행위는 게시판에 글을 작성(create)하거나, 글을 조회(read)하거나, 글을 수정(update)하거나, 글을 삭제(delete)하는 4가지의 범주로 세분화해볼 수 있습니다. 따라서 사이트에는 CREATE, READ, UPDATE, DELETE의 네 가지 권한이 존재하는데, 모든 회원이 이 네 권한을 모두 행사할 수 있다면 크고 작은 혼란이 올 수 있습니다(사이트에 처음 방문한 회원이 사이트 내의 모든 게시글을 삭제하는 일이 발생한다고 생각해 보십시오!). 따라서 회원의 역할에 따라 권한 범위를 나누어 특정 회원에게 특정 역할을 부여하는 방식으로 시스템 내의 권한 문제를 해결할 수 있습니다.
이렇게 권한을 역할 내에 포함하는 이유는 같은 역할군에 해당하는 회원에게 적용되는 권한의 범위가 동일하기 때문입니다. 권한들을 역할 내에서 관리한다면 시스템을 보다 일관적이고 체계적으로 관리할 수 있습니다.
아래의 도식을 통해 위의 예에서 사용된 사이트의 역할과 권한 부여를 확인할 수 있습니다.
역할(Role) | 권한(Authority) |
ROLE_ADMIN | CREATE, READ, UPDATE, DELETE |
ROLE_MEMBER | CREATE, READ, UPDATE |
ROLE_GUEST | READ |
위의 도식처럼 역할을 정의해 권한의 범위를 지정한다면, 가독성이 높아짐은 물론 실수로 불필요한 회원에게 불필요한 권한을 부여하는 불상사를 막을 수 있습니다. 또한 회원에게 부여되는 권한이 역할 내에서 정의되기 때문에, 특정 역할군에 속하는 모든 회원들의 권한을 변경해야 할 때 훨씬 더 용이하게 이를 컨트롤할 수 있습니다.
다시 프로젝트를 열어 InitData를 다음과 같이 수정합니다. 이번에는 권한 대신 회원에게 역할을 부여할 예정입니다.
InitData
@Component
@RequiredArgsConstructor
public class InitData {
private final UserDetailsService userDetailsService;
@PostConstruct
public void saveUsers() {
// role 정의 방법 1
UserDetails userA = User.withUsername("jinseok")
.password("1234")
.authorities("ROLE_ADMIN")
.build();
UserDetails userB = User.withUsername("suchon")
.password("1234")
.authorities("ROLE_MANAGER")
.build();
UserDetailsManager userDetailsService = (UserDetailsManager) this.userDetailsService;
userDetailsService.createUser(userA);
userDetailsService.createUser(userB);
}
}
회원에게 역할을 부여하는 방법은 두 가지입니다. 그 중 첫 번째는 authorities 메소드 내부에 해당 역할의 이름을 기입하는 것입니다. 이 때, 반드시 접두사 ROLE_ 을 기입하여야 합니다.
@Component
@RequiredArgsConstructor
public class InitData {
private final UserDetailsService userDetailsService;
@PostConstruct
public void saveUsers() {
// role 정의 방법 2
UserDetails userA = User.withUsername("jinseok")
.password("1234")
.roles("ADMIN")
.build();
UserDetails userB = User.withUsername("suchon")
.password("1234")
.roles("MANAGER")
.build();
UserDetailsManager userDetailsService = (UserDetailsManager) this.userDetailsService;
userDetailsService.createUser(userA);
userDetailsService.createUser(userB);
}
}
두 번째 방법은 roles 메소드를 사용하는 것입니다. 유의해야 할 부분은 roles 메소드를 사용할 경우 접두사 ROLE_ 을 사용할 경우 cannot start with ROLE_ (it is automatically added) 예외가 터진다는 부분입니다. roles 메소드를 사용할 때는 접두사 없이, 역할의 이름만 기입합니다.
ProjectConfiguration
접근 범위를 설정하는 설정 클래스에서 역할의 접근 범위를 설정하는 방법은 권한(authority)의 경우와 크게 다르지 않습니다. 다만 hasAuthority와 hasAnyAuthority 대신 각각 hasRole과 hasAnyRole을 사용한다는 차이가 있습니다.
- hasRole(String role) : 매개변수로 주어진 역할만 접근 승인합니다.
- hasAnyRole(String... roles) : 매개변수로 주어진 하나 이상의 역할을 접근 승인합니다.
각각의 메소드의 사용은 권한의 경우와 완전히 동일하지만 hasRole과 hasAnyRole을 사용할 경우, 접두사를 제거하고 사용하는 부분이 다릅니다. 아래의 코드를 참고해주시기 바랍니다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.anyRequest().hasRole("ADMIN"); // 접두사 ROLE_ 은 역할을 선언할 때만 사용. 역할을 사용할 때는 빼고 쓴다.
return http.build();
}
역할(role)과 권한(authority)를 실제 프로덕션 수준의 레벨에서 사용하는 방법은 차후의 글에서 조금 더 자세히 살펴보도록 하겠습니다.
3. matcher 메소드를 통한 인가(authorization) 구현하기
혹시 위의 1번과 2번 장을 학습하며 무언가 이상하다는 느낌을 받으시진 않으셨나요? 권한과 역할을 공부하기 위한 학습용의 예제로서는 부족한 부분이 없지만 실제 구동하는 애플리케이션에 대입하기에는 무언가 이상한 부분이 하나 있습니다. 그 부분은 바로 권한과 역할에 따른 인가(authorization) 처리가 사이트의 모든 경로에 적용된다는 부분인데, 실제로 웹사이트를 구현한다면 특정 페이지는 로그인을 한 페이지, 특정 페이지는 관리자만 접근이 가능한 페이지 등등으로 인가의 세분화가 필요한 일이 비일비재합니다. 그런데 위의 두 개의 장에서는 경로에 따른 인가 작업의 분화를 처리할 수 없었습니다.
이를 위해서는 matcher 메소드가 필요합니다. 스프링 시큐리티에서 사용이 가능한 matcher 메소드는 크게 세 가지로 각각의 matcher 메소드는 아래와 같습니다.
- mvcMatchers
- antMatchers
- regexMathcers
mvcMatchers
public abstract C mvcMatchers(String... mvcPatterns);
public abstract C mvcMatchers(HttpMethod method, String... mvcPatterns);
mvcMatchers를 사용할 수 있는 방법은 두 가지로, 경로에 해당하는 문자열을 매개변수로 넣어 매개변수의 경로로 인가 분기를 처리하는 방법이 첫 번째, 경로에 해당하는 문자열과 http method를 기입해 예를 들면 GET /hello 와 같이 조금 더 세분화된 인가 분기를 처리하는 방법이 두 번째입니다. 백문이 불여일타이니, mvcMatchers를 사용한 두 가지 코드를 살펴보며 조금 더 자세히 논의를 이어보도록 하겠습니다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.mvcMatchers("/hello").permitAll()
.anyRequest().denyAll();
return http.build();
}
첫 번째 방법입니다. mvcMatchers 내부에 /hello 문자열이 있는 이 코드는, /hello 경로로 접근했을 때는 모든 접근을 승인하고 나머지 모든 경로에는 접근을 불허하는 내용을 담고 있습니다. 인가 설정 정보는 위에서 아래로 내려가며 위에 적힌 것이 보다 높은 우선 순위를 가진다는 부분을 명심해 주시기 바랍니다. 만약 더 넓은 범위의 경로를 위 칸 코드에 작성하고, 더 좁은 범위의 경로를 아래 칸 코드에 작성하면 아래 칸 코드는 작동하지 않습니다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.mvcMatchers(HttpMethod.GET, "/hello").permitAll()
.anyRequest().denyAll();
return http.build();
}
두 번째 방법입니다. 매개변수에 http method를 추가하여 이를테면 GET /hello 의 요청에만 접근을 승인하고 POST /hello, PUT /hello를 포함한 나머지의 모든 경우에는 접근을 불허하는 설정을 작성할 수 있습니다.
아래처럼 경로 문자열에 /** 를 추가해 hello 이하의 모든 경로에 같은 설정을 부여할 수도 있습니다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.mvcMatchers(HttpMethod.GET, "/hello/**").permitAll()
.anyRequest().denyAll();
return http.build();
}
📌 참고: 스프링 시큐리티의 csrf 설정
사실, mvcMatchers 메소드에 HttpMethod.GET 매개변수를 붙이지 않아도 POST /hello, PUT /hello 등 GET을 제외한 모든 http method는 접근이 불가능합니다. 이는 스프링 시큐리티가 기본적으로 제공하는 csrf 방어 로직 때문으로 스프링 시큐리티는 웹 해킹 기법 중 하나인 csrf 공격(Cross Site Request Forgery)을 방어하기 위해 GET 이외의 모든 메소드를 접근 불허합니다. 따라서 인가와 관련된 원활한 공부를 위해서는 스프링 시큐리티의 csrf 설정을 꺼둘 필요가 있는데, 아래처럼 코드를 한 줄 추가해 주시면 csrf 보안 로직이 작동되지 않아 원활한 테스트가 가능해집니다. 다만 csrf 설정을 꺼두는 이 행동은 공부를 하기 위해서지, csrf 보안 로직이 걸리적거린다고 실제 애플리케이션에서도 꺼두고 코딩을 하는 불상사는 발생하지 않도록 주의합시다. csrf와 csrf 방어로직에 관련해서는 추후에 조금 더 자세히 살펴볼 수 있도록 하겠습니다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.mvcMatchers(HttpMethod.GET, "/hello").permitAll()
.anyRequest().denyAll();
http.csrf().disable(); // csrf 방어로직 스위치 off
return http.build();
}
antMatchers
다음은 antMatchers 메소드입니다. antMatchers의 사용법은 앞서 살펴본 mvcMatchers와 완전히 동일합니다. 그렇다면 mvcMatchers와 antMatchers의 차이점은 무엇이 있을까요? 아래의 예제를 통해 살펴보도록 하겠습니다. 우선 HelloContoller를 다음과 같이 변경합니다.
HelloController
@RestController
public class HelloController {
@GetMapping("helloMvc")
public String helloMvc() {
return "hello mvc matchers!!";
}
@GetMapping("helloAnt")
public String helloAnt() {
return "hello ant matchers!!";
}
}
GET /helloMvc 경로와 GET /helloAnt 경로를 각각 캐치하는 컨트롤러입니다. 다음으로 ProjectConfiguration 클래스를 다음과 같이 수정합니다.
ProjectConfiguration
@Configuration
@EnableWebSecurity
public class ProjectConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.mvcMatchers(HttpMethod.GET, "/helloMvc").denyAll()
.antMatchers(HttpMethod.GET, "/helloAnt").denyAll()
.anyRequest().permitAll();
http.csrf().disable();
return http.build();
}
...
}
각각 mvcMatchers와 antMatchers 메소드로 GET /helloMvc와 GET /helloAnt의 접근을 막은 것을 확인할 수 있습니다. 위의 코드를 정리한 도식은 아래와 같습니다.
matchers | method + 경로 | 접근여부 |
mvcMatchers | GET /helloMvc | 어떠한 경우에도 접근되어서는 안됨 |
antMatchers | GET /helloAnt | 어떠한 경우에도 접근되어서는 안됨 |
이외의 모든 경로 | 모든 경우 접근 가능해야 함 |
자, 이제 프로젝트를 구동한 후 테스트를 진행해보겠습니다.
mvcMatchers 테스트
아래의 두 가지 경우에 대한 테스트를 진행해 주십시오.
- GET /helloMvc
- GET /helloMvc/
테스트를 진행해 보시면, 두 경우 모두 403 Forbidden 상태코드가 응답으로 내려오는 것을 확인할 수 있습니다. 이번에는 antMatchers를 테스트 해보겠습니다.
antMatchers 테스트
마찬가지로 아래의 두 가지 테스트를 진행해 주십시오.
- GET /helloAnt
- GET /helloAnt/
테스트 결과를 확인해보시면 첫 번째의 경우, 즉 GET /helloAnt에는 403 Forbidden 상태코드가 응답으로 내려오지만 두 번째 경우에는 정상적으로 컨트롤러가 호출되어 200의 상태코드와 함께 정상적인 응답이 내려오는 것을 확인할 수 있습니다. 이는 우측에 위치한 슬레시(/) 때문으로, antMatchers의 경우 경로를 적힌 그대로만 해석하기 때문에 경로에 적히지 않은 부분에 대해서는 동작하지 않는다는 것을 확인할 수 있습니다. 반면 mvcMatchers의 경우 스프링 프레임워크의 도움을 받아 조금 더 광범위한 부분이 캐치되어 경로 설정이 적용된다는 것을 알 수 있습니다.
이처럼, antMatchers는 mvcMatchers에 비해 조금 더 보안에 취약하다는 부분을 알 수 있습니다. 물론 개발자가 모든 경로에 대해 철저히 방어하고 이를 로직으로 녹여낸다면 괜찮겠지만, 그렇지 않은 경우 antMatchers는 mvcMatchers에 비해 조금은 더 위험할 수 있습니다. 다만 많은 프로젝트들이 antMatchers를 사용하기 때문에 antMatchers는 좋지 않아, 하고 넘어가기 보다는 antMathcers가 이러한 부분에서 보안적으로 조금은 더 취약하구나를 명확히 인지하고 있는 것이 중요하다고 생각합니다.
다음 글
'Spring Framework > 스프링 시큐리티' 카테고리의 다른 글
[스프링 시큐리티 무작정 따라하기] 5. 필터(Filter) 이해하기 (0) | 2022.09.22 |
---|---|
[스프링 시큐리티 무작정 따라하기] 3. 인증 구현하기 (2) | 2022.08.16 |
[스프링 시큐리티 무작정 따라하기] 2. 회원 관리하기 (2) | 2022.07.31 |
[스프링 시큐리티 무작정 따라하기] 1. Hello Spring Security (0) | 2022.07.12 |
[스프링 시큐리티 - 인증] 공식문서 번역하며 공부하기 - 서블릿 인증 아키텍처 (0) | 2022.01.11 |