-
SpringSecurity 05Spring&SpringBoot/SpringSecurity 2025. 9. 24. 13:03
인가 프로세스
요청 기반 권한 부여 - HttpSecurity.authorizeHttpRequests()
- Spring Security는 요청 기반 권한 부여와 메서드 기반 권한 부여를 통해 자원에 대한 심층적 방어 제공
- 요청 기반 권한 부여는 클라이언트의 요청(HttpServletRequest)에 대한 권한 부여를 모델링하는 것이며 HttpSecurity 인스턴스를 사용하여 권한 규칙을 선언
authorizeHttpRequests() API
- requestMatchers()
- Http 요청의 URL 패턴, HTTP 메서드, 요청 파라미터 등을 기반으로 어떤 요청에 대해서는 특정 보안 설정을 적용하고 다른 요청에 대해서는 적용하지 않도록 제어
- 예) 특정 API 경로에만 CSRF 보호 적용하거나 특정 경로에 대해 이증 요구하지 않도록 설정
- requestMatchers(String ... urlPatterns) : 자원 경로 한 개 이상 정의
- requestMatchers(RequestMatcher ... requestMatchers) : AntPathRequstMatcher, MvcRequestMatcher 등 구현체 사용해서 자원 경로 한 개 이상 정의
- requestMatchers(HttpMethod method, String ... urlPatterns) : Http Method와 자원 경로 한 개 이상 정의
- 엔드포인트 & 권한부여
- requestMatchers("/admin").hasRole("/ADMIN")
@EnableWebSecurity @Configuration public class SecurityConfigV1 { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(authorize -> authorize .requestMatchers("/user/{name}") .access(new WebExpressionAuthorizationManager("#name == authentication.name")) .requestMatchers("/admin/db") .access(new WebExpressionAuthorizationManager("hasAythority('ROLE_DB') or hasAuthority('ROLE_ADMIN')")) .anyRequest().authenticated()) .formLogin(Customizer.withDefaults()); return http.build(); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password("{noop}1111") .roles("USER").build(); UserDetails manager = User.withUsername("manager") .password("{noop}1111") .roles("MANAGER").build(); UserDetails admin = User.withUsername("admin") .password("{noop}1111") .roles("ADMIN").build(); return new InMemoryUserDetailsManager(user); } }@RestController public class IndexController { @GetMapping("/") public String index() { return "index"; } @GetMapping("/user") public String user() { return "user"; } @GetMapping("/user/{name") public String userName(@PathVariable(value = "name") String name) { return "name"; } @GetMapping("/admin/db") public String admin() { return "admin"; } }- 클라이언트의 요청에 대하여 위에서부터 아래로 나열된 순서대로 처리하며 요청에 대하여 첫 번째 일치만 적용되고 다음 순서로 넘어가지 않는다
표현식 및 커스텀 권한 구현
- 표현식을 사용해서 권한 규칙을 설정하도록 WebExpressionAuthorizationManager 제공
- 표현식은 Security가 제공하는 권한 규칙을 사용하거나 사용자가 표현식을 커스텀하게 구현해서 설정 가능
- requestMatchers().access(new WebExpressionAuthorizationManager("expression"))
@Component("customWebSecurity") public class CustomWebSecurity { public boolean check(Authentication authentication, HttpServletRequest request) { return authentication.isAuthenticated; } }@EnableWebSecurity @Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, ApplicationContext context) throws Exception { DefaultHttpSecurityExpressionHandler expressionHandler = new DefaultHttpSecurityExpressionHandler(); expressionHandler.setApplicationContext(context); WebExpressionAuthorizationManager authorizationManager = new WebExpressionAuthorizationManager("@customWebSecurity.check(authentication, request"); authorizationManager.setExpressionHandler(expressionHandler); http.authorizeHttpRequests(authorize -> authorize .requestMatchers("/custom/**").access(authorizationManager) .anyRequest().authenticated()) .formLogin(Customizer.withDefaults()); return http.build(); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password("{noop}1111") .roles("USER").build(); UserDetails manager = User.withUsername("manager") .password("{noop}1111") .roles("MANAGER").build(); UserDetails admin = User.withUsername("admin") .password("{noop}1111") .roles("ADMIN").build(); return new InMemoryUserDetailsManager(user); } }커스텀 RequestMatcher 구현
- RequestMacher의 matcher 및 matches 메서드를 사용해 클라이언트의 요청 객체로부터 값을 검증하도록 커스텀한 RequestMatcher를 구현하고 requestMatchers() 메서드에 설정
public class CustomRequestMatcher implements RequestMatcher { private final String urlPattern; public CustomRequestMatcher(String urlPattern) { this.urlPattern = urlPattern; } @Override public boolean matches(HttpServletRequest request) { String requestURI = request.getRequestURI(); return requestURI.startsWith(urlPattern); } }@EnableWebSecurity @Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, ApplicationContext context) throws Exception { http.authorizeHttpRequests(authorize -> authorize .requestMatchers(new CustomRequestMatcher("/admin")).hasAuthority("ROLE_ADMIN") .anyRequest().authenticated()) .formLogin(Customizer.withDefaults()); return http.build(); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password("{noop}1111") .roles("USER").build(); UserDetails manager = User.withUsername("manager") .password("{noop}1111") .roles("MANAGER").build(); UserDetails admin = User.withUsername("admin") .password("{noop}1111") .roles("ADMIN").build(); return new InMemoryUserDetailsManager(user); } }요청 기반 권한 부여 - HttpSecurity.securityMatcher()
securityMatcher()
- securityMatcher(String .. urlPatterns)
- securityMatcher(RequestMatcher .. requestMatchers)
- http.sercurityMatcher("/api/**").authorizeHttpRequest(auth -> auth.requestMatchers(...))
- Spring MVC가 클래스 경로에 있으면 MvcRequestMatcher가 사용되고 그렇지 않으면 AntPathRequestMatcher가 사용된다
securityMatchers(Customizer<RequestMatcherConfigurer>)

