From 9ddfc24d2f5f455c703ef791d1539e13b3e489f5 Mon Sep 17 00:00:00 2001 From: Kim DongHyo <60608509+kdhyo@users.noreply.github.com> Date: Tue, 21 Jun 2022 11:57:10 +0900 Subject: [PATCH] [#30] Feature/role hierarchy (#34) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 계층권한 구현 * refactor: 생성자 메서드 위치 변경 --- .../server/global/security/RoleConfig.java | 56 +++++++++++++++++++ .../global/security/WebSecurityConfig.java | 2 - .../user/application/UserController.java | 5 +- .../ticketing/server/user/domain/User.java | 16 +++--- .../server/user/domain/UserGrade.java | 35 +++++++++++- 5 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 server/src/main/java/com/ticketing/server/global/security/RoleConfig.java diff --git a/server/src/main/java/com/ticketing/server/global/security/RoleConfig.java b/server/src/main/java/com/ticketing/server/global/security/RoleConfig.java new file mode 100644 index 0000000..e035b9d --- /dev/null +++ b/server/src/main/java/com/ticketing/server/global/security/RoleConfig.java @@ -0,0 +1,56 @@ +package com.ticketing.server.global.security; + +import com.ticketing.server.user.domain.UserGrade; +import java.util.ArrayList; +import java.util.List; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.AccessDecisionManager; +import org.springframework.security.access.AccessDecisionVoter; +import org.springframework.security.access.annotation.Jsr250Voter; +import org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice; +import org.springframework.security.access.hierarchicalroles.RoleHierarchy; +import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; +import org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter; +import org.springframework.security.access.vote.AffirmativeBased; +import org.springframework.security.access.vote.AuthenticatedVoter; +import org.springframework.security.access.vote.RoleHierarchyVoter; +import org.springframework.security.access.vote.RoleVoter; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; + +@EnableGlobalMethodSecurity( + securedEnabled = true, + jsr250Enabled = true, + prePostEnabled = true +) +@Configuration +public class RoleConfig extends GlobalMethodSecurityConfiguration { + + @Override + protected AccessDecisionManager accessDecisionManager() { + List> decisionVoters = new ArrayList<>(); + ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice(); + expressionAdvice.setExpressionHandler(getExpressionHandler()); + decisionVoters.add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice)); + decisionVoters.add(new Jsr250Voter()); + + decisionVoters.add(new RoleVoter()); + decisionVoters.add(roleHierarchyVoter()); + decisionVoters.add(new AuthenticatedVoter()); + return new AffirmativeBased(decisionVoters); + } + + @Bean + public RoleHierarchyVoter roleHierarchyVoter() { + return new RoleHierarchyVoter(roleHierarchy()); + } + + @Bean + public RoleHierarchy roleHierarchy() { + RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); + roleHierarchy.setHierarchy(UserGrade.getRoleHierarchy()); + return roleHierarchy; + } + +} diff --git a/server/src/main/java/com/ticketing/server/global/security/WebSecurityConfig.java b/server/src/main/java/com/ticketing/server/global/security/WebSecurityConfig.java index 2778e60..67ae421 100644 --- a/server/src/main/java/com/ticketing/server/global/security/WebSecurityConfig.java +++ b/server/src/main/java/com/ticketing/server/global/security/WebSecurityConfig.java @@ -8,7 +8,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -19,7 +18,6 @@ import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity -@EnableGlobalMethodSecurity(securedEnabled = true) @RequiredArgsConstructor public class WebSecurityConfig extends WebSecurityConfigurerAdapter { diff --git a/server/src/main/java/com/ticketing/server/user/application/UserController.java b/server/src/main/java/com/ticketing/server/user/application/UserController.java index 253e1f3..1bdd5ca 100644 --- a/server/src/main/java/com/ticketing/server/user/application/UserController.java +++ b/server/src/main/java/com/ticketing/server/user/application/UserController.java @@ -11,6 +11,7 @@ import com.ticketing.server.user.application.response.UserChangePasswordResponse import com.ticketing.server.user.application.response.UserDeleteResponse; import com.ticketing.server.user.application.response.UserDetailResponse; import com.ticketing.server.user.domain.User; +import com.ticketing.server.user.domain.UserGrade; import com.ticketing.server.user.service.UserServiceImpl; import com.ticketing.server.user.service.interfaces.AuthenticationService; import javax.servlet.http.HttpServletResponse; @@ -57,14 +58,14 @@ public class UserController { } @DeleteMapping - @Secured("ROLE_GUEST") + @Secured(UserGrade.ROLES.GUEST) public ResponseEntity deleteUser(@RequestBody @Valid UserDeleteRequest request) { User user = userService.delete(request.toDeleteUserDto(passwordEncoder)); return ResponseEntity.status(HttpStatus.OK).body(UserDeleteResponse.from(user)); } @PutMapping("/password") - @Secured("ROLE_GUEST") + @Secured(UserGrade.ROLES.GUEST) public ResponseEntity changePassword(@RequestBody @Valid UserModifyPasswordRequest request) { if (request.oldEqualNew()) { log.error("기존 패스워드와 동일한 패스워드로 변경할 수 없습니다."); diff --git a/server/src/main/java/com/ticketing/server/user/domain/User.java b/server/src/main/java/com/ticketing/server/user/domain/User.java index f990643..df74ef4 100644 --- a/server/src/main/java/com/ticketing/server/user/domain/User.java +++ b/server/src/main/java/com/ticketing/server/user/domain/User.java @@ -23,14 +23,6 @@ import lombok.NoArgsConstructor; @NoArgsConstructor public class User extends AbstractEntity { - public User(String name, String email, String password, UserGrade grade, String phone) { - this.name = name; - this.email = email; - this.password = password; - this.grade = grade; - this.phone = phone; - } - @Column(name = "name") @NotEmpty(message = "{validation.not.empty.name}") private String name; @@ -58,6 +50,14 @@ public class User extends AbstractEntity { private LocalDateTime deletedAt; + public User(String name, String email, String password, UserGrade grade, String phone) { + this.name = name; + this.email = email; + this.password = password; + this.grade = grade; + this.phone = phone; + } + public User delete(DeleteUserDTO deleteUser) { if (isDeleted) { throw new AlreadyDeletedException("이미 탈퇴된 회원 입니다."); diff --git a/server/src/main/java/com/ticketing/server/user/domain/UserGrade.java b/server/src/main/java/com/ticketing/server/user/domain/UserGrade.java index d6ba4e6..cc19ab5 100644 --- a/server/src/main/java/com/ticketing/server/user/domain/UserGrade.java +++ b/server/src/main/java/com/ticketing/server/user/domain/UserGrade.java @@ -1,5 +1,38 @@ package com.ticketing.server.user.domain; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor public enum UserGrade { - GUEST, STAFF + ADMIN(ROLES.ADMIN, null), + STAFF(ROLES.STAFF, ROLES.ADMIN), + GUEST(ROLES.GUEST, ROLES.STAFF); + + private final String roleName; + private final String parentName; + + public static class ROLES { + + public static final String ADMIN = "ROLE_ADMIN"; + public static final String STAFF = "ROLE_STAFF"; + public static final String GUEST = "ROLE_GUEST"; + + private ROLES() { + } + } + + public static String getRoleHierarchy() { + StringBuilder sb = new StringBuilder(); + + for (UserGrade grade : UserGrade.values()) { + if (grade.parentName != null) { + sb.append(grade.parentName); + sb.append(" > "); + sb.append(grade.roleName); + sb.append("\n"); + } + } + + return sb.toString(); + } }