활동적인 사용자의 UserDetails를 얻는 방법


170

내 컨트롤러에서 활성 (로그인 된) 사용자가 필요할 때 UserDetails구현 을 위해 다음을 수행하고 있습니다 .

User activeUser = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.debug(activeUser.getSomeCustomField());

잘 작동하지만 스프링이 이런 경우 인생을 더 쉽게 만들 수 있다고 생각합니다. (가)이 할 수있는 방법이 있나요 UserDetails컨트롤러 나 방법 중 하나에를 autowire는?

예를 들면 다음과 같습니다.

public ModelAndView someRequestHandler(Principal principal) { ... }

그러나 대신을 얻는 UsernamePasswordAuthenticationToken, 내가 얻을 UserDetails대신을?

우아한 솔루션을 찾고 있습니다. 어떤 아이디어?

답변:


226

서문 : Spring-Security 3.2 이후 @AuthenticationPrincipal로이 답변의 끝에 멋진 주석 이 있습니다. 이것은 Spring-Security> = 3.2를 사용할 때 가장 좋은 방법입니다.

때를:

  • 이전 버전의 Spring-Security를 ​​사용하십시오.
  • 주체에 저장된 일부 정보 (예 : 로그인 또는 ID)로 데이터베이스에서 사용자 정의 사용자 개체를로드해야합니다.
  • 방법을 배우고 싶어 HandlerMethodArgumentResolver하거나 WebArgumentResolver우아한 방법으로이 문제를 해결하거나 뒤에 배경을 배울 수있는 단지 원하는 수 @AuthenticationPrincipalAuthenticationPrincipalArgumentResolver(그것이 기반으로하기 때문에 HandlerMethodArgumentResolver)

그런 다음 계속 읽으십시오. 그렇지 않으면 @AuthenticationPrincipalRob Winch (저자 @AuthenticationPrincipal)와 Lukas Schmelzeisen (자신의 답변) 에게 감사의 말씀을 전 합니다.

(BTW : 제 답변은 조금 더 오래되었으므로 (2012 년 1 월) Spring Security 3.2에 주석 솔루션 기반을 가진 최초의 사람으로 등장한 것은 Lukas Schmelzeisen 이었습니다 @AuthenticationPrincipal.)


그런 다음 컨트롤러에서 사용할 수 있습니다

public ModelAndView someRequestHandler(Principal principal) {
   User activeUser = (User) ((Authentication) principal).getPrincipal();
   ...
}

한 번 필요하면 괜찮습니다. 그러나 인프라 세부 정보로 컨트롤러를 오염시키기 때문에 추악한 일이 여러 번 필요한 경우 일반적으로 프레임 워크에 의해 숨겨져 야합니다.

따라서 실제로 원하는 것은 다음과 같은 컨트롤러를 갖는 것입니다.

public ModelAndView someRequestHandler(@ActiveUser User activeUser) {
   ...
}

따라서을 구현하기 만하면 WebArgumentResolver됩니다. 방법이 있습니다

Object resolveArgument(MethodParameter methodParameter,
                   NativeWebRequest webRequest)
                   throws Exception

웹 요청 (두 번째 매개 변수)을 가져 와서 User메소드 인수 (첫 번째 매개 변수)에 대한 책임이 있다고 생각되면 if 를 반환해야합니다 .

Spring 3.1부터라는 새로운 개념이 HandlerMethodArgumentResolver있습니다. Spring 3.1 이상을 사용한다면 그것을 사용해야한다. (이 답변의 다음 섹션에 설명되어 있습니다)

public class CurrentUserWebArgumentResolver implements WebArgumentResolver{

   Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) {
        if(methodParameter is for type User && methodParameter is annotated with @ActiveUser) {
           Principal principal = webRequest.getUserPrincipal();
           return (User) ((Authentication) principal).getPrincipal();
        } else {
           return WebArgumentResolver.UNRESOLVED;
        }
   }
}

