개요
- 아래와 같이 웹 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