모르지 않다는 것은 아는것과 다르다.

Spring Security

@AuthenticationPrincipal

채마스 2022. 2. 28. 20:51

개요

  • 아래와 같이 웹 MVC 핸들러 아규먼트로 Principal 객체를 받을 수 있다.
@GetMapping("/")
public String index(Model model, Principal principal){
    if (principal == null){
        model.addAttribute("message", "null");
    }else {
        model.addAttribute("message", "hello" + principal.getName());
    }
}
  • 여기서 Principal 대신 Account 를 받을 수 없을까? -> @AuthenticationPrincipal 를 사용하면 된다.





@AuthenticationPrincipal

  • SecurityContextHolder.getContext().getAuthentication().getPrincipal() 반환된 Principal 은 UserDetailService 의 loadUserbyUsername 메소드에서 (UserDatils) 타입으로 리턴된 값인 User객체이다. (위 컨트롤러의 파라미터는 principal 과는 다르다.)
@Service
public class AccountService implements UserDetailsService {

    @Autowired AccountRepository accountRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Account account = accountRepository.findByUsername(username);
        if (account == null) {
            throw new UsernameNotFoundException(username);
        }

        return User.builder()
                .username(account.getUsername())
                .password(account.getPassword())
                .roles(account.getRole())
                .build();
    }
}
  • Spring Security 에서 제공하는 User 를 상속받아 UserAccount 를 구현한다. -> UserDetails 와 Account를 연결해주는 어댑터 역할을 한다.
public class UserAccount extends User {

    private Account account;

    public UserAccount(Account account) {
        super(account.getUsername(), account.getPassword(), List.of(new SimpleGrantedAuthority("ROLE_" + account.getRole())));
    }

    public Account getAccount() {
        return account;
    }
}
  • 서비스는 아래와 같이 바뀔 수 있다.
@Service
public class AccountService implements UserDetailsService {

    @Autowired AccountRepository accountRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Account account = accountRepository.findByUsername(username);
        if (account == null) {
            throw new UsernameNotFoundException(username);
        }

        return new UserAccount(account);
    }
}
  • 위와 같이 반환값이 User 가 아닌 UserAccount가 반환되어 Principal 에 저장된다.
  • 이제 Controller 에서 @AuthenticationPrincipal 를 이용해서 UserAccount 를 인자로 받을 수 있다.
@GetMapping("/")
public String index(Model model, @AuthenticationPrincipal UserAccount userAccount) {
    if (userAccount == null) {
        model.addAttribute("message", "null");
    } else {
        model.addAttribute("message", "hello" + userAccount.getUsername());
    }

    return "index";
}
  • 여기서 UserAccount가 아닌 Account 를 파라미터로 받고 싶다면? -> spring expression 을 사용해서 해결할 수 있다. ->@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account") 로 애노테이션을 설정해주면 된다.





커스텀 애노테이션 적용

  • 파라미터로 Account를 받기위해서 파라미터 마다 @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account") 를 붙이기는 너무 번거롭다. -> 커스텀 애노테이션으로 이를 해결할 수 있다.
  • CurrentUser 라는 커스텀 애노테이션을 구현한다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account")
public @interface CurrentUser {
}
@GetMapping("/")
public String index(Model model, @CurrentUser Account account) {
    if (account == null) {
        model.addAttribute("message", "Hello Spring Security");
    } else {

        model.addAttribute("message", "Hello" + account.getUsername());
    }

    return "index";
}
  • 위와 같이 Account 를 Principal 대신 받을 수 있다.





스프링 데이터 연동

  • implementation 'org.springframework.boot:spring-boot-starter-security' 로 dependency 를 추가해 준다.
  • @Query 애노테이션에서 SpEL로 principal 참조할 수 있는 기능 제공한다.
  • 아래와 같이 @Query에서 principal 을 사용할 수 있다.
  • 여기서 principal 은 당연히 UserDetailService 의 loadUserbyUsername 메소드에서 (UserDatils) 타입으로 리턴된 값인 UserAccount 객체이다.
@Query("select b from Book b where b.author.id = ?#{principal.account.id}")
List<Book> findCurrentUserBooks();




REFERENCES

  • 백기선님의 스프링 시큐리티

'Spring Security' 카테고리의 다른 글

권한 계층 적용하기  (0) 2022.02.28
Voter 커스텀 하기  (0) 2022.02.28
메소드 시큐리티  (0) 2022.02.28
Custom DSL 적용  (0) 2022.02.28
Ajax 인증처이  (0) 2022.02.28