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

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

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

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

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

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

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

Я ищу элегантное решение. Любые идеи?

Ответ 1

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

Когда вы:

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

затем продолжайте читать - иначе просто используйте @AuthenticationPrincipal и поблагодарите Роба Винча (автора @AuthenticationPrincipal) и Лукаса Шмельцайзена (за его ответ).

(Кстати: мой ответ немного старше (январь 2012 г.), поэтому Лукас Шмельцайзен первым предложил решение для аннотаций @AuthenticationPrincipal в Spring Security 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;
        }
   }
}

Вам необходимо определить пользовательскую аннотацию. Вы можете пропустить ее, если каждый экземпляр User всегда должен быть взят из контекста безопасности, но никогда не является объектом команды.

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

@См. Использование интерфейса Spring MVC 3.1 HandlerMethodArgumentResolver


Решение Spring-Security 3.2

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

Это просто пишет

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

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

@См. Spring Security 3.2. Ссылка, глава 11.2. @AuthenticationPrincipal


Решение Spring-Security 4.0

Он работает как решение Spring 3.2, но в Spring 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 - так же, как я описал это с майским решением 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, глава 39.3 @AuthenticationPrincipal

Ответ 2

В то время как 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...
}

См. Spring Документация по безопасности: @AuthenticationPrincipal

Ответ 3

Spring Безопасность предназначена для работы с другими фреймворками Spring, поэтому она не тесно интегрирована с Spring MVC. Spring Безопасность возвращает объект Authentication из метода HttpServletRequest.getUserPrincipal() по умолчанию, так что вы получаете в качестве принципала. Вы можете получить свой объект UserDetails непосредственно из этого, используя

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

Обратите внимание также, что типы объектов могут различаться в зависимости от используемого механизма аутентификации (например, вы можете не получить UsernamePasswordAuthenticationToken), а Authentication не обязательно должен содержать UserDetails. Это может быть строка или любой другой тип.

Если вы не хотите напрямую звонить SecurityContextHolder, самый изящный подход (который я последую за ним) заключается в том, чтобы внедрить свой собственный интерфейс доступа к контексту безопасности, который настроен в соответствии с вашими потребностями и типами пользовательских объектов. Создайте интерфейс с помощью соответствующих методов, например:

interface MySecurityAccessor {

    MyUserDetails getCurrentUser();

    // Other methods
}

Затем вы можете реализовать это, обратившись к SecurityContextHolder в стандартной реализации, тем самым полностью отключив ваш код от Spring Security. Затем введите это в контроллеры, которым необходим доступ к информации о безопасности или информации о текущем пользователе.

Другим основным преимуществом является то, что легко выполнить простые реализации с фиксированными данными для тестирования, не беспокоясь о заполнении нитей-локалей и т.д.

Ответ 4

Внедрите интерфейс 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());
        }
}

Ответ 5

Начиная с версии 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 of AuthenticationPrincipal и AuthenticationPrincipalArgumentResolver

Ответ 6

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

Ответ 7

И если вам нужен авторизованный пользователь в шаблонах (например, 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>

Ответ 8

Вы можете попробовать это: Используя объект аутентификации из Spring, мы можем получить от него данные о пользователе в методе контроллера. Ниже приведен пример, передавая объект Authentication в методе контроллера вместе с аргументом. Когда пользователь проходит аутентификацию, детали заполняются в объекте Authentication.

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