[Spring Security] @AuthenticationPrincipal
@AuthenticationPrincipal
@GetMapping("/")
public String index(Model model, Principal principal) {
if (principal == null) {
model.addAttribute("message", "Spring security");
} else {
model.addAttribute("message", "Hello, " + principal.getName());
}
return "index";
}
로그인한 사용자의 정보를 파라미터로 받아오고 싶을 때, Java 표준 Principal 객체를 받아서 사용한다. Java 표준 Principal 객체는 name 정보만 참조할 수 있다.
@AuthenticationPrincipal 어노테이션을 사용하면 UserDetailsService에서 Return한 객체를 파라미터로 직접 받아 사용할 수 있다.
@GetMapping("/")
public String index(Model model, @AuthenticationPrincipal User user) {
if (user == null) {
model.addAttribute("message", "Spring security");
} else {
model.addAttribute("message", "Hello, " + user.getName());
}
return "index";
}
현재 로그인한 사용자의 정보를 참조하고 싶을 때 도메인의 User를 나타내는 객체 (Account)를 직접 사용하고 싶다는 요구사항이 있다면?
Adapter 클래스
- UserDetailsService에서 리턴하는 타입을 변경하면, Controller에서 @AuthenticationPrincipal로 받아올 수 있는 객체가 변경된다.
- 이때 사용할 수 있는 방법은 두가지이다.
1. Account 객체를 직접 리턴하기
2. Account 객체를 감싸는 Adapter 클래스를 사용하기
Account 객체를 직접 리턴하는 방법은 Account 객체가 UserDetails를 구현해야 한다.
도메인 객체는 특정 기술에 종속되지 않도록 개발하는 것이 좋다.
AccountAdapter.java
@Getter
public class AccountAdapter extends User {
private Account account;
public AccountAdapter(Account account) {
super(account.getUserId(), account.getPassword(), authorities(account.getRoles()));
this.account = account;
}
private static Collection<? extends GrantedAuthority> authorities(Set<AccountRole> roles) {
return roles.stream()
.map(r -> new SimpleGrantedAuthority(r.getRole().authority()))
.collect(Collectors.toSet());
}
}
- User 클래스를 상속받는다.
- AccountAdapter의 멤버는 오로지 Account 객체만 존재한다.
- 생성자 내부에서 User 클래스의 생성자를 호출하여 username, password, role을 세팅한다.
User 클래스를 상속받는 이유는?
- UserDetailsService에서 리턴하는 객체는 UserDetails 타입이어야 한다.
- UserDetails를 구현하는 User 클래스를 상속받는 방식으로 사용한다.
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = accountRepository.findByUserId(username);
if (account == null) {
throw new UsernameNotFoundException("user not found");
}
return new AccountAdapter(account);
}
AccountAdapter 사용하기
@GetMapping("/")
public String index(Model model, @AuthenticationPrincipal AccountAdapter accountAdapter) {
if (accountAdapter == null) {
model.addAttribute("message", "Spring security");
} else {
model.addAttribute("message", "Hello, " + accountAdapter.getAccount().getUserId());
}
return "index";
}
accountAdapter.getAccount()로 Account 객체를 참조하지 말고 Account 객체를 직접 사용하도록 하자.
Account 객체 직접 사용하기
@AuthenticationPrincipal은 SpEL을 지원한다.
SpEL을 사용해서 Adapter 클래스가 아닌 Account 객체를 직접 가져올 수 있다.
@GetMapping("/")
public String index(Model model, @AuthenticationPrincipal(expression = "#this == 'anonymousUser ? null : account") Account account) {
if (account == null) {
model.addAttribute("message", "Spring security");
} else {
model.addAttribute("message", "Hello, " + account.getUserId());
}
return "index";
}
만약 현재 참조중인 객체가 AnonymousAuthenticaionFilter에 의해 생성된 Authentication인 경우 null을 반환하고, 아니라면 AccountAdapter 객체로 간주하고 Account 객체를 반환한다.
@CurrentUser
SpEL을 사용해서 직접 Account 객체를 가져오긴 했지만 해당 코드가 너무 길다.
- 커스텀 어노테이션을 생성하여 해결하자
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account")
public @interface CurrentUser {
}
@GetMapping("/")
public String index(Model model, @CurrentUser Account account) {
if (account == null) {
model.addAttribute("message", "Spring security");
} else {
model.addAttribute("message", "Hello, " + account.getUserId());
}
return "index";
}
정리
- @AuthenticationPrincipal을 사용하여 UserDetailsService에서 리턴한 객체를 컨트롤러의 파라미터로 직접 참조할 수 있다.
- 만약 도메인의 User를 표현하는 클래스(Account)를 직접 참조하고 싶다면 Adapter 클래스를 사용하자
출처
'web > Spring' 카테고리의 다른 글
[Spring] Assert (0) | 2022.07.15 |
---|---|
[Spring] RedirectAttributes 인터페이스의 addFlashAttribute (0) | 2022.07.12 |
[Spring] WebSecurityConfigurerAdapter deprecated 해결법 (0) | 2022.07.05 |
[Spring] Spring Security란? (0) | 2022.07.04 |
[Spring] HandlerInterceptor를 활용하여 컨트롤러 중복코드 제거하기 (0) | 2022.06.15 |
댓글 개