티스토리 뷰

반응형

개발을 하다 보니 Security 인증 후에 로그인 한 객체의 정보가 필요한 경우가 발생했습니다.

SpringBoot에서 Security 인증 후에 로그인 한 객체의 정보를 가져오고 싶다면 어떻게 해야 할까요??

 

1. SecurityContextHolder 로 직접 가져오기

인증 후 로그인한 객체의 정보를 가져오기 위해서는 객체의 정보가 어디에 저장되는지를 알아야 한다.

 

아래 그림을 보면 Security의 객체 정보는 SecurityContextHolder 내부에 들어 있다.

그러면 SecurityContextHolder 구조를 한번 살펴보자.

SecurityContextHolder 는 시큐리티가 인증한 내용들을 가지고 있으며, SecurityContext 를 포함하고 있고 SecurityContext를 현재 스레드와 연결해 주는 역할을 한다.

여기서 Principal 객체가 인증된 사용자 정보를 저장하는 객체를 의미한다.

 

 

SecurityContextHolder를 통해 확인하는 방법

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrinicipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
Object credentials = authentication.getcredentials();
boolean authenticated = authentication.isAuthenticated();

시큐리티는 기본적으로 사용자 정보를 쓰레드 단위로 관리하기 때문에 같은 thread의 application 내에서 어디서든 SecurityContextHolder의 인증 정보를 확인 가능하도록 구현되어 있다.

 

2. Bean 을 이용한 방법

SecurityContextHolder를 이용해서 정보를 가져오는 방법을 활용하면 직접 가져오는 것이 아닌 빈으로 등록해서 사용할 수 있다.

 

AuthenticationFacade 인터페이스

public interface AuthenticationFacade {
    Authentication getAuthentication();
}

 

AuthenticationFacacadeImpl 구현체

@Component
public class AuthenticationFacadeImpl implements AuthenticationFacade {
    @Override
    public Authentication getAuthentication(){
        return SecurityContextHolder.getContext().getAuthentication();
    }
}

 

생성자 주입을 이용하여 사용

@RequiredArgsConstructor
public class SecurityController {
    private final AuthenticationFacade auth;

    ...
}

 

3. Controller에서 사용자 정보 얻기

스프링에서는 좀 더 간단하게 인증된 사용자 정보를 가져오는 방법을 제공한다.

어느곳에서든 사용할 수 있는 방법은 아니고 컨트롤러에서만 사용할 수 있는 방법이다.

 

@Controller 선언된 bean 객체에서는 메서드 인자로 Principal 객체에 직접 접근할 수 있는 추가적인 옵션이 존재한다.

@Controller
public class SecurityController {

    @GetMapping("/username")
    @ResponseBody
    public String securityUserName(Principal principal) {
        return principal.getName();
    }
}

컨트롤러에서는 Prinicipal을 사용하면 별다른 옵션없이 Prinicipal의 값을 가져와 주입준다.

 

스프링이 자동으로 무언갈 처리해주는거 같다. 그러면 스프링은 어떻게 Principal 값을 가져오는 걸까??

Spring Controller Principal injecting

그 방법은 ArgumentResolver를 이용해서 내부적으로 가져오게 된다.

Principal의 값은 ServletRequestMethodArgumentResolver 를 통해 Prinicipal 값을 주입하고 있다.

public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
        Class<?> paramType = parameter.getParameterType();
        return (Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()		
	}

	@Nullable
	private Object resolveArgument(Class<?> paramType, HttpServletRequest request) throws IOException {
        // { ... }
        else if (Principal.class.isAssignableFrom(paramType)) {
        Principal userPrincipal = request.getUserPrincipal();
        if (userPrincipal != null && !paramType.isInstance(userPrincipal)) {
            throw new IllegalStateException(
                "Current user principal is not of type [" + paramType.getName() + "]: " + userPrincipal);
            }
            return userPrincipal;
        }
        // ...
}

ServletRequestMethodArgumentResolver 를 살펴보면 Principal userPrincipal = request.getUserPrinicipal();

를 통해 Principal 객체의 값을 가져오는걸 확인할 수 있다.

 

스프링은 Principal 객체 뿐만 아니라 authentication 객체도 자동 주입을 지원해주고 있다.

@Controller
public class SecurityController {

    @GetMapping("/username")
    @ResponseBody
    public String securityUserName(Authentication authentication) {
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();

        return userDetails.getUsername();
    }
}

 

4. @AuthenticationPrincipal

그런데 위의 코드를 잘 살펴보시면 아시겠지만 Principal 객체의 경우 SecurityContextHolder에서 값을 가져오는게 아닌 HttpServletRequest 에서 추출하는 것을 확인 할 수 있다.

 

Controller에서 사용하는 Principal객체의 경우 HttpServletRequest 에서 값으로 사용하는 자바 표준 Principal 객체이기 때문에 Authentication안의 Principal객체와 달리 name정도밖에 불러오지 못하는 한계가 존재한다.

 

Spring Security 3.2 부터는 @AuthenticationPrincipal 어노테이션을 지원해주기 시작했다.

@AuthenticationPrincipal 도 인증된 사용자 정보를 읽어오지만 3번에서 보여준 Principal의 객체를 읽어오는 경우와는 다르게 SecurityContextHolder에서 직접 값을 꺼내오기 때문에 좀 더 많은 사용자의 정보를 사용할 수 있다.

 

AuthenticationPrincipalArgumentResolver를 살펴보면 SecurityContextHolder 에서 값을 꺼내오는 것을 확인 할 수 있다.

public final class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return findMethodAnnotation(AuthenticationPrincipal.class, parameter) != null;
	}

	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
        	// here
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		
		{ ... }
		Object principal = authentication.getPrincipal();
		AuthenticationPrincipal annotation = findMethodAnnotation(AuthenticationPrincipal.class, parameter);
		String expressionToParse = annotation.expression();
		if (StringUtils.hasLength(expressionToParse)) {
			StandardEvaluationContext context = new StandardEvaluationContext();
			context.setRootObject(principal);
			context.setVariable("this", principal);
			context.setBeanResolver(this.beanResolver);
			Expression expression = this.parser.parseExpression(expressionToParse);
			principal = expression.getValue(context);
		}
		{ ... }
		return principal;
	}

@AuthenticationPrincipal 어노테이션을 사용할 경우 UserDetailsService에서 반환된 객체를 매개변수로 직접 받아서 사용할 수 있다.

 

Principal 객체는 사용자 이름/비밀번호로 인증하는 경우 보통 UserDetails 인스턴스 입니다.

 

이러한 UserDetails는 UserDetailsService가 반환하고 있다.

즉, Authentication 내에 있는 principal객체는 UserDetails 인스턴스 이고

UserDetails는 UserDetailsService가 반환하고 있는 구조다.

 

그래서 커스텀사용자 객체를 반환하고 싶다면 UserDetailsService 구현체 반환값을 변경해주어 사용 가능하다.

커스텀사용자 객체 반환 방법을 다룬 다른 블로그 글이 많이 있으니 참고하시면 될것 같다.

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함