아카이브

[스프링 기반 REST API 개발] 시큐리티 현재 사용자 조회하기 본문

Spring/스프링 기반 REST API 개발

[스프링 기반 REST API 개발] 시큐리티 현재 사용자 조회하기

주멘이 2021. 1. 9. 22:01

SecurityContext

  • 자바 ThreadLocal 기반 구현으로 인증 정보를 담고 있다.
  • ※ ThreadLocal ? 오직 한 Thread에 의해서 read/write 가능한 variable을 생성할 수 있도록 하는 것(Thread Local Variable)
  • 인증 정보를 꺼내는 방법 
 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 
 // Anonymous인 경우 principal = "anonymousUser" 로 String 이다.

@AuthenticationPrincipal spring.security.User user

  • 인증을 안했다 ? null : username과 authorities 참조 가능 
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account")

 

queryEvents를 호출할때 user가 있는 경우 create-event link 추가

    @GetMapping
    public ResponseEntity queryEvents(Pageable pageable, PagedResourcesAssembler<Event> assembler,
                                      @CurrentUser Account account) {
                                      
        // @CurrentUser Account account는 @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account") 이다
        
        Page<Event> page = this.eventRepository.findAll(pageable);
        PagedModel<EntityModel<Event>> entityModels = assembler.toModel(page, EventResource::new);
        entityModels.add(new Link("/docs/index.html#resources-events-list").withRel("profile"));
        if (account != null) {
            entityModels.add(linkTo(EventController.class).withRel("create-event"));
        }

        return ResponseEntity.ok(entityModels);
    }

spring.security.User를 상속받는 클래스를 구현하면 User 도메인을 받을 수 있다.

UserDetails로 부터 받을 수 있는 security.User 대신에 Account 도메인으로 받기 위해  Adapter를 구현한다.

  • @AuthenticationPrincipal을 이용하여 Account 객체 가져오기 위함
  • Adapter.getAccount().getId()

AccountAdapter 구현하기

public class AccountAdapter extends User {

    private Account account;

    public AccountAdapter(Account account) {
        super(account.getEmail(), account.getPassword(), authorities(account.getRoles()));
        this.account = account;
    }

    public Account getAccount() {
        return account;
    }

    private static Collection<? extends GrantedAuthority> authorities(Set<AccountRole> roles) {
        return roles.stream()
                .map(r -> new SimpleGrantedAuthority("ROLE_" + r.name()))
                .collect(Collectors.toSet());
    }


}

 

AccountService의 loadByUsername이 AccountAdapter를 리턴하도록 수정하기

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Account account = accountRepository.findByEmail(username)
                .orElseThrow(() -> new UsernameNotFoundException(username));
        return new AccountAdapter(account);
    }
  • AccountAdapter는 User를 상속하고 있고,  User는 UserDetails를 구현하고 있다.

 

@AuthenticationPrincipal 을 사용할 경우,  anonymousUser인 경우 Object가 아니라 'anonymousUser' String이 넘어와서 오류가 발생할 수 있다.

그래서 expression에 Object를 반환할 수 있도록, 아래와 같이 설정해야 한다.

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account")
public @interface CurrentUser {
}