Як отримати активних користувачів UserDetails


170

У своїх контролерах, коли мені потрібен активний (увійшов) користувач, я виконую наступні дії, щоб отримати свою UserDetailsреалізацію:

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

Це прекрасно працює, але я думаю, що Весна може полегшити життя у такому випадку. Чи є спосіб UserDetailsвключити автоматичне з'єднання або в контролер, або в метод?

Наприклад, щось на кшталт:

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

Але замість того, щоб отримати UsernamePasswordAuthenticationToken, я отримую UserDetailsзамість цього?

Я шукаю елегантне рішення. Будь-які ідеї?

Відповіді:


226

Преамбула: З Spring-Security 3.2 @AuthenticationPrincipalв кінці цієї відповіді є приємна примітка . Це найкращий шлях для використання Spring-Security> = 3.2.

Коли ти:

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

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

(BTW: Моя відповідь трохи старша (січень 2012 р.), Тому саме Лукаш Шмельцейзен виступив першим із @AuthenticationPrincipalбазою рішень для анотації на Spring Security 3.2.)


Потім ви можете використовувати у своєму контролері

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

Це нормально, якщо вам це потрібно один раз. Але якщо вам це потрібно в кілька разів негарно, оскільки воно забруднює ваш контролер деталями інфраструктури, це, як правило, повинно бути приховано рамкою.

Тож, що ви можете дуже хотіти, це мати такий контролер:

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

Тому вам потрібно лише впровадити a 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>

@See: Дізнайтеся, як налаштувати аргументи методу Spring MVC @Controller

Слід зазначити, що якщо ви використовуєте Spring 3.1, вони рекомендують HandlerMethodArgumentResolver через WebArgumentResolver. - дивіться коментар Джея


Те саме з 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>

@See Використання інтерфейсу Spring MVC 3.1 HandlerMethodArgumentResolver


Весна-Безпека 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 Spring 3.1 вище.

@ Погляньте на Spring 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 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


Крім того, просто створіть боб, який має метод getUserDetails () та @Autowire, що у вашому контролері.
sourcedelica

Реалізуючи це рішення, я встановив точку перерви у верхній частині резолюціїArgument (), але мій додаток ніколи не вступає у вирішальник веб-аргументів. Ваша весняна конфігурація в контексті сервлета, а не кореневий контекст, правда?
Джей

@Jay: Ця конфігурація є частиною кореневого контексту, а не контекстного сервлету. - Це здається, що я забув вказати ідентифікатор (id="applicationConversionService")у прикладі
Ральф

11
Слід зазначити, що якщо ви використовуєте Spring 3.1, вони рекомендують HandlerMethodArgumentResolver через WebArgumentResolver. Я змусив HandlerMethodArgumentResolver працювати, налаштовуючи його з <annotation-driven> в сервлет-контексті. Крім цього, я реалізував відповідь, як розміщено тут, і все працює чудово
Jay

@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
для тих, хто не любить читати надані посилання, це потрібно ввімкнути @EnableWebMvcSecurityабо в XML, або в ньому:<mvc:annotation-driven> <mvc:argument-resolvers> <bean class="org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver" /> </mvc:argument-resolvers> </mvc:annotation-driven>
sodik

Як перевірити, чи customUserнуль чи ні?
Саджад

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. Потім введіть це в контролери, яким потрібен доступ до інформації про безпеку або інформації про поточного користувача.

Інша основна перевага полягає в тому, що легко зробити прості реалізації з фіксованими даними для тестування, не турбуючись про заселення локальних потоків тощо.


Я розглядав цей підхід, але не був впевнений: а) як саме це зробити правильно? (Tm) та б) якщо виникнуть якісь проблеми з ниткою. Ви впевнені, що там не було б проблем? Я збираюся з розміщеним вище способом анотації, але думаю, що це все-таки гідний спосіб вирішити цю проблему. Дякуємо за публікацію :)
Повітряний ведмідь

4
Технічно те саме, що отримати доступ до SecurityContextHolder безпосередньо з вашого контролера, тому не повинно виникнути жодних проблем з нанизуванням. Він просто зберігає виклик в одному місці і дозволяє легко вводити альтернативи для тестування. Ви також можете повторно використовувати той самий підхід у інших не веб-класах, яким потрібен доступ до інформації про безпеку.
Шон овець

Отримав ... ось що я думав. До цього я добре повернусь, якщо у мене виникнуть проблеми з тестуванням одиниць методом анотацій або якщо я хочу зменшити зв'язок із Spring.
Повітряний ведмідь

@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>в свій файл конфігурації програми.
Ерік

Краще отримати авторизованого користувача в шаблоні через spring-security-taglibs: stackoverflow.com/a/44373331/548473
Григорій Кіслін

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

Ось відповідні Javadocs AuthenticationPrincipal та AuthenticationPrincipalArgumentResolver


1
@nbro Коли я додав рішення, яке стосується конкретної версії, жодне з інших рішень не було оновлено, щоб врахувати це рішення
geoand

Автентифікація
Основний

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, ми можемо отримати його дані в методі контролера. Нижче наводиться приклад, передаючи об’єкт аутентифікації в методі контролера разом з аргументом. Після того, як користувач отримав автентифікацію, дані заповнюються в об’єкт аутентифікації.

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

Будь-ласка, уточнюйте свою відповідь.
Микола Шевченко

Я відредагував свою відповідь, ми могли використовувати об’єкт аутентифікації, який містить дані про користувача, який вже перевірив автентифікацію.
Мірза Шуджатулла
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.