티스토리 뷰

728x90

스프링 시큐리티 : 스프링 시큐리티 그밖에..

타임리프 스피링 시큐리티 확장팩

  • 의존성
  <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 네임 스페이스

  • 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;
      }
    
    }

@AuthenticationPrincipal

  • 웹 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);
    }
  • 변경내용
    • java principal
    @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();
  • thymeleaf 에서 리스트 참조
  <tr th:each="book : ${books}">
    <td><span th:text="${book.title}"> Title </span></td>
  </tr>

728x90
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday