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

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

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

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

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

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

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

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



답변

서문 : 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 참조


답변

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을 참조하십시오 .


답변

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에서 코드를 완전히 분리 할 수 ​​있습니다. 그런 다음 보안 정보 또는 현재 사용자의 정보에 액세스해야하는 컨트롤러에이를 삽입하십시오.

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


답변

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());
        }
}


답변

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입니다.


답변

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


답변

그리고 템플릿 (예 : 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>