AccessDecisionManager 또는 Voter를 커스텀 해보자.

USER 사용자만 접근이 가능한 페이지

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
				.mvcMatchers("/", "info", "/account/**").permitAll()
				.mvcMatchers("/admin").hasRole("ADMIN")
				.mvcMatchers("/user").hasRole("USER")
				.anyRequest().authenticated();

		http.formLogin();
		http.httpBasic();
	}
}

무식한 방법으로는 UserDetailsService에서 role을 줄때 ADMIN이면 USER도 같이 주면 된다.

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
	Account account = accountRepository.findByUsername(username);
	if (account == null) {
		throw new UsernameNotFoundException(username);
	}

	return User.builder()
			.username(account.getUsername())
			.password(account.getPassword())
			.roles(account.getRole(), "USER")
			.build();
}

또는 계층 구조를 이해하는 AccessDecisionManager를 명시적으로 정의할 수 있다. 기본은 이전에 살펴본 하나라도 허용하면 허용하는 AffirmativeBased를 사용한다.

우리는 여기서도 AffirmativeBased를 사용하긴 할 것이다.

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	public AccessDecisionManager accessDecisionManager() {
		RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
		roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");

		DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
		handler.setRoleHierarchy(roleHierarchy);

		WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
		webExpressionVoter.setExpressionHandler(handler);

		List<AccessDecisionVoter<?>> voters = Collections.singletonList(webExpressionVoter);
		return new AffirmativeBased(voters);
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
				.mvcMatchers("/", "info", "/account/**").permitAll()
				.mvcMatchers("/admin").hasRole("ADMIN")
				.mvcMatchers("/user").hasRole("USER")
				.anyRequest().authenticated()
				.accessDecisionManager(accessDecisionManager());

		http.formLogin();
		http.httpBasic();
	}
}

짜잔! ExpressionHandler를 바꿔주어서 가능하다.

코드가 장황하지만 기본 설정과 다 똑같은데 handler에 Rolehierarchy만 추가했을 뿐이다. 이 코드를 조금 줄이면 AccessDecisionManager가 아니라 ExpressionHandler를 커스텀 하는 것이다.