목차
@RoleHierarcy 우리는 일반적으로 ADMIN , MANAGER , USER 권한이 있다고 하면 Admin 권한으로 모든 자원에 접근이 가능하다고 생각하지만 Spring Security는 상하위 개념으로 인식하지 못하고 권한들을 독립적 으로 인식해 MANAGER 권한, USER 권한 리소스에 접근할 수 없다. 만약 접근하려고 하면 접근 권한과 관련된 403(Forbidden) Error
가 발생한다.
권한간 상하 관계를 적용하기 위해 Spring Security에서는 RoleHierarchy 인터페이스를 제공하고 구현체로는 RoleHierarchyImpl 를 제공한다. 권한을 적용하기 위해 RoleHierarchyVoter 객체에 RoleHierarchy 객체를 넣어줌으로써 권한 계층 구조를 사용할 수 있다.
RoleHierarchy : 권한간 계층 관계 정보를 저장
RoleHierarchyVoter 인가 처리시 RoleHierarchy 내 저장된 권한간 계층 정보를 사용할 수 있도록 한다.
권한을 저장하기 위한 Entity @Entity @Table(name = "ROLE_HIERARCHY") @AllArgsConstructor @NoArgsConstructor @Data @Builder @ToString(exclude = {"parentName", "roleHierarchy"}) public class RoleHierarchy implements Serializable { @Id @GeneratedValue private Long id; @Column(name = "child_name") private String childName; @ManyToOne(cascade = {CascadeType.ALL}, fetch = FetchType.LAZY) @JoinColumn(name = "parent_name", referencedColumnName = "child_name") private RoleHierarchy parentName; @OneToMany(mappedBy = "parentName", cascade = {CascadeType.ALL }) private Set<RoleHierarchy> roleHierarchy = new HashSet <>(); }
권한 정보를 가져오기 위한 Repository @Repository public interface RoleHierarchyRepository extends JpaRepository <RoleHierarchy, Long> { RoleHierarchy findByChildName (String roleName) ; }
권한을 가져와 권한간 계층을 적용 findAllHierarchy 메소드를 이용해 저장된 권한을 가져와 상하 관계를 만들어 준다.상위 권한 > 하위 권한
형태로 만들어 반환해준다.
@Service public class RoleHierarchyService { @Autowired private RoleHierarchyRepository roleHierarchyRepository; @Transactional public String findAllHierarchy () { List<RoleHierarchy> roleHierarchies = roleHierarchyRepository.findAll(); StringBuilder concatedRoles = new StringBuilder (); roleHierarchies.stream().forEach( roleHierarchy -> { if (roleHierarchy.getParentName() != null ){ concatedRoles.append(roleHierarchy.getParentName().getChildName()); concatedRoles.append(" > " ); concatedRoles.append(roleHierarchy.getChildName()); concatedRoles.append("\n" ); } }); return concatedRoles.toString(); } }
설정을 확인하기 위한 Controller @RestController public class SampleController { @GetMapping("/") public String home () { return "Home" ; } @GetMapping("/user") public String user () { return "user" ; } @GetMapping("/manager") public String manager (Principal principal) { return "manager" ; } @GetMapping("/admin") public String admin (Principal principal) { return "admin" ; } }
Security 설정
/user
: USER 권한이 있는 유저가 접근할 수 있다.
/manager
: MANAGER 권한이 있는 유저가 접근할 수 있다.
/admin
: ADMIN 권한이 있는 유저가 접근할 수 있다.
간단한 User 등록을 위해 In-Memory User를 사용할 것이다. User는 총 3명으로 각각 ADMIN , MANAGER , USER 권한을 가지고 있다.
@EnableWebSecurity @Slf4j public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private RoleHierarchyService roleHierarchyService; @Override protected void configure (AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("admin" ).password("{noop}1234" ).roles("ADMIN" ).and() .withUser("manager" ).password("{noop}1234" ).roles("MANAGER" ).and() .withUser("user" ).password("{noop}1234" ).roles("USER" ); } @Override protected void configure (HttpSecurity http) throws Exception { http. authorizeRequests() .antMatchers("/signup" ).permitAll() .antMatchers("/h2-console/**" ).permitAll() .antMatchers("/login" ).permitAll() .antMatchers("/logout" ).permitAll() .antMatchers("/user" ).hasRole("USER" ) .antMatchers("/manager" ).hasRole("MANAGER" ) .antMatchers("/admin" ).hasRole("ADMIN" ) .anyRequest().authenticated(); http .formLogin().permitAll(); } @Bean public RoleHierarchyImpl roleHierarchy () { String allHierarchy = roleHierarchyService.findAllHierarchy(); RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl (); roleHierarchy.setHierarchy(allHierarchy); return roleHierarchy; } @Bean public AccessDecisionVoter<? extends Object > roleVoter(){ RoleHierarchyVoter roleHierarchyVoter = new RoleHierarchyVoter (roleHierarchy()); return roleHierarchyVoter; } }
스프링 Security에서 권한간 상하관계를 인식할 수 있어 ADMIN 권한으로 MANAGER 권한의 자원에 접근할 수 있다.