| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- IntelliJ
- JavqScript
- SpringBoot
- API
- 백틱
- findBy
- 시
- MockAPI
- ES6+
- 웹
- login
- js
- 낙서
- Security 설정
- 분리 개발
- HttpOnly
- VS Code
- 자바 스크립트
- 보안
- AJAX
- 공부
- 유효성 검사
- 로그인
- Method 방식
- 웹개발
- 그림
- @AuthenticationPrincipal
- 카카오
- 취미
- security
- Today
- Total
개발가능구역
[ IntelliJ ] Spring Security로 역할 기반 접근 제어(RBAC) 구현하기: 관리자, 일반 회원, 비회원 권한 설정 가이드 본문
[ IntelliJ ] Spring Security로 역할 기반 접근 제어(RBAC) 구현하기: 관리자, 일반 회원, 비회원 권한 설정 가이드
oosomall 2025. 10. 8. 15:42
웹 애플리케이션 개발에서 보안은 아무리 강조해도 지나치지 않다. 특히 사용자 역할에 따라 접근 가능한 기능을 제한하는 역할 기반 접근 제어(Role-Based Access Control, RBAC)는 필수적이다.
이 글에서는 Spring Security를 이용하여 관리자(ADMIN), 일반 회원(MEMBER), 비회원(ANONYMOUS) 세 가지 주요 역할에 대한 접근 권한을 IntelliJ 기반 Spring Boot 프로젝트에서 설정하는 방법을,
기존 코드에서 바뀐 코드를 중심으로 자세히 설명하고, 앞전에 다뤘던 Security 설정도 다시 복습해 보았다.
❌ 전: 기존 SecurityConfig 클래스
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final CustomOAuth2UserService customOAuth2UserService;
public SecurityConfig(CustomOAuth2UserService customOAuth2UserService) {
this.customOAuth2UserService = customOAuth2UserService;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/sign_up", "/signup_save",
"/check/**", "/image/**", "/css/**", "/js/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.loginProcessingUrl("/go_login")
.usernameParameter("id")
.passwordParameter("pw")
.defaultSuccessUrl("/")
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll()
)
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.successHandler(customAuthenticationSuccessHandler())
//.defaultSuccessUrl("/", true)
.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService)
)
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler() {
return new CustomAuthenticationSuccessHandler();
}
}
⭕ 후: SecurityConfig 클래스
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final CustomOAuth2UserService customOAuth2UserService;
public SecurityConfig(CustomOAuth2UserService customOAuth2UserService) {
this.customOAuth2UserService = customOAuth2UserService;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
// 1. 비회원 (모두 접근 가능)
.requestMatchers("/", "/sign_up", "/signup_save",
"/check/**", "/image/**", "/css/**", "/js/**").permitAll()
// 2. 관리자 (ADMIN 역할만 접근 가능
.requestMatchers("/admin/**").hasRole("ADMIN")
// 3. 일반 회원(MEMBER 역할만 접근 가능)
.requestMatchers("/member/**").hasRole("MEMBER")
// 4. 나머지 모든 요청은 로그인만 하면 접근 가능(인증)
// ( 로그인 = ADMIN, MEMBER 모두 해당된다 )
.anyRequest().authenticated()
)
/* 관리자 외 접근권한 닫기
1. exceptionHandling : Security가 인증/인가 과정에서 발생하는 예외를 처리하는 방법 설정
2. accessDeniedPage : 로그인 상태이지만 요청된 리소스에 접근할 권한이 없을 때(403) /login페이지로 이동
*/
.exceptionHandling(exception -> exception
.accessDeniedPage("/login")
)
.formLogin(form -> form
.loginPage("/login")
.loginProcessingUrl("/go_login")
.usernameParameter("id")
.passwordParameter("pw")
.defaultSuccessUrl("/")
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll()
)
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.successHandler(customAuthenticationSuccessHandler())
//.defaultSuccessUrl("/", true)
.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService)
)
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler() {
return new CustomAuthenticationSuccessHandler();
}
}
✅1. Spring Security 설정 파일 개요
우리가 권한 설정을 할 핵심 파일은 @Configuration과 @EnableWebSecurity 어노테이션이 붙은 SecurityConfig 클래스다. 이 클래스 내의 filterChain(HttpSecurity http) 메서드가 모든 보안 규칙을 정의하는 역할을 한다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// ... (생성자 및 기타 빈 설정 생략)
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
// 1. 비회원 (모두 접근 가능)
.requestMatchers("/", "/sign_up", "/signup_save",
"/check/**", "/image/**", "/css/**", "/js/**").permitAll()
// 2. 관리자 (ADMIN 역할만 접근 가능
.requestMatchers("/admin/**").hasRole("ADMIN")
// 3. 일반 회원(MEMBER 역할만 접근 가능)
.requestMatchers("/member/**").hasRole("MEMBER")
// 4. 나머지 모든 요청은 로그인만 하면 접근 가능(인증)
.anyRequest().authenticated()
)
// ... (예외 처리, 폼 로그인, 로그아웃, OAuth2 설정 생략)
return http.build();
}
// ...
}
✅2. 역할 기반 접근 제어(RBAC) 핵심 설정 분석
filterChain 메서드 내에서 < authorizeHttpRequests >를 통해 URL 패턴별 접근 권한을 설정한다. Spring Security는 설정된 순서대로 규칙을 적용하므로, 가장 구체적인 규칙을 위에, 가장 일반적인 규칙을 아래에 배치하는 것이 중요하다.
2.1. 비회원 접근 허용 (permitAll())
// 1. 비회원 (모두 접근 가능)
.requestMatchers("/", "/sign_up", "/signup_save",
"/check/**", "/image/**", "/css/**", "/js/**").permitAll()
- requestMatchers(...): 특정 URL 패턴을 지정한다. 여기서는 메인 페이지(/), 회원가입 관련 페이지(/sign_up, /signup_save), 리소스 파일(이미지, CSS, JS) 경로 등을 포함한다.
- .permitAll(): 이 패턴에 해당하는 요청은 인증 여부(로그인 여부)와 관계없이 모든 사용자에게 접근을 허용한다. 즉, 비회원도 접근할 수 있게 된다.
2.2. 관리자 전용 접근 (hasRole("ADMIN"))
// 2. 관리자 (ADMIN 역할만 접근 가능
.requestMatchers("/admin/**").hasRole("ADMIN")
- requestMatchers("/admin/**"): /admin으로 시작하는 모든 경로를 지정한다. (예: /admin/users, /admin/dashboard).
- .hasRole("ADMIN"): 해당 요청은 ADMIN 역할을 가진 인증된 사용자만 접근할 수 있다. Spring Security는 내부적으로 ROLE_ 접두사를 자동으로 붙여 ROLE_ADMIN 권한을 확인한다.
2.3. 일반 회원 전용 접근 (hasRole("MEMBER"))
// 3. 일반 회원(MEMBER 역할만 접근 가능)
.requestMatchers("/member/**").hasRole("MEMBER")
- requestMatchers("/member/**"): /member로 시작하는 모든 경로를 지정한다.
- .hasRole("MEMBER"): 해당 요청은 MEMBER 역할을 가진 인증된 사용자만 접근할 수 있게 된다. (내부적으로 ROLE_MEMBER 권한 확인).
2.4. 로그인 필수 (인증된 사용자) (authenticated())
// 4. 나머지 모든 요청은 로그인만 하면 접근 가능(인증)
// ( 로그인 = ADMIN, MEMBER 모두 해당된다 )
.anyRequest().authenticated()
- .anyRequest(): 위에 명시된 규칙을 제외한 나머지 모든 요청을 지정한다.
- .authenticated(): 해당 요청은 인증된 사용자, 즉 로그인한 사용자만 접근할 수 있게 되는 것이다. 관리자(ADMIN)든 일반 회원(MEMBER)이든 로그인만 했다면 접근 가능
✅3. 예외 처리: 접근 거부 시 동작 설정
사용자가 로그인했으나 접근 권한이 없는 페이지를 요청했을 때(예: 일반 회원이 /admin 페이지에 접근 시도)의 동작을 설정한다.
.exceptionHandling(exception -> exception
.accessDeniedPage("/login")
)
- exceptionHandling: Spring Security의 예외 처리 방법을 설정한다.
- accessDeniedPage("/login"): 권한이 없어 접근이 거부될 경우(HTTP Status 403 Forbidden) 사용자를 /login 페이지로 리디렉션한다.
✅4. 인증 설정: 폼 로그인, 로그아웃, 비밀번호 암호화
4.1. 폼 기반 로그인 설정
.formLogin(form -> form
.loginPage("/login")
.loginProcessingUrl("/go_login")
.usernameParameter("id")
.passwordParameter("pw")
.defaultSuccessUrl("/")
.permitAll()
)
- .loginPage("/login"): 커스텀 로그인 페이지 URL을 지정합니다.
- .loginProcessingUrl("/go_login"): 로그인 처리를 담당할 URL을 지정합니다. (실제 컨트롤러 구현은 필요 없음, Spring Security가 처리).
- .usernameParameter("id"), .passwordParameter("pw"): 로그인 폼에서 사용할 사용자 ID와 비밀번호 파라미터 이름을 지정합니다.
- .defaultSuccessUrl("/"): 로그인 성공 시 기본으로 이동할 페이지입니다.
4.2. 로그아웃 설정
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll()
)
- .logoutUrl("/logout"): 로그아웃을 처리할 URL을 지정합니다.
- .logoutSuccessUrl("/login"): 로그아웃 성공 시 이동할 페이지입니다.
4.3. 비밀번호 암호화 (PasswordEncoder)
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
- BCryptPasswordEncoder: Spring Security에서 권장하는 비밀번호 암호화 방식 중 하나입니다. 실제 DB에 저장된 사용자의 비밀번호는 이 인코더를 통해 암호화되어 있어야 합니다.
🚫 문제 발견
3. 예외처리: 접근 거부 시 동작 설정에서 관리자 외에는 로그인 페이지로 이동하라고 설정해뒀다. 이는 페이지 이동이 매우 불편해지니 관리자만 사용할 수 있는 버튼이 있게 top.html(네비게이션 바)를 간단하게 설정해보자.
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<ul class="nav navbar-nav">
<li class="dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="#">TEST02 <span
class="caret"></span></a>
<ul class="dropdown-menu">
// 해당 부분 추가
<li sec:authorize="hasRole('ADMIN')">
<a href="/admin/input02">test02 입력</a>
</li>
<li><a href="output02">test02 출력</a></li>
</ul>
</li>
</ul>
</div>
</nav>
✅ 작동 원리
- sec:authorize="hasRole('ADMIN')": Thymeleaf가 HTML을 렌더링할 때, 현재 로그인된 사용자에게 ROLE_ADMIN 권한이 있는지 확인한다.
- 관리자인 경우: 조건이 참(true)이므로, 해당 <li> 태그와 그 안에 있는 <a href="/admin/input02">test02 입력</a>가 HTML 페이지에 정상적으로 포함되어 보이게 된다.
- 관리자가 아닌 경우 (일반 회원 또는 비회원): 조건이 거짓(false)이므로, 해당 <li> 태그 전체가 HTML 소스코드에서 완전히 제거되어 사용자에게는 보이지 않게 된다.
✅ 참고
일반 회원이 주소창에 /admin/input02를 직접 입력하고 접근을 시도하면 여전히 Spring Security의 인가(Authorization) 처리가 필요하다.
따라서, 버튼 숨기기와 이전에 설정하신 < SecurityConfig의 hasRole("ADMIN") >을 함께 사용하는 것이 가장 안전한 방법이다.
// SecurityConfig.java
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN") // ◀ 서버에서 ADMIN 권한만 허용
// ...
)
.exceptionHandling(exception -> exception
.accessDeniedPage("/admin_page") // ◀ 권한 부족 시 전용 페이지로 리다이렉트
)
/login → /admin_page로 변경 후에 admin_page.html을 새로 만들었다.
해당 페이지는 관리자만 이용가능하다라는 문자열을 <p>로만 넣어뒀기에 일반 회원이 /admin/input02를 직접 입력하고 접속 시도하면 [ 관리자만 이용가능한 페이지입니다. ] 라는 메시지가 떠서 보안할 수 있게 된다.
이러한 설정을 통해, 여러분의 Spring Boot 애플리케이션은 사용자 역할에 따라 안전하게 리소스를 보호하는 강력한 역할 기반 접근 제어(RBAC) 시스템을 갖추게 됩니다. 이제 지정된 역할(ADMIN, MEMBER)을 사용자에게 부여하는 로직(예: UserDetailsService 구현체)만 잘 구현하면 완벽한 보안 시스템이 구축된다고 한다!
'SpringBoot > IntelliJ' 카테고리의 다른 글
| [ IntelliJ ] Spring Security 로그인 02 (0) | 2025.10.01 |
|---|---|
| [ IntelliJ ] Spring Security 로그인 01 (0) | 2025.09.30 |
| [ IntelliJ ] JavaScript 활용 유효성 검사 ( 전화번호 ) (0) | 2025.09.29 |
| [ IntelliJ ] SpringBoot JavaScript 활용 유효성 검사 ( 비밀번호 확인 ) (2) | 2025.09.27 |
| [ IntelliJ ] Validation 유효성 검사 (4) | 2025.09.26 |