[Spring Boot] Spring Security (5) - 역할(Role)과 권한(Authority)의 차이는 무엇일까?
흔히 Spring Security에서 제공하는 역할(Role)과 권한(Authority)을 혼동하는 사용하는 경우가 많은데 비슷하면서도 다르다. 예를 들어 A계정과 B계정은 둘다 시스템관리자로서 SYSTEM 역할을 가지고 있지만 원칙상 A 계정은 게시판의 글을 등록할 수만 있으며, B 계정은 등록된 글을 삭제만 할 수 있다고 했을 때 역할은 같지만 권한은 다르게 설정해야 할 것이다.
소규모 프로젝트에서는 둘 중 한가지로 사용해서도 충분히 접근제어를 할 수 있지만 역할과 권한을 분리하여 명시함으로써 유지보수성도 높아질 것이다. 자유롭게 혼용하여 사용하는 것이 Spirng Security를 적절하게 사용하지 않는 것이 아닐까 싶다. 그러니 hasRole 메소드에는 'ROLE_' 접두어를 내부적으로 붙이지 않았을까..? 우선 기초데이터부터 넣어보자.
-- A 사용자 정보 등록 (비밀번호는 BCryptPasswordEncoder로 인코딩 된 값을 넣었다. 로그인 비밀번호 : 123)
INSERT INTO public.tb_user
(id, "name", "password")
VALUES('A', 'A', '$2a$10$6iJv7gmGXdP2Sf0ojKBfwuUgxaHqv0c3f50SfDNdxNxq.dSymAAlW');
-- B 사용자 정보 등록 (비밀번호는 BCryptPasswordEncoder로 인코딩 된 값을 넣었다. 로그인 비밀번호 : 123)
INSERT INTO public.tb_user
(id, "name", "password")
VALUES('B', 'B', '$2a$10$6iJv7gmGXdP2Sf0ojKBfwuUgxaHqv0c3f50SfDNdxNxq.dSymAAlW');
-- 데이터를 생성할 수 있는 권한 등록
INSERT INTO public.tb_role
(id, "name")
VALUES(4, 'OP_CREATE_DATA');
-- 데이터를 삭제할 수 있는 권한 등록
INSERT INTO public.tb_role
(id, "name")
VALUES(5, 'OP_DELETE_DATA');
-- A 관리자에게 SYSTEM 역할과 OP_CREATE_DATA 권한 등록
INSERT INTO public.tb_user_roles
(user_id, role_id)
VALUES('A', 1);
INSERT INTO public.tb_user_roles
(user_id, role_id)
VALUES('A', 4);
-- B 관리자에게 SYSTEM 역할과 OP_DELETE_DATA 권한 등록
INSERT INTO public.tb_user_roles
(user_id, role_id)
VALUES('B', 1);
INSERT INTO public.tb_user_roles
(user_id, role_id)
VALUES('B', 5);
- UserAuthority.java
package com.example.security.enums;
public enum UserAuthority {
OP_CREATE_DATA, OP_DELETE_DATA
}
- hello.html
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<br>
<!--SYSTEM 역할을 갖는다면 이 글이 보임-->
<h3 sec:authorize="hasRole(T(com.example.security.enums.UserRole).SYSTEM)">Has SYSTEM Role</h3>
<!--SYSTEM 역할과 OP_CREATE_DATA 권한을 갖는다면 이 글이 보임-->
<h3 sec:authorize="hasRole(T(com.example.security.enums.UserRole).SYSTEM) and hasAuthority(T(com.example.security.enums.UserAuthority).OP_CREATE_DATA)">Has OP_CREATE_DATA Authority</h3>
<!--SYSTEM 역할과 OP_DELETE_DATA 권한을 갖는다면 이 글이 보임-->
<h3 sec:authorize="hasRole(T(com.example.security.enums.UserRole).SYSTEM) and hasAuthority(T(com.example.security.enums.UserAuthority).OP_DELETE_DATA)">Has OP_DELETE_DATA Authority</h3>
<div>
Authorities : <span sec:authentication="principal.authorities"></span>
</div>
<br>
<p>
시스템 관리자 페이지로 이동 <a th:href="@{/system}">SYSTEM</a>
</p>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out" />
</form>
</body>
</html>
위와 같이 기초데이터를 넣고 hello 페이지를 통하여 역할과 권한을 확인하였다. 이제 역할과 권한을 구분하여 목적에 맞게 설정을 하여 접근제어를 해보자.
- SecurityConfiguration.java
package com.example.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.example.security.enums.UserAuthority;
import com.example.security.enums.UserRole;
import lombok.AllArgsConstructor;
@EnableWebSecurity
@AllArgsConstructor
public class SecurityConfiguration {
private final UserDetailsService userDetailsService;
@Bean
public static BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
/* @formatter:off */
http
.authorizeRequests()
.antMatchers("/", "/home", "/signUp").permitAll() // 설정한 리소스의 접근을 인증절차 없이 허용
.antMatchers("/system").hasRole(UserRole.SYSTEM.toString()) // SYSTEM 역할을 가지고 있어야 접근 허용
.antMatchers("/system/create").access("hasRole('" + UserRole.SYSTEM.toString() + "') and hasAuthority('" + UserAuthority.OP_CREATE_DATA.toString() + "')") // SYSTEM 역할과 OP_CREATE_DATA 권한을 가지고 있어야 접근 허용
.antMatchers("/system/delete").access("hasRole('" + UserRole.SYSTEM.toString() + "') and hasAuthority('" + UserAuthority.OP_DELETE_DATA.toString() + "')") // SYSTEM 역할과 OP_DELETE_DATA 권한을 가지고 있어야 접근 허용
.anyRequest().authenticated() // 그 외 모든 리소스를 의미하며 인증 필요
.and()
.formLogin()
.permitAll()
.loginPage("/login") // 기본 로그인 페이지
.and()
.logout()
.permitAll()
// .logoutUrl("/logout") // 로그아웃 URL (기본 값 : /logout)
// .logoutSuccessUrl("/login?logout") // 로그아웃 성공 URL (기본 값 : "/login?logout")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")) // 주소창에 요청해도 포스트로 인식하여 로그아웃
.deleteCookies("JSESSIONID") // 로그아웃 시 JSESSIONID 제거
.invalidateHttpSession(true) // 로그아웃 시 세션 종료
.clearAuthentication(true) // 로그아웃 시 권한 제거
.and()
.exceptionHandling()
.accessDeniedPage("/accessDenied");
return http.build();
/* @formatter:on */
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
}
antMatchers로 하는 URI 검증은 위에서부터 아래로 역할 및 권한을 검증하게 된다. '/system' 경로는 SYSTEM 역할만 가지고 있으면 접근이 가능하지만 '/system/create' 와 '/system/delete'는 SYSTEM 역할에 추가로 각각의 기능을 수행할 수 있는 권한 또한 가지고 있어야 한다.
- SystemController.java
package com.example.security.web.controller;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class SystemController {
@GetMapping("/system")
public String system(HttpServletRequest request) {
return "system";
}
@GetMapping("/system/create")
public String create(HttpServletRequest request) {
return "systemCreate";
}
@GetMapping("/system/delete")
public String delete(HttpServletRequest request) {
return "systemDelete";
}
@GetMapping("/accessDenied")
public String accessDenied() {
return "accessDenied";
}
}
- hello.html
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<br>
<!--SYSTEM 역할을 갖는다면 이 글이 보임-->
<h3 sec:authorize="hasRole(T(com.example.security.enums.UserRole).SYSTEM)">Has SYSTEM Role</h3>
<!--SYSTEM 역할과 OP_CREATE_DATA 권한을 갖는다면 이 글이 보임-->
<h3 sec:authorize="hasRole(T(com.example.security.enums.UserRole).SYSTEM) and hasAuthority(T(com.example.security.enums.UserAuthority).OP_CREATE_DATA)">Has OP_CREATE_DATA Authority</h3>
<!--SYSTEM 역할과 OP_DELETE_DATA 권한을 갖는다면 이 글이 보임-->
<h3 sec:authorize="hasRole(T(com.example.security.enums.UserRole).SYSTEM) and hasAuthority(T(com.example.security.enums.UserAuthority).OP_DELETE_DATA)">Has OP_DELETE_DATA Authority</h3>
<div>
Authorities : <span sec:authentication="principal.authorities"></span>
</div>
<br>
<p>
시스템 관리자 페이지로 이동 <a th:href="@{/system}">SYSTEM</a>
</p>
<p>
시스템 관리자 등록 페이지로 이동 <a th:href="@{/system/create}">SYSTEM CREATE</a>
</p>
<p>
시스템 관리자 삭제 페이지로 이동 <a th:href="@{/system/delete}">SYSTEM DELETE</a>
</p>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out" />
</form>
</body>
</html>
- systemCreate.html
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head>
<title>Spring Security Example</title>
</head>
<body>
<h1>SYSTEM CREATE</h1>
<p>안녕하세요. 이 페이지는 시스템 역할을 가지고 있고 OP_CREATE_DATA 권한을 가지고 있어야 접근이 가능합니다.</p>
</body>
</html>
- systemDelete.html
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head>
<title>Spring Security Example</title>
</head>
<body>
<h1>SYSTEM DELETE</h1>
<p>안녕하세요. 이 페이지는 시스템 역할을 가지고 있고 OP_DELETE_DATA 권한을 가지고 있어야 접근이 가능합니다.</p>
</body>
</html>
이 포스트를 마지막으로 지금까지 Spring Security의 기본 설정들에 대해서 알아보았다. 앞으로는 지금까지와의 시리즈와는 별개로 세부적인 기능들에 대해서 포스팅을 지속적으로 해보려고 한다.
이전글 : [Spring Boot] Spring Security (4) - 역할 별 페이지 접근제어
'Programming > Spring Security' 카테고리의 다른 글
[Spring Boot] Spring Security (4) - 역할 별 페이지 접근제어 (0) | 2022.08.19 |
---|---|
[Spring Boot] Spring Security (3) - 로그인 시 사용자 역할 조회 및 부여 (0) | 2022.08.17 |
[Spring Boot] Spring Security (2) - 회원가입 및 로그인을 위한 데이터베이스 연동 (0) | 2022.08.16 |
[Spring Boot] Spring Security (1) - 기본 프로젝트 생성 (3) | 2022.07.21 |
댓글