사용자 정의 어노테이션을 정의해야합니다. 모든 사용자 인스턴스가 항상 보안 컨텍스트에서 가져와야하지만 명령 오브젝트가 아닌 경우이를 무시할 수 있습니다.

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ActiveUser {}

구성에서는 다음을 추가하기 만하면됩니다.

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"
    id="applicationConversionService">
    <property name="customArgumentResolver">
        <bean class="CurrentUserWebArgumentResolver"/>
    </property>
</bean>

@See : Spring MVC @Controller 메소드 인자를 커스터마이징하는 법 배우기

Spring 3.1을 사용하는 경우 WebArgumentResolver보다 HandlerMethodArgumentResolver를 권장합니다. -Jay의 코멘트보기


HandlerMethodArgumentResolverSpring 3.1 이상 과 동일

public class CurrentUserHandlerMethodArgumentResolver
                               implements HandlerMethodArgumentResolver {

     @Override
     public boolean supportsParameter(MethodParameter methodParameter) {
          return
              methodParameter.getParameterAnnotation(ActiveUser.class) != null
              && methodParameter.getParameterType().equals(User.class);
     }

     @Override
     public Object resolveArgument(MethodParameter methodParameter,
                         ModelAndViewContainer mavContainer,
                         NativeWebRequest webRequest,
                         WebDataBinderFactory binderFactory) throws Exception {

          if (this.supportsParameter(methodParameter)) {
              Principal principal = webRequest.getUserPrincipal();
              return (User) ((Authentication) principal).getPrincipal();
          } else {
              return WebArgumentResolver.UNRESOLVED;
          }
     }
}

구성에서 이것을 추가해야합니다

<mvc:annotation-driven>
      <mvc:argument-resolvers>
           <bean class="CurrentUserHandlerMethodArgumentResolver"/>         
      </mvc:argument-resolvers>
 </mvc:annotation-driven>

@Spring MVC 3.1 HandlerMethodArgumentResolver 인터페이스 활용


스프링 보안 3.2 솔루션

Spring Security 3.2 (Spring 3.2와 혼동하지 마십시오)에는 자체 솔루션이 내장되어 있습니다 : @AuthenticationPrincipal( org.springframework.security.web.bind.annotation.AuthenticationPrincipal). 이것은 Lukas Schmelzeisen의 답변 에 잘 설명되어 있습니다.

그냥 쓰고 있어요

ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) {
    ...
 }

이 작업을 수행하려면 "활성화" 하거나이 Bean을 등록하여 위의 Spring 3.1 솔루션에서 설명한 것과 같은 방식으로 AuthenticationPrincipalArgumentResolver( org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver) 를 등록해야합니다 .@EnableWebMvcSecuritymvc:argument-resolvers

@Spring Security 3.2 Reference, 11.2 장을 참조하십시오. Auth


스프링 시큐리티 4.0 솔루션

그것은 봄 3.2 솔루션처럼 작동하지만, 봄 4.0에서 @AuthenticationPrincipalAuthenticationPrincipalArgumentResolver타 패키지에 "이동"되었다 :

(그러나 이전 패키지에있는 이전 클래스는 여전히 존재하므로 혼합하지 마십시오!)

그냥 쓰고 있어요

import org.springframework.security.core.annotation.AuthenticationPrincipal;
ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) {
    ...
}

이 작업을 수행하려면 "활성화" 하거나이 Bean을 등록하여 위의 Spring 3.1 솔루션에서 설명한 것과 같은 방식으로 ( org.springframework.security.web.method.annotation.) 를 등록해야합니다 .AuthenticationPrincipalArgumentResolver@EnableWebMvcSecuritymvc:argument-resolvers

<mvc:annotation-driven>
    <mvc:argument-resolvers>
        <bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
    </mvc:argument-resolvers>
</mvc:annotation-driven>

@Spring Security 5.0 참조 서, 39.3 장 @AuthenticationPrincipal 참조


또는 getUserDetails () 메소드가있는 Bean을 작성하고 컨트롤러에 @Autowire를 작성하십시오.
sourcedelica