- 특정 패턴에 해당하는 요청을 단일이 아닌 다중 설정으로 구성해서 보안 규칙을 적용할 수 있으며 현재의 규칙은 이전의 규칙을 대체하지 않는다
- 패턴1. http.securityMatchers((matchers) -> matchers.requestMatchers("/api/**", "/oauth/**"));
- 패턴2. http.securityMatchers((matchers) -> matchers.requestMatchers("/api/**"). requestMatchers( "/oauth/**"));
- 패턴3. http.securityMatchers((matchers) -> matchers.requestMatchers("/api/**")).securityMatchers((matchers) -> matchers.requestMatchers(" /oauth/**" ));
@EnableWebSecurity @Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth .anyRequest().authenticated()) .formLogin(Customizer.withDefaults()) ; return http.build(); } @Bean @Order(1) public SecurityFilterChain securityFilterChainV2(HttpSecurity http) throws Exception { http.securityMatchers(matchers -> matchers.requestMatchers("/api/**", "/oauth/**")) .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()); return http.build(); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password("{noop}1111") .roles("USER").build(); UserDetails manager = User.withUsername("manager") .password("{noop}1111") .roles("MANAGER").build(); UserDetails admin = User.withUsername("admin") .password("{noop}1111") .roles("ADMIN").build(); return new InMemoryUserDetailsManager(user); } }@RestController @RequiredArgsConstructor public class IndexController { @GetMapping("/") public String index() { return "index"; } @GetMapping("/custom") public String custom() { return "custom"; } @GetMapping("/user/{name}") public String userName(@PathVariable(value="name") String name) { return name; } @GetMapping("/api/photos") public String photos() { return "photos"; } @GetMapping("/oauth/login") public String oauth() { return "oauthLogin"; } }메서드 기반 권한 부여 - @PreAuthorize, @PostAuthorize
- @EnableMethodSecurity 어노테이션 추가
- SpEL(Spring Expression Language) 표현식 사용
@PreAuthorize
- 메서드가 실행되기 전 특정한 보안 조건이 충족되는지 확인하는 데 사용
- 보통 서비스 또는 컨트롤러 레이어의 메서드에 적용되어 해당 메서드가 호출되기 전 사용자의 인증 정보와 권한 검사
@EnableWebSecurity @EnableMethodSecurity @Configuration public class SecurityConfigV1 { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth .anyRequest().authenticated()) .formLogin(Customizer.withDefaults()) ; return http.build(); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password("{noop}1111") .roles("USER").build(); UserDetails db = User.withUsername("db") .password("{noop}1111") .roles("DB").build(); UserDetails admin = User.withUsername("admin") .password("{noop}1111") .roles("ADMIN", "SECURE").build(); return new InMemoryUserDetailsManager(user); } }@RestController public class MethodController { @GetMapping("/admin") @PreAuthorize("hasAuthority('ROLE_ADMIN')") public String admin() { return "admin"; } @GetMapping("/user") @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_USER')") public String user() { return "user"; } @GetMapping("/isAuthenticated") @PreAuthorize("isAuthenticated") public String isAuthenticated() { return "isAuthenticated"; } @GetMapping("/user/{id}") @PreAuthorize("#id == authentication.name") public String authentication(@PathVariable(name="id") String id) { return id; } }- 메서드 기반 보안 부여는 보통 service 쪽에서 많이 사용됨
@PostAuthorize
- 메서드가 실행된 후에 보안 검사 수행
@Getter @AllArgsConstructor public class Account { private String owner; private boolean isSecure; }@RestController public class MethodController { ... @GetMapping("/owner") @PostAuthorize("returnObject.owner == authentication.name") public Account owner(String name) { return new Account(name, false); } @GetMapping("/isSecure") @PostAuthorize("hasAuthority('ROLE_ADMIN') and returnObject.isSecure") public Account isSecure(String name, String secure) { return new Account(name, "Y".equals(secure)); } }메서드 기반 권한 부여 - @PreFilter, @PostFilter
- @PreFilter : 메서드가 실행되기 전에 메서드에 전달된 컬렉션 타입의 파라미터에 대한 필터링 수행
- @PostFilter : 메서드가 반환하는 컬렉션 타입의 결과에 대해 필터링 수행
@EnableWebSecurity @EnableMethodSecurity @Configuration public class SecurityConfigV1 { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth .anyRequest().authenticated()) .formLogin(Customizer.withDefaults()) .csrf(AbstractHttpConfigurer::disable) ; return http.build(); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password("{noop}1111") .roles("USER").build(); UserDetails db = User.withUsername("db") .password("{noop}1111") .roles("DB").build(); UserDetails admin = User.withUsername("admin") .password("{noop}1111") .roles("ADMIN", "SECURE").build(); return new InMemoryUserDetailsManager(user); } }@RestController @RequiredArgsConstructor public class MethodControllerV1 { private final DataService dataService; @PostMapping("/writeList") public List<Account> writeList(@RequestBody List<Account> data) { return dataService.writeList(data); } @PostMapping("/writeMap") public Map<String, Account> writeMap(@RequestBody List<Account> data) { Map<String, Account> accountMap = data.stream().collect(Collectors.toMap(account -> account.getOwner(), account -> account)); return dataService.writeMap(accountMap); } @GetMapping("/readList") public List<Account> readList() { return dataService.readList(); } }@Service public class DataService { @PreFilter("filterObject.owner == authentication.name") public List<Account> writeList(List<Account> data) { return data; } @PreFilter("filterObject.value.owner == authentication.name") public Map<String, Account> writeMap(Map<String, Account> data) { return data; } @PostFilter("filterObject.value.owner == authentication.name") public List<Account> readList() { return new ArrayList<>(List.of( new Account("user", false), new Account("db", false), new Account("admin", false) )); } }@Getter @Setter @NoArgsConstructor @AllArgsConstructor public class Account { private String owner; private boolean isSecure; }메서드 기반 권한 부여 - @Secured, JSR-250 및 부가 기능
- @Secured
- 지정된 권한(역할)을 가진 사용자만 해당 메서드를 호출할 수 있으며 더 풍부한 형식을 지원하는 @PreAuthorize 사용 권장
- Spring Security 설정에서 @EnableMethodSecurity(securedEnable=true) 설정 활성화 필요
- JSR-250
- @RolesAllowed, @PermitAll 및 @DenyAll 어노테이션 보안 기능 활성화
- Spring Security 설정에서 @EnableMethodSecurity(jsr250Enable = ture) 설정 활성화 필요
@RestController @RequiredArgsConstructor public class MethodController { private final DataService dataService; @GetMapping("/user") @Secured("ROLE_USER") public String writeList() { return "user"; } @GetMapping("/admin") @RolesAllowed("ADMIN") public String admin() { return "admin"; } @GetMapping("/permitAll") @PermitAll public String permitAll() { return "permitAll"; } @GetMapping("/denyAll") @DenyAll public String denyAll() { return "denyAll"; } }@EnableWebSecurity @EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true) @Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth .anyRequest().permitAll()) .formLogin(Customizer.withDefaults()) .csrf(AbstractHttpConfigurer::disable) ; return http.build(); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("user") .password("{noop}1111") .roles("USER").build(); UserDetails db = User.withUsername("db") .password("{noop}1111") .roles("DB").build(); UserDetails admin = User.withUsername("admin") .password("{noop}1111") .roles("ADMIN", "SECURE").build(); return new InMemoryUserDetailsManager(user); } }- 메타정보 추가
@RestController @RequiredArgsConstructor public class MethodController { ... @GetMapping("/isAdmin") @IsAdmin public String isAdmin() { return "isAdmin"; } @GetMapping("/ownerShip") @OwnerShip public Account ownerShip(String name) { return new Account(name, false); } }@Documented @Retention(RUNTIME) @Target({TYPE, METHOD}) @PreAuthorize("hasRole('ADMIN')") public @interface IsAdmin { }@Documented @Retention(RUNTIME) @Target({TYPE, METHOD}) @PostAuthorize("returnObject.owner == authentication.name") public @interface OwnerShip { }- 커스텀 빈을 사용하여 표현식 구하기
@RestController @RequiredArgsConstructor public class MethodController { ... @GetMapping("/delete") @PreAuthorize("@myAuthorizer.isUser(#root)") public String delete() { return "delete"; } }@Component("myAuthorizer") public class MyAuthorizer { public boolean isUser(MethodSecurityExpressionOperations root) { return root.hasAnyAuthority("ROLE_USER"); } }정적 자원 관리
- RequestMatcher 인스턴스를 등록하여 무시해야 할 요청을 지정할 수 있다
- 주로 정적 자원(이미지, CSS, JavaScript 파일 등)에 대한 요청이나 특정 엔드포인트가 보안 필터를 거치지 않도록 설정할 때 사용
- Ignoring 보다 permitAll 권장 : 정적 자원에 대한 요청일지라도 안전한 헤더를 작성할 수 있어 더 안전
@EnableWebSecurity @EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true) @Configuration public class SecurityConfig { ... @Bean public WebSecurityCustomizer webSecurityCustomizer() { return new WebSecurityCustomizer() { @Override public void customize(WebSecurity web) { web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations()); } }; } }계층적 권한 - RoleHirerachy
- 기본적으로 Spring Security에서 권한과 역할은 계층적이거나 상하 관계로 구분하지 않는다
- 인증 주체가 다양한 역할과 권한을 부여받아야 한다
- RoleHirerachy는 역할 간의 계층 구조를 정의하고 관리하는 데 사용되며 보다 간편하게 역할 간의 계층 구조를 설정하고 이를 기반으로 사용자에 대한 액세스 규칙을 정의할 수 있다
- setHirerachy
- 역할 계층을 설정하고 각 역할에 대해 해당 역할의 하위 계층에 속하는 모든 역할 집합을 미리 정해 놓는다
- getReachableGrantedAuthorities
- 모든 도달 가능한 권한의 배열을 반환한다
- 도달 가능한 권한은 직접 할당된 권한에 더해 역할 계층에서 이들로부터 도달 가능한 모든 권한을 의미
@RestController @RequiredArgsConstructor public class IndexController { @GetMapping("/user") public String user() { return "user"; } @GetMapping("/db") public String db() { return "db"; } @GetMapping("/admin") public String admin() { return "admin"; } }@EnableWebSecurity @EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true) @Configuration public class SecurityConfig { @Bean public RoleHierarchy roleHierarchy() { RoleHierarchyImpl hierarchy = new RoleHierarchyImpl(); hierarchy.setHierarchy("ROLE_ADMIN > ROLE_DB\n" + "ROLE_DB > ROLE_USER\n" + "ROLE_USER > ROLE_ANONYMOUS\n"); return hierarchy; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(authorize -> authorize .requestMatchers("user").hasRole("USER") .requestMatchers("db").hasRole("DB") .requestMatchers("admin").hasRole("ADMIN") .anyRequest().authenticated()) .formLogin(Customizer.withDefaults()) .csrf(AbstractHttpConfigurer::disable) ; return http.build(); } }728x90'Spring&SpringBoot > SpringSecurity' 카테고리의 다른 글
SpringSecurity 06 (0) 2025.09.25 SpringSecurity 04 (0) 2025.09.18 SpringSecurity 03 (0) 2025.09.17 SpringSecurity 02 (0) 2025.09.16 SpringSecurity 01 (0) 2025.09.15