[Spring Security] 계층 권한 적용하기

식빵·2022년 9월 17일
0

이 시리즈에 나오는 모든 내용은 인프런 인터넷 강의 - 스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 에서 기반된 것입니다. 그리고 여기서 인용되는 PPT 이미지 또한 모두 해당 강의에서 가져왔음을 알립니다. 추가적으로 여기에 작성된 코드들 또한 해당 강의의 github 에 올라와 있는 코드를 참고해서 만든 겁니다.


현재는 Admin 권한이 하나 있으면, 그 권한만 갖는다.
하지만 실제로는 우리가 원하는 것은 만약 Admin 권한이 있다면 User, Manager 권한 모두 갖도고록 하고 싶다. 즉 Admin 을 최상위 권한으로 두고 싶다는 의미다.

이러기 위해서는 권한을 계층적으로 적용시켜야 하는데,
스프링 시큐리티를 사용하면 쉽게 구현할 수 있다. 아래 그림을 보자.

  • RoleHierarchy 를 통해서 권한에 계층으로 다룰 수 있다.

  • ROLE_ADMIN > ROLE_MANAGER > ROLE_USER 순으로 권한이 있을 경울 ROLE_ADMIN 만 있으면 하위 권한을 모두 포함시킨다.

  • RoleHierarcy 클래스는 문자열을 통해서 이런 권한 계층을 분석한다.

    • ROLE_SUPER_ADMIN > ROLE_MANAGER
       ROLE_MANAGER > ROLE_USER
    • 반드시 new line 처리를 해줘야한다! 포맷이 그런 것이니 의문 갖지 말기!
  • 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

import io.security.corespringsecurity.domain.entity.RoleHierarchy;
import org.springframework.data.jpa.repository.JpaRepository;

public interface RoleHierarchyRepository extends JpaRepository<RoleHierarchy, Long> {
	RoleHierarchy findByChildName(String roleName);
}



Service

@Service
@RequiredArgsConstructor
public class RoleHierarchyServiceImpl implements RoleHierarchyService {
	
	private final RoleHierarchyRepository roleHierarchyRepository;
	
	@Transactional
	@Override
	public String findAllHierarchy() {
		List<RoleHierarchy> roleHierarchy = roleHierarchyRepository.findAll();
		StringBuilder concatRoles = new StringBuilder();
		
		for (RoleHierarchy role : roleHierarchy) {
			if (role.getParentName() != null) {
				concatRoles.append(role.getParentName().getChildName());
				concatRoles.append(" > ");
				concatRoles.append(role.getChildName());
				concatRoles.append("\n");
			}
		}
		return concatRoles.toString();
	}
}


DB INSERT

INSERT INTO public.role_hierarchy
(id, child_name, parent_name)
VALUES(0, 'ROLE_ADMIN', null),
(1, 'ROLE_MANAGER', 'ROLE_ADMIN'),
(2, 'ROLE_USER', 'ROLE_MANAGER');

select * from role_hierarchy rh ;



Spring Security 설정 변경

@Configuration
@EnableWebSecurity
@Slf4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    // ... 생략 ...
        
    private AccessDecisionManager affirmativeBased() {
        AffirmativeBased affirmativeBased = new AffirmativeBased(getAccessDecistionVoters());
        return affirmativeBased;
    }
    
    private List<AccessDecisionVoter<?>> getAccessDecistionVoters() {
        List<AccessDecisionVoter<? extends Object>> accessDecisionVoters = new ArrayList<>();
        accessDecisionVoters.add(roleVoter());
        return accessDecisionVoters;
    }
    
    @Bean
    public AccessDecisionVoter<? extends Object> roleVoter() {
        RoleHierarchyVoter roleHierarchyVoter = new RoleHierarchyVoter(roleHierarchy());
        return roleHierarchyVoter;
    }
    
    @Bean
    public RoleHierarchyImpl roleHierarchy() {
        return new RoleHierarchyImpl();
    }

    
    @Bean
    public MyUrlFilterInvocationSecurityMetadatsSource myUrlFilterInvocationSecurityMetadataSource() throws Exception {
        return new MyUrlFilterInvocationSecurityMetadatsSource(urlResourcesMapFactoryBean().getObject(), securityResourceService);
    }


	@Bean
    public PermitAllFilter customFilterSecurityInterceptor() throws Exception {
        PermitAllFilter permitAllFilter = new PermitAllFilter(permitAllResources);
        permitAllFilter.setSecurityMetadataSource(myUrlFilterInvocationSecurityMetadataSource());
        permitAllFilter.setAccessDecisionManager(affirmativeBased());
        permitAllFilter.setAuthenticationManager(authenticationManagerBean());
        return permitAllFilter;
    }
}



roleHierarchy.setHierarchy 시작 시점 정하기

import io.security.corespringsecurity.service.RoleHierarchyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.stereotype.Component;

@Component
public class SecurityInitializer implements ApplicationRunner {
	
	@Autowired
	private RoleHierarchyService roleHierarchyService;
	
	@Autowired
	private RoleHierarchyImpl roleHierarchy;
	
	@Override
	public void run(ApplicationArguments args) throws Exception {
		String allHierarchy = roleHierarchyService.findAllHierarchy();
		roleHierarchy.setHierarchy(allHierarchy);
	}
}
  • 이렇게 하면 Spring 애플리케이션이 초기화가 다 끝나고 나서 자동으로 run 메소드에 작성한 내용이 실행된다.
profile
백엔드를 계속 배우고 있는 개발자입니다 😊

0개의 댓글