이 솔루션을 구현하면서 resolveArgument () 맨 위에 중단 점을 설정했지만 내 앱은 절대 웹 인수 확인 자로 들어 가지 않습니다. 서블릿 컨텍스트의 스프링 구성은 루트 컨텍스트가 아닙니다.
Jay

@Jay :이 구성은 서블릿 컨텍스트가 아닌 루트 컨텍스트의 일부입니다. - (id="applicationConversionService")예제 에서 ID를 지정하는 것을 잊어 버린 이음새
Ralph

11
Spring 3.1을 사용하는 경우 WebArgumentResolver보다 HandlerMethodArgumentResolver를 권장합니다. 서블릿 컨텍스트에서 <annotation-driven>으로 구성하여 HandlerMethodArgumentResolver가 작동하도록했습니다. 그 외에는 여기에 게시, 나는 대답을 구현하고 모든 것이 잘 작동
제이

@sourcedelica 왜 정적 메소드를 작성하지 않습니까?
Alex78191

66

Ralphs Answer 가 우아한 솔루션을 제공하는 반면 Spring Security 3.2를 사용하면 더 이상 자체적으로 구현할 필요가 없습니다 ArgumentResolver.

UserDetails구현 이있는 경우 다음을 수행 CustomUser할 수 있습니다.

@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) {

    // .. find messages for this User and return them...
}

스프링 보안 문서 : @AuthenticationPrincipal을 참조하십시오 .


2
제공된 링크를 읽고 싶지 않은 사람들을 위해, @EnableWebMvcSecurityXML 로 또는 XML 로 활성화해야 합니다.<mvc:annotation-driven> <mvc:argument-resolvers> <bean class="org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver" /> </mvc:argument-resolvers> </mvc:annotation-driven>
sodik

customUser이 null인지 확인하는 방법 은 무엇입니까?
Sajad

if (customUser != null) { ... }
T3rm1

27

Spring Security는 Spring 이외의 다른 프레임 워크와 작동하도록 설계되었으므로 Spring MVC와 긴밀하게 통합되지 않습니다. Spring Security는 기본적으로 메소드 에서 Authentication객체를 반환 HttpServletRequest.getUserPrincipal()하므로 주체로 얻습니다. UserDetails다음을 사용하여 직접 객체 를 얻을 수 있습니다.

UserDetails ud = ((Authentication)principal).getPrincipal()

또한 객체 유형은 사용 된 인증 메커니즘 ( UsernamePasswordAuthenticationToken예 :을 얻지 못할 수 있음)에 따라 다를 수 Authentication있으며을 엄격하게 포함 할 필요는 없습니다 UserDetails. 문자열 또는 다른 유형일 수 있습니다.

SecurityContextHolder직접 호출하지 않으려는 경우 가장 우아한 방법 (필요한 방법)은 필요와 사용자 개체 유형에 맞게 사용자 지정된 사용자 지정 보안 컨텍스트 접근 자 인터페이스를 주입하는 것입니다. 관련 메소드를 사용하여 인터페이스를 작성하십시오 (예 :

interface MySecurityAccessor {

    MyUserDetails getCurrentUser();

    // Other methods
}

그런 다음 SecurityContextHolder표준 구현에서 에 액세스하여이를 구현하여 Spring Security에서 코드를 완전히 분리 할 수 ​​있습니다. 그런 다음 보안 정보 또는 현재 사용자의 정보에 액세스해야하는 컨트롤러에이를 삽입하십시오.

다른 주요 이점은 스레드 로컬 채우기 등을 걱정할 필요없이 테스트 용 고정 데이터로 간단한 구현을 쉽게 수행 할 수 있다는 것입니다.


나는이 접근법을 고려하고 있었지만, a) 올바른 방법 (tm)을 정확하게 수행하는 방법과 b) 스레딩 문제가 있는지 확실하지 않았습니다. 문제가 없을 것이라고 확신하십니까? 위의 주석 방법을 사용하고 있지만 여전히 좋은 방법이라고 생각합니다. 게시 해 주셔서 감사합니다. :)
The Awnry Bear

