๋ค์ด๊ฐ๋ฉด์
๊ธฐ์กด์ ๋์ค์ฝ๋์ ํ๋ก์ ํธ์์๋ ๊ด๋ฆฌ์, ์ ์ ๊ตฌ๋ถ ์์ด
๋ก๊ทธ์ธํ๋ฉด ๋๊ตฌ๋ ๋ชจ๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์์์ต๋๋ค.
์ด๋ฒ ๋ฏธ์ ์์ role์ ์ ์ํ๊ณ , role์ ๋ฐ๋ผ ํ๋์ ์ ํํ๊ธฐ๋ก ํ์ต๋๋ค.
์ด ํฌ์คํ ์์๋ ๊ถํ ๊ณ์ธต ๊ตฌ์กฐ์ ๋ํด ์์๋ณด๊ณ , ์ด๋ฅผ ์ฝ๋์ ์ ์ฉํด๋ณด๊ณ ์ ํฉ๋๋ค.
๊ฐ๋ฐ ํ๊ฒฝ
Spring Boot 3.4.0
Spring Security 6.4.1
๊ถํ ๊ณ์ธต ๊ตฌ์กฐ Role Hierarchy
Spring Security์ ๊ถํ ๊ณ์ธต ๊ตฌ์กฐ(Role Hierarchy) ๋ ์ญํ (Role) ๊ฐ์ ํฌํจ ๊ด๊ณ๋ฅผ ์ ์ํ๋ ๊ธฐ๋ฅ์ ๋๋ค.
ํ์ฌ์์ ์ฌ์ฅ์ด ๋ถ์ฅ์ ๋ชจ๋ ๊ถํ์ ๊ฐ์ง๊ณ , ๋ถ์ฅ์ด ์ฌ์์ ๊ถํ์ ๋ชจ๋ ๊ฐ์ง๋ ๊ฒ์ฒ๋ผ,
๋ ๋์ ์ญํ ์ด ๋ ๋ฎ์ ์ญํ ์ ๊ถํ์ ๋ชจ๋ ํฌํจํ๋ ๊ตฌ์กฐ๋ฅผ ์ ์ํ ์ ์์ต๋๋ค.
๊ทธ๋ฐ๋ฐ ์ Role Hierarchy์ธ๋ฐ "์ญํ ๊ณ์ธต ๊ตฌ์กฐ"๊ฐ ์๋๊ณ "๊ถํ ๊ณ์ธต ๊ตฌ์กฐ"๋ผ๊ณ ๋ถ๋ฅผ๊น์?
Spring Security์์ ์ฌ์ฉ๋๋ ๋ชจ๋ ๊ถํ์ GrantedAuthority ๋ผ๋ ๊ณตํต ๊ฐ๋ ์ผ๋ก ํํ๋ฉ๋๋ค.
์ญํ ๊ณผ ๊ถํ์ ์์ฃผ ๊ตฌ๋ถ๋๋ ๊ฒ์ด ์๋๋ผ, ์ญํ ์ ๊ถํ์ ๋ฌถ์์ด๊ธฐ ๋๋ฌธ์ ๊ฒฐ๊ณผ์ ์ผ๋ก๋ ๊ถํ ๊ฐ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ์ ์ํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ๋๋ค.
๊ถํ์ ๊ฐ๋ณ ๋จ์์ด๋ฉฐ, ์ญํ ์ ์ด๋ฐ ๊ถํ๋ค์ ๋ฌถ์์ด๊ณ , ์ญํ ๊ฐ ๊ณ์ธต ๊ตฌ์กฐ๋ ๋์ ์ญํ ์ด ๋ฎ์ ์ญํ ์ ์ํ ๊ถํ๋ค์ ํฌํจํ๋ ๊ตฌ์กฐ์ด์ฃ .
- ๊ถํ Permission
- ํน์ ์์ ์ด๋ ๊ธฐ๋ฅ์ ์ํํ ์ ์๋ ์ธ๋ถ์ ์ธ ๊ถํ
- ex) ๊ฒ์๊ธ ์์ฑ(POST), ๋๊ธ ์์ฑ(POST_COMMENT), ๋๊ธ ์ญ์ (DELETE_COMMENT) ๋ฑ
- ์ญํ Role
- ์ฌ๋ฌ ๊ถํ์ ๋ฌถ์ด๋์ ์งํฉ
- ex) ROLE_ADMIN, ROLE_USER
- ROLE_ADMIN์ ๊ณต์ง์ฌํญ ์์ฑ, ์ฌ์ฉ์ ์กฐํ, ์ฌ์ฉ์ ์ญ์ ๋ฑ์ ๊ถํ์ผ๋ก ๋ฌถ์ฌ์์
- ROLE_USER๋ ๊ฒ์๊ธ ์์ฑ, ๋๊ธ ์์ฑ, ๋๊ธ ์กฐํ ๋ฑ์ ๊ถํ์ผ๋ก ๋ฌถ์ฌ์์
์ด๋ฒ ํ๋ก์ ํธ์์๋
๊ด๋ฆฌ์/์ฑ๋ ๋งค๋์ /์ผ๋ฐ ์ฌ์ฉ์ ์ ๋๋ก ๊ถํ์ ๋ถ๋ฆฌํฉ๋๋ค.

