스프링과 JPA 기반 웹 애플리케이션 개발 #21 현재 인증된 사용자 정보 참조

Jake Seo·2021년 5월 30일
0

스프링과 JPA 기반 웹 애플리케이션 개발 #21 현재 인증된 사용자 정보 참조

해당 내용은 인프런, 스프링과 JPA 기반 웹 애플리케이션 개발의 강의 내용을 바탕으로 작성된 내용입니다.

강의를 학습하며 요약한 내용을 출처를 표기하고 블로깅 또는 문서로 공개하는 것을 허용합니다 라는 원칙 하에 요약 내용을 공개합니다. 출처는 위에 언급되어있듯, 인프런, 스프링과 JPA 기반 웹 애플리케이션 개발입니다.

제가 학습한 소스코드는 https://github.com/n00nietzsche/jakestudy_webapp 에 지속적으로 업로드 됩니다. 매 커밋 메세지에 강의의 어디 부분까지 진행됐는지 기록해놓겠습니다.


현재 인증된 사용자 정보 참조

  • 스프링 시큐리티의 스프링 웹 MVC 지원
    • @AuthenticationPrincipal
      • 핸들러 매개변수로 현재 인증된 Principal을 참조할 수 있다.
    • Principal을 어디에 넣었더라?
public void login(Account account) {
    UsernamePasswordAuthenticationToken token =
            new UsernamePasswordAuthenticationToken(
                    account.getNickname(),
                    account.getPassword(),
                    List.of(new SimpleGrantedAuthority("ROLE_USER"))
            );

    SecurityContext context = SecurityContextHolder.getContext();
    context.setAuthentication(token);
}
  • @AuthenticationPrincipalSpEL을 사용해서 Principal 내부 정보에 접근할 수 있다.
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account")
  • 익명 인증인 경우에는 null로 설정하고, 아닌 경우에는 account 프로퍼티를 조회해서 설정하라는 뜻

Principal는 접근 주체를 말한다.

@CurrentUser 인터페이스 작성

package com.jakestudy.account;

import org.springframework.security.core.annotation.AuthenticationPrincipal;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME) // RUNTIME까지 유지되는 애노테이션
@Target(ElementType.PARAMETER) // 파라미터 타입으로 들어가는 애노테이션
// 인증정보가 없을 때는 스프링 시큐리티에서 자동으로 `anonymousUser` 라는 문자열을 principal 에 설정하는 특성을 이용했다.
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account")
public @interface CurrentUser {

}

위와 같이 작성하면 된다. @CurrentUser 인터페이스를 작성하는 이유는 매번 @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account")와 같이 애노테이션을 넣어주어도 되지만, 상당히 귀찮고 코드의 가독성이 매우 저해되기 때문이다.

그래서 커스텀한 애노테이션을 만들어서 쉽게 현재 사용자를 가져올 수 있도록 만들었다. #this == 'anonymousUser'라는 코드가 나온 이유는 스프링 시큐리티에서 인증되지 않은 사용자에게 기본으로 'anonymousUser'라는 principal을 제공하기 때문이다.

위는 디버그 중 SecurityContextHolder.getContext().getAuthentication() 메소드의 결과를 추적한 것인데, principal"anonymousUser"라는 값이 들어있는 것을 볼 수 있다.

UserAccount 클래스 작성하기

@Getter
// 스프링 시큐리티의 userdetail에서 user를 가져오는 것을 잊지말자.
// 다른 라이브러리도 user라는 이름의 객체가 많아서 주의해야 한다.
public class UserAccount extends User {

    private final Account account;

    public UserAccount(Account account) {
        super(account.getNickname(), account.getPassword(), List.of(new SimpleGrantedAuthority("ROLE_USER")));
        this.account = account;
    }
}

스프링 시큐리티 컨텍스트 내부에 인증 정보 중 현재 이용중인 principal에 대한 값으로 단순히 문자열을 넣는 것이 아니라, Account라는 객체 자체를 넣기 위해서 만든 클래스이다. 해당 클래스가 상속하는 User 클래스는 import org.springframework.security.core.userdetails.User에 위치한 클래스이다.

또한 super(account.getNickname(), account.getPassword(), List.of(new SimpleGrantedAuthority("ROLE_USER"))); 을 통해 스프링 시큐리티에서 제공하는 User 생성자를 호출하여, 스프링 시큐리티 authentication에서 name을 호출했을 때, password를 호출했을 때 Account 객체에서 각각 어느정보를 가져올지에 대해 넣어놓았다.

위와 같이 기존 코드를 보면, ${#authentication.name} 등을 가져오는 부분이 있는데, 이 때 account.getNickname()을 가져오도록 UserAccount 클래스의 소스에 명시해놓았다.

위는 스프링 시큐리티에서 제공하는 User 클래스의 내용 및 생성자이다.

login 메소드 변경하기

    public void login(Account account) {
        UsernamePasswordAuthenticationToken token =
                new UsernamePasswordAuthenticationToken(
                        new UserAccount(account), // principal을 변경했음
                        account.getPassword(),
                        List.of(new SimpleGrantedAuthority("ROLE_USER"))
                );

기존에 principal로 그냥 account.getNickname()을 호출했을 때 나오는 문자열을 줬다면, 이번에는 principal로 객체를 주었다.

principal의 자리에는 위에서 보다시피 어떤 오브젝트든 들어갈 수 있지만, User를 상속하는 것이 이번 소스코드의 내용처럼 활용하기 좋을 것이다.

디버그로 값을 추적해보면 아래와 같이 객체가 들어있는 것을 확인할 수 있다.

MainController 소스 변경

@Controller
public class MainController {
    @GetMapping("/")
    public String home(@CurrentUser Account account, Model model) {
        if(account != null) {
            model.addAttribute(account);
        }

        return "index";
    }
}

account가 있는 경우에만 내려준다. 내려줄 모델의 문자열은 변수명과 같아서 생략했다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글