4
컨트롤러에서 직접 SecurityContextHolder에 액세스하는 것과 기술적으로 동일하므로 스레딩 문제가 없어야합니다. 그것은 단지 한 곳으로 전화를 유지하고 테스트를위한 대안을 쉽게 주입 할 수있게합니다. 보안 정보에 액세스해야하는 웹이 아닌 다른 클래스에서도 동일한 접근 방식을 재사용 할 수 있습니다.
양 Shaun

알았어 ... 그게 내가 생각한 것입니다. 주석 방법으로 단위 테스트에 문제가 있거나 Spring과의 결합을 줄이려면 다시 방문하는 것이 좋습니다.
Awnry Bear

@LukeTaylor이 접근법을 더 자세히 설명해 주시겠습니까? 봄과 봄 보안에 익숙하지 않으므로 구현 방법을 잘 모릅니다. 액세스하려면 어디서 구현해야합니까? 내 UserServiceImpl에?
Cu7l4ss

9

HandlerInterceptor인터페이스를 구현 한 후 UserDetails다음과 같이 모델이있는 각 요청에 를 삽입하십시오 .

@Component 
public class UserInterceptor implements HandlerInterceptor {
    ....other methods not shown....
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        if(modelAndView != null){
            modelAndView.addObject("user", (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal());
        }
}

1
@atrain에게 감사합니다. 유용하고 우아합니다. 또한 <mvc:interceptors>응용 프로그램 구성 파일에을 추가해야 했습니다.
Eric

그것은 더 나은 통해 템플릿의 사용자 권한을 얻을됩니다 spring-security-taglibs: stackoverflow.com/a/44373331/548473
그리고 리 Kislin

9

Spring Security 버전 3.2부터는 이전 답변 중 일부에서 구현 된 사용자 정의 기능 @AuthenticationPrincipal이 기본적으로 주석 형식으로 제공 됩니다.AuthenticationPrincipalArgumentResolver .

사용의 간단한 예는 다음과 같습니다.

@Controller
public class MyController {
   @RequestMapping("/user/current/show")
   public String show(@AuthenticationPrincipal CustomUser customUser) {
        // do something with CustomUser
       return "view";
   }
}

CustomUser는 다음에서 할당 가능해야합니다. authentication.getPrincipal()

다음은 대응하는 AuthenticationPrincipalAuthenticationPrincipalArgumentResolver의 Javadoc입니다.


1
나는 버전 특정 솔루션을 추가 할 때 @nbro, 다른 해결책으로는 계정에이 솔루션을 취할 업데이트되지 있었다
geoand

AuthenticationPrincipalArgumentResolver는 이제 더 이상 사용되지 않습니다
Igor Donin

5
@Controller
public abstract class AbstractController {
    @ModelAttribute("loggedUser")
    public User getLoggedUser() {
        return (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
}

0

그리고 템플릿 (예 : JSP)에서 권한이있는 사용자가 필요한 경우

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<sec:authentication property="principal.yourCustomField"/>

함께

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-taglibs</artifactId>
        <version>${spring-security.version}</version>
    </dependency>

0

이것을 시도해 볼 수 있습니다 : Spring의 Authentication Object를 사용하면 컨트롤러 메소드에서 사용자 세부 정보를 얻을 수 있습니다. 아래는 인수와 함께 컨트롤러 메소드에 Authentication 객체를 전달하는 예입니다. 사용자가 인증되면 세부 정보가 Authentication 객체에 채워집니다.

@GetMapping(value = "/mappingEndPoint") <ReturnType> methodName(Authentication auth) {
   String userName = auth.getName(); 
   return <ReturnType>;
}

답을 정교하게 작성하십시오.
Nikolai Shevchenko

내 대답을 편집했습니다. 우리는 이미 인증 한 사용자의 사용자 세부 정보가있는 인증 개체를 사용할 수 있습니다.
Mirza Shujathullah
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.