장쫄깃 기술블로그

[Spring Security] 5. Spring Security + Thymeleaf 권한에 따른 메뉴 동적으로 출력하기 본문

Spring Framework/Spring Security

[Spring Security] 5. Spring Security + Thymeleaf 권한에 따른 메뉴 동적으로 출력하기

장쫄깃 2022. 5. 13. 16:50
728x90


들어가며


지난 게시글에서 URL에 대한 권한을 동적으로 체크하는 방법에 대해서 알아보았다. 이번 게시글에서는 권한에 따른 메뉴를 동적으로 출력하는 방법에 대해서 알아보려고 한다.

 

Spring Security 기본 설정은 이번 게시글에선 생략하려고 한다. 이번 게시글의 모든 내용은 이전 게시글에서 Spring Security Session or JWT 기본 설정 관련 내용을 전부 완료한 이후 진행하는 것으로 가정하려고 한다. 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. Dependency 추가


Thymeleaf 의존성을 추가한다.

 

<gradle>

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'

 

 

2. URL(Menu) 정보 관리


필자는 메뉴 관련 정보를 DB에서 조회 후 싱글톤으로 관리하였다.

 

먼저 DTO를 구현한다.

 

@Getter
@Setter
public class PageDTO extends CommonDTO {
	private String pageIdx                  = null;
	private String pageNm                   = null;
	private String pageUrl                  = null;
	
	private String gnbIdx                   = null;
	private String gnbNm                    = null;
	private String gnbIcon                  = null;
	
	private String authRoleNm               = null;
}

 

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

 

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

 

상위 메뉴와 하위 메뉴를 위해 2차원 리스트를 구현하였다.

 

MenuStaticValue.menuList는 Initializer 클래스에서 어플리케이션 기동 시 저장하거나, 메뉴 정보가 추가/수정/삭제될 때 다시 저장한다.

 

예를 들면,

@Component
public class DemoInitializer implements ApplicationRunner{
	@Override
	public void run(ApplicationArguments args) throws Exception {
		// ...
		
		// 전체 메뉴-권한 정보 리스트 조회/저장
		List<List<PageDTO>> menuList = // ... DB에서 메뉴 정보 조회하는 로직 실행
		MenuStaticValue.menuList = menuList;
		
		// ...
	}
}

이와 같은 방식이다.

 

 

2. Thymeleaf를 이용하여 메뉴 동적 호출하기


이전에 스프링 시큐리티를 이용해 로그인에 성공하고, 권한이 성공적으로 부여됐다면, Spring Security + Thymeleaf를 이용해 메뉴를 동적으로 호출할 수 있다.

 

html 코드에 아래와 같이 코드를 추가한다.

 

<ul>
	<!-- thymeleaf, Spring Security를 이용한 메뉴 출력 -->
	<li th:each="page, index : ${T(com.demo.some.util.staticval.MenuStaticValue).menuList}" class="nav-item sec">
		<a th:href="'#gnb-' + ${page[0].gnbIdx}">
			<i th:class="${page[0].gnbIcon}"></i>
			<p th:text="${page[0].gnbNm}"></p>
		</a>
		<div th:id="'gnb-' + ${page[0].gnbIdx}">
			<ul class="menu-sub-nav">
				<li th:each="subPage, subIndex : ${page}" th:class="'menu-sub ' + ${subPage.pageUrl}" th:sec:authorize="'hasAnyRole(' + ${subPage.authRoleNm} + ')'">
					<a th:href="'/' + ${subPage.pageUrl}">
						<span th:text="${subPage.pageNm}"></span>
					</a>
				</li>
			</ul>
		</div>
	</li>
</ul>

 

2차원 리스트의 메뉴 목록을 출력하는 Thymeleaf 로직이다.

 

${T(com.demo.some.util.stativcal.MenuStativValue).menuList} 부분에서 T는 Thymeleaf에서 static 객체를 호출하겠다는 뜻이다. static 싱글톤으로 관리중이던 MenuStaticValue.menuList를 Thymeleaf에서 호출하는 것이다.

 

th:sec:authorize 부분은 Thymeleaf에서 태그 권한을 동적으로 출력하기 위해 사용한다. Thymeleaf에서는 sec:authorize를 사용하여 시큐리티 권한에 따른 태그 노출 기능을 제공하며, 태그에 대한 사용자 권한이 없는 경우에는 태그가 노출되지 않는다. 거기에 더해 th: 를 사용하여 권한을 Thymeleaf에서 동적으로 출력하도록 했다. hasAnyRole은 설정한 권한 중 하나라도 포함되는 경우 true가 되는 메소드이다.

 

다음으로 javascript로 불필요한 메뉴를 삭제하는 기능을 추가한다.

 

$(".menu-sub-nav").each(function(index, element){
	if($(element).find(".menu-sub").length == 0 ){
		$(element).parents("li.nav-item").remove();
	}
});

 

이렇게 하면 권한이 없어 하위메뉴가 노출되지 않은 상위메뉴를 삭제해준다.

 

 

3. 그 밖의 Spring Security + Thymeleaf 기능들


<!-- 인증되지 않은(로그인하지 않은) 사용자에게 보임 -->
<button sec:authorize="isAnonymous()" type="button" onclick="location.href='/admin/loginView'">로그인</button>
<!-- 인증된(로그인한) 사용자에게 보임 -->
<button sec:authorize="isAuthenticated()" type="button" onclick="location.href='/admin/logout'">로그아웃</button>

<!-- ROLE_ADMIN 권한을 가지고 있다면 보임 -->
<div sec:authorize="hasRole('ADMIN')">ROLE_ADMIN 권한이 있습니다.</div>
<!-- ROLE_SUB_ADMIN 권한을 가지고 있다면 보임 -->
<div sec:authorize="hasRole('SUB_ADMIN')">ROLE_SUB_ADMIN 권한이 있습니다.</div>
<!-- ROLE_USER 권한을 가지고 있다면 보임 -->
<div sec:authorize="hasRole('USER')">ROLE_USER 권한이 있습니다.</div>
<!-- ROLE_ADMIN 혹은 ROLE_SUB_ADMIN 권한을 가지고 있다면 보임 -->
<div sec:authorize="hasAnyRole('ADMIN, SUB_ADMIN')">ROLE_ADMIN 혹은 ROLE_SUB_ADMIN 권한이 있습니다.</div>

<br/>
<!--인증시 사용된 객체에 대한 정보-->
<b>Authenticated DTO:</b>
<div sec:authorize="isAuthenticated()" sec:authentication="principal"></div>

<br/>
<!--인증시 사용된 객체의 Username (ID)-->
<b>Authenticated username:</b>
<div sec:authorize="isAuthenticated()" sec:authentication="name"></div>

<br/>
<!--객체의 권한-->
<b>Authenticated admin role:</b>
<div sec:authorize="isAuthenticated()" sec:authentication="principal.authorities"></div>

 

728x90