Как получить UserDetails активного пользователя


в моих контроллерах, когда мне нужен активный (зарегистрированный) пользователь, я делаю следующее, чтобы получить мой UserDetails реализация:

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

он отлично работает, но я думаю, что весна может облегчить жизнь в таком случае. Есть ли способ иметь UserDetails autowired в контроллер или метод?

например, что-то вроде:

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

но вместо того, чтобы получать UsernamePasswordAuthenticationToken, Я UserDetails вместо?

Я смотрю для элегантного решения. Есть идеи?

7 145

7 ответов:

преамбула: С весны-безопасность 3.2 есть хорошая аннотация @AuthenticationPrincipal описано в конце этого ответа. Это лучший способ пойти, когда вы используете Spring-Security >= 3.2.

если вы:

  • используйте более старую версию Spring-Security,
  • необходимо загрузить пользовательский объект пользователя из базы данных по некоторой информации (например, логин или id), хранящейся в основном или
  • хотите узнать, как HandlerMethodArgumentResolver или WebArgumentResolver может решить эту проблему элегантным способом, или просто хотите узнать фон позади @AuthenticationPrincipal и AuthenticationPrincipalArgumentResolver (потому что он основан на HandlerMethodArgumentResolver)

тогда продолжайте читать-иначе просто используйте @AuthenticationPrincipal и спасибо Робу Винчу (автор @AuthenticationPrincipal) и Lukas Schmelzeisen (для его ответа).

(кстати: мой ответ немного старше (январь 2012), так что это было Lukas Schmelzeisen которые приходят как первый с @AuthenticationPrincipal основание решения аннотации на безопасности весны 3.2.)


тогда вы можете использовать в вашем контроллере

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 если он чувствует себя ответственным за аргумент метода (первый параметр).

с весны 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>

@см.: научитесь настраивать аргументы метода Spring MVC @Controller

следует отметить, что если вы используете Spring 3.1, они рекомендуют HandlerMethodArgumentResolver над WebArgumentResolver. - см. комментарий Джей


то же самое с HandlerMethodArgumentResolver для весны 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>

@See использование пружины MVC 3.1 HandlerMethodArgumentResolver интерфейс


Spring-Security 3.2 Solution

Spring Security 3.2 (не путайте с Spring 3.2) имеет собственное встроенное решение: @AuthenticationPrincipal (org.springframework.security.web.bind.annotation.AuthenticationPrincipal). Это хорошо описано в Лукас Schmelzeisen это

это просто писать

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

чтобы получить эту работу, вам нужно зарегистрировать AuthenticationPrincipalArgumentResolver (org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver) : либо "активация" @EnableWebMvcSecurity или зарегистрировав этот боб внутри mvc:argument-resolvers - так же, как я описал его с решением may Spring 3.1 выше.

@See Пружинная Защита 3.2 Ссылка, Глава 11.2. @AuthenticationPrincipal


Spring-Security 4.0 Solution

он работает как решение Spring 3.2, но весной 4.0 @AuthenticationPrincipal и AuthenticationPrincipalArgumentResolver был "перемещен" в другой пакет:

(но старые классы в своих старых упаковочных комплектах все еще существует, поэтому не смешивайте их!)

это просто пишу

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

чтобы получить эту работу вам нужно зарегистрировать (org.springframework.security.web.method.annotation.)AuthenticationPrincipalArgumentResolver : либо "активация" @EnableWebMvcSecurity или зарегистрировав этот боб внутри mvc:argument-resolvers - так же, как я описал его с решением may Spring 3.1 выше.

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

@See Spring Security 5.0 Reference, Глава 39.3 @AuthenticationPrincipal

пока Ральф Отвечает обеспечивает элегантное решение, с Spring Security 3.2 вам больше не нужно реализовывать свой собственный ArgumentResolver.

если у вас UserDetails реализация CustomUser, вы можете просто сделать это:

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

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

посмотреть Spring Security Documentation: @AuthenticationPrincipal

Spring Security предназначена для работы с другими не пружинными фреймворками, поэтому она не тесно интегрирована с 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";
   }
}

пользователь должен быть назначен из authentication.getPrincipal()

здесь представлены соответствующие javadocs из AuthenticationPrincipal и AuthenticationPrincipalArgumentResolver

@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>