본문 바로가기
Programming/Spring Security

[Spring Boot] Spring Security (5) - 역할(hasRole)과 권한(hasAuthority)의 차이는 무엇일까?

by 주리니e 2022. 8. 22.
728x90

[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>

A는 시스템관리자면서 데이터를 생성할 권한을 가지고 있다.
B는 시스템 관리자면서 데이터를 삭제할 권한을 가지고 있다.

위와 같이 기초데이터를 넣고 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) - 역할 별 페이지 접근제어

 

[Spring Boot] Spring Security (4) - 역할 별 페이지 접근제어

[Spring Boot] Spring Security (4) - 역할 별 페이지 접근제어 개발환경 Eclipse IDE 2022-06 Spring Boot 2.7.2 Gradle 7.0 Lombok PostgreSQL 지난 시간에 이어서 이번에는 사용자가 가지고 있는 역할을 확인..

jiurinie.tistory.com

 

728x90

댓글