HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🌟
Programmers 최종 플젝 6조
/
💫
[백엔드] 트러블 슈팅
/
⚙️
@AuthUser 적용 배경(feat. SpEL 문법 수정 배경, EmptyUser)
⚙️

@AuthUser 적용 배경(feat. SpEL 문법 수정 배경, EmptyUser)

생성일
Jan 16, 2022 02:30 PM
태그
Spring Security
작성자
속성 1
속성 1
작성 여부
작성 완료

🗯️ 문제


@Target({ElementType.PARAMETER, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : user") public @interface AuthUser {
AuthUser.interface
Controller layer 에서 매개변수에 어노테이션을 사용하여 편리하게 인증 유저 정보를 받아오기 위해 @AuthenticationPrincipal 어노테이션을 사용하여 Custom 어노테이션(@AuthUser)을 만들어 사용했습니다.
위 코드 덕분에 쉽게 유저 객체를 얻어올 수 있었는데요. 구현 중에 발생한 문제점 및 개선 방안에 대해서 설명해보도록 하겠습니다.
 
먼저 @AuthenticationPrincipal 의 expression 매개변수에 작성한 SpEL 표현식에 대해서 설명하겠습니다.
SpEL 알아보기

SpEL 이란? (Spring Expression Language)


SpEL은 Spring Expression Language의 약자로 스프링 표현식입니다.
객체 그래프를 조회하고 조작하는 기능을 제공합니다. Unified EL 과 비슷하지만, 메소드 호출을 지원하며, 문자열 템플릿 기능도 제공합니다.
OGNL, MVEL, JBOss EL 등 자바에서 사용할 수 있는 여러 EL이 있지만, SpEL은 모든 스프링 프로젝트 전반에 걸쳐 사용할 EL로 만들었습니다.(스프링 3.0 부터 지원하는 표현식입니다.)
스프링 시큐리티에서도 SpEL을 많이 사용합니다. 문자열로 구성된 표현식을 통해 자바 코드의 객체나 메서드를 사용하고, and나 or 연산을 지원하여 권한에 따른 조건문 작성도 쉽게 할 수 있습니다.
 
 
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : user")
SpEL 표현식에선 # 을 앞에 붙여 변수를 참조할 수 있는데, #this는 항상 현재 평가하고 있는 객체를 참조합니다.
@AuthenticationPrincipal 에선 인증되지 않은 객체에 대해 ‘anonymousUser’ 문자열을 return 하는데,
이를 이용하여 현재 전달된 객체가 인증되지 않았으면 null을 파라미터에 담고, 인증된 객체라면 user 객체로 담는 식으로 표현을 하였습니다.
 

문제점


인증되지 않은 객체에 대해 null을 User 객체에 담으니 개발 중에 한가지 문제점을 발견하였습니다.
 
해당 문제점은 프로젝트 요구사항에 따른 API 명세서를 참고하였을 때, anonymous 유저(인증하지 않은 유저)와 인증한 유저에 대해서 접근을 허용하는 API가 존재하였기 때문에 발생하였는데요,
때문에 Controller 로 부터 User 객체를 전달받은 Service 메서드에서 user가 null 인지 아닌지에 대한 체크를 하고 null 이 아닌 경우, user.getId()를 Repository 메서드에 전달해주는 코드를 모든 메서드에서 해줘야 했습니다.
if(user == null){ XXXRepository.XXX(); } else{ XXXRepository.XXX(user.getId(),...); }
위 코드에서 동일한 Repository 메서드를 사용하여 if 조건문을 없앨 수 있는 방법이 무엇일까 고민을 하게 되었습니다.

🔥 해결 방법


위에서 발생한 문제점을 해결하기 위해 User 객체를 상속받는 EmptyUser 클래스를 구현하였습니다.
notion image
그리고 @AuthenticationPrincipal 어노테이션의 Custom 어노테이션인 @AuthUser 에서 SpEL 표현식을 다음과 같이 수정하였습니다.
@Target({ElementType.PARAMETER, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? new kdt.prgrms.kazedon.everevent.domain.user.EmptyUser() : user") public @interface AuthUser {
AuthUser.interface
위와 같이 수정하면 anonymous 유저일 경우 User 객체엔 EmptyUser 객체가 담기게 됩니다.
이렇게 수정하고 다시 서비스 메서드들을 살펴보도록 하겠습니다.
public void serviceMethod(User user, ... ) { XXXRepository.XXX(user.getId(),...); }
EmptyUser는 모든 필드 값을 null로 갖고 있기 때문에, user.getId()를 하더라도 NullPointException이 발생하지 않고 repository 메서드에 null이 전달되게 됩니다. 또한 코드도 간결해진 것도 확인할 수 있는데요. repository 메서드도 2개로 따로 분리하지 않고 하나에서 관리할 수 있다는 점도 개선점으로 확인할 수 있습니다.
 

문제 및 개선 사항


@AuthUser을 사용하면서 발생한 문제점을 해결하고, 개선된 사항은 다음과 같습니다.
문제점
  • Service Layer 메서드에서 NullPointException 방지를 위해 User 객체의 null 체크를 항시 했어야 했음.
  • 때문에 repository 메서드도 많아짐.
개선 사항
  • 인증되지 않은 객체에 대해 EmptyUser() 객체를 생성하여 사용함.
  • user null 체크 if 조건문을 작성할 필요가 없음.
  • user.getId()를 해도 NullPointException이 발생하지 않고 null 값을 repository 메서드에 전달함.
  • repository 메서드도 하나로 사용할 수 있음.
 

회고


@AuthUser 메서드를 사용하면서 여러가지 고민들을 많이 하게 되었습니다.
  • @AuthenticationPrincipal 에서 Entity 객체인 User를 바로 받아도 될까
    • 서비스 메서드에 필요할 유저 정보가 아닌 이상, 불필요한 정보까지 넘겨줄 필요가 있을까? 라는 고민을 하게 되었습니다. 또 근본적으로 Entity 객체를 Controller 단에서 알고 있어도 될까? 에 대한 고민도 해본 것 같습니다. 해당 부분에 대해서는 더 공부해보고 리팩토링이나 포스팅을 한번 해보고 싶습니다.
  • @AuthenticationPrincipal 내부 원리에 대해서 깊이 공부해 보고 싶다.
    • 클라이언트로부터 전달받은 정보(Token)을 통해 인증 로직을 처리하는 것은 Spring Security의 Filter 단에서 수행하게 됩니다. @AuthenticationPrincipal 어노테이션도 인증 로직을 진행할 때, Principal 객체를 생성해주는 코드를 통해 인증 객체를 전달 받습니다.
  • @AuthUser로 전달받은 User를 서비스 메서드에 전달할 때, user 객체 그대로 전달할까 user.getId() 값을 전달할까
    • user 객체를 그대로 전달하는 방법을 선택하였습니다.
    • 그 이유로는 위 문제를 해결하기 전엔 user.getId()를 서비스 메서드에 전달하게 되면 controller 단에서 if문을 통해 user가 null 인지 체크해야 되는 문제가 발생하였기 때문입니다.
 

📖 참고문서


[Spring Security] @AuthenticationPrincipal 어노테이션은 어떻게 동작할까??
안녕하세요. 오늘은 스프링 시큐리티를 활용하면서 궁금했던 부분을 공부해보았습니다. 스프링 시큐리티는 SecurityContext에 인증된 Authentication 객체를 넣어두고 현재 스레드 내에서 공유되도록 관리하고 있는데요. 아래는 SecurityContext 인터페이스에 기재된 주석의 일부를 발췌했습니다. (SecurirtContext : Interface defining the minimum security information associated with the current thread of execution.)
[Spring Security] @AuthenticationPrincipal 어노테이션은 어떻게 동작할까??
https://sas-study.tistory.com/410
[Spring Security] @AuthenticationPrincipal 어노테이션은 어떻게 동작할까??
[Spring Security] @AuthenticationPrincipal 로그인한 사용자 정보 받아오기
Spring Security에서는 Session에서 현재 사용자의 정보를 다음과 같이 Principal로 조회할 수 있다. Principal 객체는 Java 표준 객체이고, 받을 수 있는 정보는 name뿐이다. 하지만 우리는 name뿐이 아닌 Account의 많은 정보를 얻고싶다. 그리고 Account의 정보를 Controller에서 맵핑 메서드의 파라미터로 받는 것을 효율적으로 받기 위해 @AuthenticationPrincipal과 어댑터 패턴을 적용하여 사용할 수 있다.
[Spring Security] @AuthenticationPrincipal 로그인한 사용자 정보 받아오기
https://velog.io/@solchan/Spring-Security-AuthenticationPrincipal-%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%95%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%A0%95%EB%B3%B4%EB%A5%BC-%EB%B0%9B%EC%95%84%EC%98%A4%EA%B8%B0
[Spring Security] @AuthenticationPrincipal 로그인한 사용자 정보 받아오기
@AuthenticationPrincipal - 현재 사용자 조회하기
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); User user = (User)authentication.getPrincipal(); 이는 @AuthenticationPrincipal 로도 구현 가능하고 이 결과는 UserDetailsService를 구현한 AccountService에서 반드시 상속받아야 하는 loadUserByUsername 메서드의 리턴된 결과이다. @Service public class AccountService implements UserDetailsService { ... @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Account account = accountRepository.findByEmail(username) .orElseThrow(() -> new UsernameNotFoundException(username)); return new User(account.getEmail(), account.getPassword(), authorities(account.getAccountRole())); } ...
@AuthenticationPrincipal - 현재 사용자 조회하기
https://jjeda.tistory.com/7
@AuthenticationPrincipal - 현재 사용자 조회하기
[Spring-Security] @AuthenticationPrincipal에 대하여
애플리케이션에서 로그인한 사용자와 관련된 특정한 정보들을 조회하는 건 모든 회원제 어플리케이션에서 빈번하게 일어나는 작업이다. 하지만 이를 위해 매번 사용자가 서버에 요청을 보낼때마다 DB에 접근해서 데이터를 가져오는 것은 그 자체로 비효율적이다. 따라서 한번 인증된 사용자 정보를 세션에 담아놓고 세션이 유지되는 동안 유저객체를 DB접근 없이 가져다 쓰는 것을 생각 해볼 수있다.
[Spring-Security] @AuthenticationPrincipal에 대하여
https://dev-gyus.github.io/spring/2021/03/09/Spring-ConfigurationProperties0.html
[Spring-Security] @AuthenticationPrincipal에 대하여
kingsubin
로그인한 사용자의 정보를 파라미터로 받고 싶을때 Principal 아래와 같이 객체로 받아서 사용할 수 있다. Principal 객체는 java.security 패키지에 속해있는 인터페이스이며getName() 을 통하여 name 정보를 참조할 수 있다.
kingsubin
https://kingsubin.tistory.com/367
kingsubin
Spring Security - @AuthenticationPrincipal
로그인한 사용자의 정보를 파라메터로 받고 싶을때 기존에는 다음과 같이 Principal 객체로 받아서 사용한다. 하지만 이 객체는 SecurityContextHolder의 Principal과는 다른 객체이다. 이 객체는 JAVA 표준 Principal 객체이며 우리가 참조할수 있는 정보는 name 정보 밖에 없다. @AuthenticationPrincipal 애노테이션을 사용하면 UserDetailsService에서 Return한 객체 를 파라메터로 직접 받아 사용할 수 있다.
Spring Security - @AuthenticationPrincipal
https://ncucu.me/137
Spring Security - @AuthenticationPrincipal
kyu9341.github.io
https://kyu9341.github.io/java/2020/04/30/java_springBootLogin/
현재 인증된 사용자 정보 참조
principal : Allows direct access to the principal object representing the current user Authentication라는 객체가 들어가면 들어간 순간부터 세션에 값이 저장된다. principal는 Authentication라는 객체를 가져오는 것이다. 사용자가 로그인 요청을 하게 되면 AuthenticationFilter를 거치게 된다. 로그인 요청이 오면 이 필터가 제일 먼저 작동한다. 그래서 로그인 요청을 할때는 httpbody에 무엇을 담고 올까?
현재 인증된 사용자 정보 참조
https://velog.io/@leyuri/%ED%98%84%EC%9E%AC-%EC%9D%B8%EC%A6%9D%EB%90%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%A0%95%EB%B3%B4-%EC%B0%B8%EC%A1%B0
현재 인증된 사용자 정보 참조
SpEL
SpEL (스프링 Expression Language)
SpEL (스프링 Expression Language)
https://blossun.github.io/spring/core-technology/04_SpEL/
SpEL (스프링 Expression Language)
06. Controller와 View에서의 Spring Security
이번 포스트에서는 Controller와 View에서 Spring Security 사용에 대해 알아보자. UserDetails 활용 Controller의 handler method에서 UserDetails를 파라미터로 받아서 활용할 수 있다. 이때 파라미터에는 @AuthenticationPrincipal 이라는 애너테이션이 선언되어 있어야 한다. @GetMapping("/admin") public String getAdmin(Model model, @AuthenticationPrincipal UserDetails user) { if (user != null) { log.trace("user: {}", user.getUsername()); log.trace("user: {}", user.getAuthorities()); } return "admin"; } SecurityConfig WebSecurityConfigurerAdapter 클래스에서 HttpSecurity 를 통해 중앙 집중적으로 경로에 대한 접근을 제어할 수 있었는데 여기서는 개별적인 처리가 쉽지 않다.
06. Controller와 View에서의 Spring Security
https://goodteacher.tistory.com/273
06. Controller와 View에서의 Spring Security