장쫄깃 기술블로그

[Spring Security] 4. Spring Security URL 권한 동적으로 체크하기 본문

Spring Framework/Spring Security

[Spring Security] 4. Spring Security URL 권한 동적으로 체크하기

장쫄깃 2022. 5. 13. 15:42
728x90


들어가며


지난 게시글들에서 Session, JWT를 이용하여 로그인 및 권한을 체크하는 방법에 대해서 알아보았다. 그런데 URL 및 권한을 관리자가 동적으로 관리해야 한다면 기존 방식을 사용할 수 없을 것이다. 접근 URL에 대한 권한 정보가 변경된 경우, 어플리케이션을 재기동해야 변경된 정보가 적용되는 문제가 있기 때문이다.

 

그래서 이번 게시글에선 URL과 권한을 동적으로 관리하고 체크할 수 있는 방법에 대해서 알아보려고 한다.

 

Spring Security 기본 설정은 이번 게시글에선 생략하려고 한다. Spring Security Session or JWT 기본 설정 관련 내용은 이전 게시글을 참고하면 된다.

링크 : https://jangjjolkit.tistory.com/25

 

[Spring Security] 2. Spring Security 적용하기 (Session)

들어가며 스프링 시큐리티 사용시 기본적으로 Session을 사용한다. 스프링 시큐리티와 Session을 이용하여 로그인 및 권한을 체크하는 기능을 만들어보았다. 스프링 시큐리티에 대한 설명은 해당

jangjjolkit.tistory.com

 

링크 : https://jangjjolkit.tistory.com/26

 

[Spring Security] 3. Spring Security 적용하기 (JWT, Access Token, Refresh Token)

들어가며 지난 게시글에서 스프링 시큐리티를 이용한 로그인 구현 시 Session을 사용하는 방법을 알아보았다. 스프링 시큐리티는 기본적으로 Session을 사용하는 것이 기본이지만 JWT를 이용하여 로

jangjjolkit.tistory.com

 

 

1. Java Configuration


해당 설정에서 기존에는 로그인을 한 경우에 접근이 가능하도록 했다. 이번에는 설정된 권한(역할)에 따른 접근가능 여부를 체크하려고 한다.

 

http.csrf().disable() // csrf 보안 설정 비활성화
	.antMatcher("/**").authorizeRequests() // 보호된 리소스 URI에 접근할 수 있는 권한 설정
	
	.antMatchers("/index").permitAll() // 전체 접근 허용
	.antMatchers("/main").authenticated() // 인증된 사용자만 접근 허용
	.antMatchers("/regist").annonymous() // 인증되지 않은 사용자만 접근 허용
	.antMatchers("/mypage").hasRole("ADMIN") // ROLE_ADMIN 권한을 가진 사용자만 접근 허용
	.antMatchers("/check").hasAnyRole("ADMIN", "USER") // ROLE_ADMIN 혹은 ROLE_USER 권한을 가진 사용자만 접근 허용
	
	// AuthorizedUrl.access API를 사용하여 URL에 대한 접근권한 동적으로 체크
	.anyRequest().access("@authorizationChecker.check(request, authentication)")
	.and()
	
	// ...

 

.anyRequest().access("@authorizationChecker.check(request, authentication)") 부분을 주목해야 한다. 위에서 설정한 URL을 제외한 다른 URL에 접근하는 경우 authorizationChecker 빈의 check 메소드를 호출하여 그것이 반환하는 값(True/False)에 따라 접근 가능 여부를 판단하겠다는 의미이다.

 

이 메소드는 입력된 SpEL을 런타임시에 평가하여 현재 사용자가 해당 URL에 대한 접근 권한이 있는지 동적으로 확인한다.

 

 

2. AuthorizationChecker 구현


AuthorizationChecker.check 메소드는 요청한 사용자의 권한이 해당 URL에 접근 가능한 권한을 가지고 있는지 판단하는 역할을 한다.

 

@Component
public class AuthorizationChecker {
	private final Logger log = LoggerFactory.getLogger(this.getClass());
	
