일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- git
- 객체지향프로그래밍
- SQL
- aop
- 디자인패턴
- http
- 인터셉터
- mybatis
- exception
- 스프링부트
- aspect
- Interceptor
- response
- network
- 자바
- 트랜잭션
- proxy pattern
- OOP
- Spring Security
- Filter
- Redis
- java
- Spring
- 스프링 시큐리티
- request
- MYSQL
- spring boot
- 관점지향프로그래밍
- RestControllerAdvice
- 스프링
- Today
- Total
장쫄깃 기술블로그
[Spring Security] 5. Spring Security + Thymeleaf 권한에 따른 메뉴 동적으로 출력하기 본문
[Spring Security] 5. Spring Security + Thymeleaf 권한에 따른 메뉴 동적으로 출력하기
장쫄깃 2022. 5. 13. 16:50
들어가며
지난 게시글에서 URL에 대한 권한을 동적으로 체크하는 방법에 대해서 알아보았다. 이번 게시글에서는 권한에 따른 메뉴를 동적으로 출력하는 방법에 대해서 알아보려고 한다.
Spring Security 기본 설정은 이번 게시글에선 생략하려고 한다. 이번 게시글의 모든 내용은 이전 게시글에서 Spring Security Session or JWT 기본 설정 관련 내용을 전부 완료한 이후 진행하는 것으로 가정하려고 한다. Spring Security Session or JWT 기본 설정 관련 내용은 이전 게시글을 참고하면 된다.
링크 : https://jangjjolkit.tistory.com/25
링크 : https://jangjjolkit.tistory.com/26
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>