문제 상황
일반적으로 Spring Security를 사용하면 현재 요청 유저의 로그인 정보를 SecurityContext에 추가합니다. 그리고 유저 정보가 필요할 때마다 SecurityContext에 접근해서 유저 정보를 가져옵니다.
유저 정보는 Authentication이라는 interface를 구현한 객체를 저장해 놓을 수 있습니다.
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
Spring Security는 기본적으로 Authentication을 구현한 많은 객체들을 기본으로 제공해 줍니다. OAuth 관련, 테스트용 Dummy 객체, 간단하게 아이디만 있는 객체 등...
하지만 만약 여기에 내가 원하는 정보를 저장할 수 없다면 어떨까요? 내가 원하는 정보를 Authentication에 저장하고 싶으면 어떻게 해야 할까요?
해결책
해결책은 간단합니다. Spring Security에는 Authentication interface만 따르면 어떤 객체도 저장할 수 있습니다. 있어야 하는 정보만 있으면 되지, 추가적인 정보를 넣으면 안 되는 건 아니라는 겁니다. 즉, 내가 원하는 방식대로 Authentication 객체를 구현해서 저장하기만 하면 됩니다.
여기서 말씀 드렸듯, Spring Security에서는 이미 많은 Authentication 객체를 기본 제공하고 있기 때문에 내가 원하는 사용 방식의 Authentication 객체가 이미 있을 확률이 높습니다. 그래서 잘 찾아보시고, 만약 없다고 해도 내 사용 방식과 최대한 일치하는 Authentication 객체를 찾은 뒤 상속하는 게 좋습니다.
예를 들어 OAuth 관련 Token이 필요한데 여기에 추가적으로 정보 몇 가지만 추가하고 싶다면 처음부터 Authentication 객체를 새로 만드는 것 보다 OAuth2AuthenticationToken를 상속받고 추가적으로 필드를 더 넣는 게 좋습니다.
예시
저는 Authentication에 기본적인 정보 (principal, credentials, authorities)는 기본으로 추가하되, 유저의 PK를 추가적으로 저장하고 싶었습니다. 그래서 UsernamePasswordAuthenticationToken이라는 기본 제공 객체를 상속하고, 거기에 추가적으로 PK만 추가하기로 했습니다.
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 570L;
private final Object principal;
private Object credentials;
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
public static UsernamePasswordAuthenticationToken unauthenticated(Object principal, Object credentials) {
return new UsernamePasswordAuthenticationToken(principal, credentials);
}
public static UsernamePasswordAuthenticationToken authenticated(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
return new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
}
public Object getCredentials() {
return this.credentials;
}
public Object getPrincipal() {
return this.principal;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {}
public void eraseCredentials() {}
}
요 객체를 상속한 객체를 만들고, 거기에 PK만 추가해 주면 됩니다.
class AppUserAuthenticationToken(
principal: Any,
credentials: Any?,
authorities: Collection<GrantedAuthority>,
id: Long,
) : UsernamePasswordAuthenticationToken(
principal,
credentials,
authorities,
) {
var appUserId: Long? = id
private set
}
이 객체를 Filter 등에서 아래와 같이 SecurityContext에 저장하면 됩니다.
val authentication = AppUserAuthenticationToken(
id = appUser.id.toLong(),
principal = appUser.email,
credentials = null,
authorities = appUser.authorities,
)
SecurityContextHolder.getContext().authentication = authentication
꺼내 쓸 때도 Casting만 해 주면 됩니다.
SecurityContextHolder.getContext().authentication as? AppUserAuthenticationToken
'👨💻 프로그래밍 > 📦 Backend' 카테고리의 다른 글
전략 패턴 (Strategy Pattern) 사용하기 (0) | 2023.05.28 |
---|---|
특정 거리 범위 내 위도/경도 계산하기 (의외로 어려움) (0) | 2023.05.27 |
🔥 Hot Ranking 알고리즘, Spring Batch로 구현하기 (feat. Strategy 패턴) (0) | 2023.05.18 |
AntMatcher 문법 알아보기 (Ant Path Pattern) (0) | 2023.05.16 |
S3, AWS SDK 사용해서 Spring Boot로 이미지 업로드 기능 구현하기 (0) | 2023.05.11 |