1. 권한 종류
권한 어노테이션의 종류
@PreAuthorize, @PostAuthorize
- SpEL(Spring Expression Language)을 지원한다. @PreAuthorize("hasRole('ROLE_USER')and(#account.username == principal.username)") 과 같이 쓸 수 있다.
- PrePostAnnotationSecurityMetadataSource가 처리한다.
@Secured, @RolesAllowed
- SpEL을 지원하지 않는다. @Secured("ROLE_USER"), @RolesAllowed("ROLE_USER")처럼 쓸 수 있다.
- SecuredAnnotationSecurityMetadataSource, Jsr250MethodSecurityMetadataSource가 처리한다.
@EnableGlobalMethodSecurity
메서드 방식의 인가 처리가 가능하게 해준다. 설정 클래스에 선언을 해주어야 한다. 또한 prePostEnabled = true, securedEnabled = true 등과 같이 속성값을 반드시 true로 변경해주어야 상기 어노테이션들을 사용할 수 있다.
PrePostAnnotationSecurityMetadataSource 등은 앞서 살펴본 MethodSecurityMetadataSource 객체를 상속하고 있다.
2. 원리 이해하기
이전 글의 메서드 방식 원리 이해와 비슷하다. 이번에는 @PreAuthorize 방식을 적용해본다. 우선 테스트를 위한 페이지를 작성한다.
실습 페이지 구성
AopSecurityController
@PreAuthorize 구문을 넣어주었고, 속성값으로 SpEL을 사용한 권한 방식을 적용해주었다. ROLE_USER라는 권한을 가져야 하고 and 조건으로 요청한 계정의 dto상의 username(ID)값이
@Controller
public class AopSecurityController {
@GetMapping("/preAuthorize")
@PreAuthorize("hasRole('ROLE_USER') and #account.username == principal.username" ) //SpEL : #account로 account 객체 참조 가능
public String preAuthorize(AccountDto account, Model model, Principal principal) {
model.addAttribute("method", "Success @PreAuthorize");
return "aop/method";
}
}
home.html
<body>
<div th:replace="layout/top::header"></div>
<div class="container">
<div class="row align-items-start">
<nav class="col-md-2 d-none d-md-block bg-light sidebar">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<div style="padding-top:10px;" class="nav flex-column nav-pills" aria-orientation="vertical">
<a th:href="@{/}" style="margin:5px;" class="nav-link active">대시보드</a>
<a th:href="@{/mypage}" style="margin:5px;" class="nav-link text-primary">마이페이지</a>
<a th:href="@{/messages}" onclick="messages()" style="margin:5px;" class="nav-link text-primary">메시지
<div>(Only Manager)</div>
</a>
<a th:href="@{/config}" style="margin:5px;" class="nav-link text-primary">환경설정</a>
<a th:href="@{/preAuthorize(username='user')}" style="margin:5px;" class="nav-link text-primary">@메서드 보안</a>
</div>
</li>
</ul>
</div>
</nav>
<div style="padding-top:50px;" class="col">
<div class="container text-center">
<h1 class="text-primary">DASHBOARD</h1>
<div class="security"></div>
<h1>Core Spring Security 에 오신 것을 환영합니다.</h1>
</div>
</div>
</div>
</div>
<div th:replace="layout/footer::footer"></div>
</body>
method.html
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="layout/header::userHead"></head>
<body>
<div th:replace="layout/top::header"></div>
<div class="container">
<div class="row align-items-start">
<nav class="col-md-2 d-none d-md-block bg-light sidebar">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<div style="padding-top:10px;" class="nav flex-column nav-pills" aria-orientation="vertical">
<a th:href="@{/}" style="margin:5px;" class="nav-link active">대시보드</a>
<a th:href="@{/mypage}" style="margin:5px;" class="nav-link text-primary">마이페이지</a>
<a th:href="@{/messages}" style="margin:5px;" class="nav-link text-primary">메시지</a>
<a th:href="@{/config}" style="margin:5px;" class="nav-link text-primary">환경설정</a>
<a th:href="@{/preAuthorize(username='user')}" style="margin:5px;" class="nav-link text-primary">@메소드보안</a>
<a th:href="@{/methodSecured}" style="margin:5px;" class="nav-link text-primary">메소드보안</a>
<a th:href="@{/pointcutSecured}" style="margin:5px;" class="nav-link text-primary">포인트컷보안</a>
<a th:href="@{/liveMethodSecured}" style="margin:5px;" class="nav-link text-primary">실시간메소드보안</a>
</div>
</li>
</ul>
</div>
</nav>
<div style="padding-top:50px;" class="col">
<div class="container text-center">
<h1 class="text-primary" th:text="${method}">Method</h1>
</div>
</div>
</div>
<div th:replace="layout/footer::footer"></div>
</body>
</html>
SecurityConfig.java
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) //메소드 보안 활성화
화면
원리 탐색
AbstractSecurityInterceptor의 beforeInvocation 부분에 break point를 걸어놓고, 로그인을 하지 않은채로 "@메서드 보안" 쪽에 접근해본다.
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
boolean debug = this.logger.isDebugEnabled();
if (!this.getSecureObjectClass().isAssignableFrom(object.getClass())) {
throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + this.getSecureObjectClass());
} else {
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
if (attributes != null && !attributes.isEmpty()) {
if (debug) {
this.logger.debug("Secure object: " + object + "; Attributes: " + attributes);
}
1차. url 방식 보안
if 구문에서 attributes 가 null이 나온다. 이것은 앞선 url 방식 보안 설정에서 배운대로, attributes 는 Collection<ConfigAttribute>이고, url 방식의 보안에서 url-role간 관계를 정의한 것이 ConfigAttribute인데 "@메서드 보안" 쪽에는 DB 상에 어떠한 resource라던가 role을 정의한 적이 없기 때문에 null 값이 나오는 것이다. 그리고 null인 경우 url 방식은 인가처리를 하지 않고 허용처리를 한다고 배웠다. 따라서 해당 url 인가처리 과정은 넘어가게 된다.
2차. 메서드 방식 보안
F9를 눌러 진행 시킨 뒤, F8로 다시 체크된 if문으로 들어와서 attributes 타입을 살펴보자. 메서드 보안 방식을 검사하는 부분을 확인할 수 있다. 등록했던 AopSecurityController는 Proxy객체로 들어오고, attributes의 속성값으로 @PreAuthorize로 설정해두었던 권한 관련 내용들이 포함된다.
참조
1) 인프런 - 스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 정수원님 강의
https://www.inflearn.com/course/%EC%BD%94%EC%96%B4-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%E
'Programming-[Backend] > Spring Security' 카테고리의 다른 글
[스프링 시큐리티]25. Method 방식 : Map 기반 DB 연동 (0) | 2022.03.01 |
---|---|
[스프링 시큐리티]23. Method 방식 : 동작방식 및 구조 알아보기 (0) | 2022.02.27 |
[스프링 시큐리티]22. 아이피 접속 제한 ; AccessDecisionVoter 추가 (0) | 2022.02.25 |
[스프링 시큐리티]21. 계층 권한 (0) | 2022.02.25 |
[스프링 시큐리티]20. 실시간 권한 업데이트, 허용필터 (0) | 2022.02.20 |