์์ ๊ฐ์ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ์ค์ ํ๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ RoleHierarchy ๋น์ ๋ฑ๋กํ์์ต๋๋ค.
์ด RoleHierarchy ์ธํฐํ์ด์ค๊ฐ Spring Security์์ ์ง์ํ๋ ๊ถํ ๊ณ์ธต ๊ตฌ์กฐ ์ค์ ์ธํฐํ์ด์ค์ ๋๋ค.
@Bean
public RoleHierarchy roleHierarchy() {
return RoleHierarchyImpl.fromHierarchy("""
ROLE_ADMIN > ROLE_CHANNEL_MANAGER
ROLE_CHANNEL_MANAGER > ROLE_USER
""");
}
์ ์ค์ ๋ง ํ๋ฉด ๋๋ค๊ณ ํ๋๋ฐ ์ ๋ ๊ณ์ธต ๊ตฌ์กฐ๊ฐ ์ ์ ์ฉ๋์ง ์์ MethodSecurityExpressionHanlder ๋น๋ ๋ฑ๋กํ์์ต๋๋ค.
@Configuration
@EnableWebSecurity
// ๐ MethodSecurityExpressionHandler ์ฌ์ฉ ์ ์ ๋
ธํ
์ด์
์ถ๊ฐ
@EnableMethodSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain chain(HttpSecurity httpSecurity,
CustomAuthenticationFilter customAuthenticationFilter) throws Exception {
httpSecurity
.authorizeHttpRequests(auth -> auth
(์๋ต)
return httpSecurity.build();
}
(์๋ต)
// ๐ ๊ณ์ธต ๊ตฌ์กฐ ์ ์ฉ
@Bean
public RoleHierarchy roleHierarchy() {
return RoleHierarchyImpl.fromHierarchy(
"ROLE_ADMIN > ROLE_CHANNEL_MANAGER\n" +
"ROLE_CHANNEL_MANAGER > ROLE_USER"
);
}
@Bean
public MethodSecurityExpressionHandler methodSecurityExpressionHandler(
RoleHierarchy roleHierarchy) {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
handler.setRoleHierarchy(roleHierarchy);
return handler;
}
}
๊ธฐ์กด์ ์ฌ์ฉ์์๊ฒ ์ด๋ค Role๋ ๋๋์ง ์์๊ธฐ ๋๋ฌธ์
์ฌ์ฉ์๋ณ๋ก ๊ถํ์ ๊ฐ์ง๋ ์ฝ๋๋ ๊ตฌํํ๊ฒ ์ต๋๋ค.
public enum Role {
ROLE_ADMIN,
ROLE_CHANNEL_MANAGER,
ROLE_USER
}
์ดํ User ์ํฐํฐ์ Role ํ๋๋ฅผ ์ถ๊ฐํ์์ต๋๋ค.
์ด์ ๊ธฐ๋ฅ๋ณ๋ก ๊ถํ์ ์ค์ ํ ๊ฑด๋ฐ 2๊ฐ์ง ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
- URL ๋ณด์
- ๋ฉ์๋ ๋ณด์
URL ๋ณด์ ๋ฐฉ๋ฒ์ SecurityFilterChain์์ ์ค์ ํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
authorizeHttpRequest์ requestMatchers ์ค์ ์์ hasRole / hasAuthority๋ก ๊ถํ์ ๋ช ์ํฉ๋๋ค.
hasrRole("USER")๊ณผ hasAuthority("ROLE_USER")๋ ๊ฐ์ ์๋ฏธ์ธ๋ฐ,
hasRole๋ก ํ๋ฉด ์๋์ผ๋ก ์ ๋์ฌ ROLE_์ ๋ถ์ฌ ROLE_USER ๊ถํ์ ํ์ธํฉ๋๋ค.
hasAuthority๋ ROLE_ ์ ๋์ฌ๋ฅผ ๋ถ์ด์ง ์๊ณ ๋ฌธ์์ด ๊ทธ๋๋ก ์ฐพ๊ธฐ ๋๋ฌธ์ ROLE_USER๋ผ๋ ๊ถํ์ด ์์ด์ผ ํฉ๋๋ค.
@Bean
SecurityFilterChain chain(HttpSecurity httpSecurity,
CustomAuthenticationFilter customAuthenticationFilter) throws Exception {
httpSecurity
.authorizeHttpRequests(auth -> auth
// ๋ชจ๋์๊ฒ ํ์ฉ
.requestMatchers("/api/auth/csrf-token").permitAll()
.requestMatchers(HttpMethod.POST, "/api/users").permitAll()
.requestMatchers(HttpMethod.POST, "/api/auth/login").permitAll()
// ๊ทธ ์ธ API๋ ์ต์ ROLE_USER ๊ถํ ํ์
.requestMatchers("/api/**").hasRole("USER")
// admin API๋ ROLE_ADMIN ๊ถํ ํ์
.requestMatchers("/admin/**").hasAuthority("ROLE_ADMIN")
// ๊ทธ ์ธ ์ธ์ฆ ํ์
.anyRequest().authenticated())
(์๋ต)
return httpSecurity.build();
}
๋ฉ์๋ ๋ณด์์ @PreAuthorize ์ ๋ ธํ ์ด์ ์ ํ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
API ์ ๊ทผ ์ ํ ์์ฒด๋ฅผ ์ ์ดํ๋ ค๋ ๊ฒฝ์ฐ ์ปจํธ๋กค๋ฌ ๋ฉ์๋ ์์ ๋ถ์ด๋ฉด ํธ๋ฆฌํฉ๋๋ค.
// ์ฌ๋ฌ ๊ถํ ์ค ํ๋ ์ด์ ๊ฐ์ง ์ฌ์ฉ์๋ง ์ ๊ทผ ๊ฐ๋ฅ
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
public List<Employee> findAllEmployees() {
return employeeRepository.findAll();
}
