Spring Security 권한 계층 사용하기 - @RoleHierarcy

목차

@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 권한의 자원에 접근할 수 있다.

Share