스프링 시큐리티 : 스프링 시큐리티 그밖에..
타임리프 스피링 시큐리티 확장팩
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
- 스프링 부트의 경우 의존성만 추가해주면된다.
- Authentication과 Authorization 참조
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Index</title>
</head>
<body>
<h1 th:text="${message}">hello</h1>
<div th:if="${#authorization.expr('isAuthenticated()')}">
<h2 th:text="${#authentication.name}">Name</h2>
<a href="/logout" th:href="@{/logout}">Logout</a>
</div>
<div th:unless="${#authorization.expr('isAuthenticated()')}">
<a href="/signin" th:href="@{/signin}">Login</a>
</div>
</body>
</html>
- typesafe 하지 못하다.
- expression 오타의 경우 문제가 생긴다.
sec 네임 스페이스
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
- Authentication과 Authorization 참조
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>Index</title>
</head>
<body>
<h1 th:text="${message}">hello</h1>
<div sec:authorize-expr="isAuthenticated()">
<h2 sec:authentication="name">Name</h2>
<a href="/logout" th:href="@{/logout}">Logout</a>
</div>
<div sec:authorize-expr="!isAuthenticated()">
<a href="/signin" th:href="@{/signin}">Login</a>
</div>
</body>
</html>
- 툴의 도움을 받아서 보다 오타확률을 줄 일 수 있다. (인텔리제이 자동완성)
메소드 시큐리티
- 서비스 계층을 직접호출 할 때, 사용하는 보안 기능
- 설정
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class MethodSecurityConfig {}
securedEnabled = true
: @Secured
어노테이션 사용
jsr250Enabled = true
: @RollAllowed
어노테이션 사용
prePostEnabled = true
: @PreAuthorize
, @PostAuthorize
사용
@Secured("ROLE_USER")
@RolesAllowed("ROLE_USER")
@PreAuthorize("hasRole('USER')")
public void dashBoard() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
System.out.println("=================");
System.out.println(authentication);
System.out.println(userDetails.getUsername());
}
@Secured
와 @RollAllowed
- 메소드 호출 이전에 권한을 확인한다.
- 스프링 EL 사용 불가
@PreAuthorize
, @PostAuthorize
- 메소드 호출 이전 및 이후에 권한을 확인할 수 있다.
- 스프링 EL을 사용 가능 (파라미터나 리턴타입이 있는경우에 활용)
- webSecurity에
expressionHanlder()
를 등록했지만, 메소드 시큐리티에서는 적용이 안된다. (계층구조 ROLE 사용)
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Override
protected AccessDecisionManager accessDecisionManager() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
AffirmativeBased accessDecisionManager = (AffirmativeBased) super.accessDecisionManager();
accessDecisionManager.getDecisionVoters().add(new RoleHierarchyVoter(roleHierarchy));
return accessDecisionManager;
}
}
- 웹 MVC 핸들러 아규먼트로 Principal 객체를 받을 수 있다.
- pricipal이 아닌 내가 정의한 유저객체를 사용하고 싶다면…
- 메소드 파라미터에 정의하는 principal은 스프링 시큐리티가 제공하는 principal이 아닌 Java.security.principal이다.
- 시큐리티가 제공하는(SecurityContextHolder안에 들어있는) principal(UserDetials타입)을 받고 싶다면..
- 도메인 객체를 User(springSecurity)를 상속하거나 UserDetails를 구현하도록 하거나 어댑터 객체를 만든다.
public class UserAccount extends User {
private Account account;
public UserAccount(Account account) {
super(account.getUsername(), account.getPassword(), List.of(new SimpleGrantedAuthority("ROLE_" + account.getRole())));
this.account = account;
}
public Account getAccount() {
return account;
}
}
- UserDetailsService 에서도 해당 객체로 리턴하도록한다.
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<Account> byUsername = accountRepository.findByUsername(username);
Account account = byUsername.orElseThrow(() -> new UsernameNotFoundException(String.format("%s is not founded!", username)));
return new UserAccount(account);
}
- 변경내용
@GetMapping("/")
public String index(Model model, Principal principal) {
if (principal == null) {
model.addAttribute("message", "Hello Spring Security");
} else {
model.addAttribute("message", "Hello " + principal.getName());
}
return "index";
}
- spring security principal
@GetMapping("/")
public String index(Model model, @AuthenticationPrincipal UserAccount userAccount) {
if (userAccount == null) {
model.addAttribute("message", "Hello Spring Security");
} else {
model.addAttribute("message", "Hello " + userAccount.getUsername());
}
return "index";
}
@GetMapping("/")
public String index(Model model, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account") Account account) {
if (account == null) {
model.addAttribute("message", "Hello Spring Security");
} else {
model.addAttribute("message", "Hello " + account.getUsername());
}
return "index";
}
- expression 활용
- 너무 긴 코드가 거슬린다면 customAnnotation을 만들어 사용하자.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account")
public @interface CurrentUser {}
스프링 데이터 연동
@Query
애노테이션에서 SpEL로 principal 참조할 수 있는 기능 제공.
- 의존성
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-data</artifactId>
<version>${spring-security.version}</version>
</dependency>
@Query
에서 spring security principal 사용하기
@Query("select b from Book b where b.author.id = ?#{principal.account.id}")
List<Book> findCurrentUserBooks();
<tr th:each="book : ${books}">
<td><span th:text="${book.title}"> Title </span></td>
</tr>