	// 캐싱처리 적용
	@Cacheable("check")
	public boolean check(HttpServletRequest request, Authentication authentication) throws Exception{
		Object principalObj = authentication.getPrincipal();
		
		// 시큐리티 사용자 정보가 AdminDTO인 경우만 허용
		if (!(principalObj instanceof AdminDTO)) {
			return false;
		}
		
		List<String> authority = new ArrayList<String>();
		// Initializer에서 저장한 menu static list를 이용한 메뉴-권한 조회
		for (SecurityUrlMatcher matcher : MenuStaticValue.menuList) {
			log.info("[AuthorizationChecker] matcher url ==> " + matcher.getUrl() + ", ajax url ==> " + matcher.getAjaxUrl() + ", matcher authority ==> " + matcher.getAuthority());
			log.info("[AuthorizationChecker] request url ==> " + request.getRequestURI());
			// API 비동기 요청인 경우 AJAX URL 체크
			if("true".equals(request.getHeader("AJAX"))) {
				if (matcher.getAjaxUrl() != null && new AntPathMatcher().match(matcher.getAjaxUrl(), request.getRequestURI())) {
					authority.add(matcher.getAuthority());
				}
			}
  			// 그 외 요청인 경우 URL 체크
			else {
				if (new AntPathMatcher().match(matcher.getUrl(), request.getRequestURI())) {
					authority.add(matcher.getAuthority());
				}
			}
		}
		
		String adminId = ((AdminDTO) authentication.getPrincipal()).getAdmId();
		AdminDTO aDTO = new AdminDTO();
		aDTO.setAdmId(adminId);
		List<AdminDTO> authorities = // ... DB에서 해당 사용자의 권한 조회
		if(authorities == null || authorities.size() == 0) {
			return false;
		}
		
		log.info("[AuthorizationChecker] admin authority ==> " + authority);
		log.info("[AuthorizationChecker] admin authorities ==> " + authorities);
		for(AdminDTO auth : authorities) {
			if (authority == null || !authority.contains(auth.getRoleNm())) {
				return false;
			}
		}
		
		return true;
	}
}

 

 

 

필자는 DB에서 관리되는 URL과 권한정보를 어플리케이션 기동 시 조회하여 static 싱글톤으로 관리하고, 관리자가 URL이나 권한정보를 수정할 경우 static 싱글톤 변수를 수정해주었다.

 

또, 속도 향상을 위해 캐싱 처리를 추가했다.

 

이렇게 하면 URL이나 권한정보의 변경사항을 스프링 시큐리티에 실시간으로 적용할 수 있다. 관리자 화면에서 URL 정보, 권한, 권한에 따른 URL 접근 가능 여부 등을 설정하고 해당 코드에 적용해주면 된다.

 

 

3. URL(Menu) 정보 관리


먼저 URL, Menu등의 정보 객체를 구현한다.

 

@Data
public class SecurityUrlMatcher {
	private String url;
	private String ajaxUrl;
	private String authority;
}

 

그리고 객체 리스트를 싱글톤으로 관리하기 위한 객체를 구현한다.

@Getter
public class MenuStaticValue {
	public static List<SecurityUrlMatcher> menuList = new ArrayList<SecurityUrlMatcher>();
}

 

마지막으로 Initializer 클래스에서 어플리케이션 기동 시 해당 정보를 저장하게 한다.

 

@Component
@EnableScheduling
public class DemoInitializer implements ApplicationRunner{
	
	private final Logger log = LoggerFactory.getLogger(this.getClass());
	
	@Override
	public void run(ApplicationArguments args) throws Exception {
		// ...
		
		MenuStaticValue.menuList = // ... DB에서 Menu 관련 정보 조회 로직 실행
		
		// ...
	}
}

 

관리자가 Menu 정보를 추가, 수정, 삭제 시 MenuStaticValue.menuList를 다시 조회해서 저장하는 로직을 실행해주면 된다.

 

 

정리하며


필자는 이 방식으로 자사 관리 시스템의 권한을 각 팀별로 다르게 설정하게 했으며, 새로운 권한 정보를 적용할 때마다 어플리케이션 재기동 없이 적용되도록 했다.

 

관련 소스 코드는 깃허브를 참고하면 된다.

링크 : https://github.com/JangDaeHyeok/Spring-Security

 

GitHub - JangDaeHyeok/Spring-Security: Spring Security 세션, JWT 방식 구현

Spring Security 세션, JWT 방식 구현. Contribute to JangDaeHyeok/Spring-Security development by creating an account on GitHub.

github.com

 

다음 게시글에서는 Spring Security + Thymeleaf를 통해 로그인한 사용자의 권한에 따라 메뉴를 동적으로 노출시키는 방법에 대해 알아보겠다.

728x90