Etkin kullanıcının Kullanıcı Ayrıntıları nasıl edinilir?


170

Denetleyicilerimde, aktif (oturum açmış) kullanıcıya ihtiyaç duyduğumda, UserDetailsuygulamamı almak için aşağıdakileri yapıyorum :

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

İyi çalışıyor, ama bence Bahar böyle bir durumda hayatı kolaylaştırabilir. Otomatik olarak UserDetailsdenetleyiciye veya yönteme bağlanmanın bir yolu var mı?

Örneğin, şöyle bir şey:

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

Ama bunun yerine alma UsernamePasswordAuthenticationToken, ben almak UserDetailsyerine?

Zarif bir çözüm arıyorum. Herhangi bir fikir?

Yanıtlar:


226

Önsöz: Spring-Security 3.2'den beri @AuthenticationPrincipalbu cevabın sonunda güzel bir açıklama var . Spring-Security> = 3.2 kullandığınızda gitmenin en iyi yolu budur.

Sen ne zaman:

  • Spring-Security'nin eski bir sürümünü kullanmak,
  • özel Kullanıcı Nesnenizi veritabanında depolanan bazı bilgilerle (oturum açma veya kimlik gibi) yüklemeniz gerekir veya
  • a HandlerMethodArgumentResolverya da WebArgumentResolverbunu zarif bir şekilde nasıl çözebileceğini öğrenmek ya da sadece arka planı öğrenmek @AuthenticationPrincipalve AuthenticationPrincipalArgumentResolver(a'ya dayalı olduğu için HandlerMethodArgumentResolver)

sonra okumaya devam edin - aksi takdirde @AuthenticationPrincipalRob Winch (Yazar @AuthenticationPrincipal) ve Lukas Schmelzeisen'a (cevabı için) kullanın ve teşekkür edin .

(BTW: Cevabım biraz daha eski (Ocak 2012), bu yüzden Spring Security 3.2'deki ek açıklama çözüm tabanına sahip olan ilk olarak Lukas Schmelzeisen oldu @AuthenticationPrincipal.)


Sonra kumandanızda kullanabilirsiniz

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

Bir kez ihtiyacınız varsa sorun değil. Ancak birkaç kez çirkin bir şekilde ihtiyacınız varsa, kontrol cihazınızı altyapı detayları ile kirletir, bu normalde çerçeve tarafından gizlenmelidir.

Yani gerçekten isteyebileceğiniz şey, böyle bir denetleyiciye sahip olmaktır:

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

Bu nedenle, yalnızca bir WebArgumentResolver. Bir yöntemi var

Object resolveArgument(MethodParameter methodParameter,
                   NativeWebRequest webRequest)
                   throws Exception

Bu, web isteğini (ikinci parametre) alır ve Useryöntem bağımsız değişkeninden (ilk parametre) sorumlu hissediyorsa döndürmelidir .

İlkbahar 3.1'den beri yeni bir kavram var HandlerMethodArgumentResolver. Spring 3.1+ kullanıyorsanız, kullanmalısınız. (Bu cevabın bir sonraki bölümünde açıklanmaktadır))

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

Özel Ek Açıklama tanımlamanız gerekir - Kullanıcı'nın her örneğinin her zaman güvenlik bağlamından alınması gerekiyorsa, ancak hiçbir zaman bir komut nesnesi değilse bunu atlayabilirsiniz.

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

Yapılandırmada yalnızca şunu eklemeniz gerekir:

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

@ Bkz: Spring MVC @Controller yöntemi bağımsız değişkenlerini özelleştirmeyi öğrenin

Spring 3.1 kullanıyorsanız, WebArgumentResolver üzerinden HandlerMethodArgumentResolver'ı önerdikleri belirtilmelidir. - Jay'in yorumuna bakın


HandlerMethodArgumentResolverBahar 3.1+ için de aynı

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

Yapılandırmada bunu eklemeniz gerekir

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

@ Spring MVC 3.1 HandlerMethodArgumentResolver arabirimini kullanma


Spring-Security 3.2 Çözümü

Spring Security 3.2 (Spring 3.2 ile karıştırmayın) kendi çözüm yoluna sahiptir: @AuthenticationPrincipal( org.springframework.security.web.bind.annotation.AuthenticationPrincipal). Bu Lukas Schmelzeisen'ın cevabında güzel bir şekilde tanımlandı

Sadece yazıyor

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

Bu çalışmayı elde etmek için AuthenticationPrincipalArgumentResolver( org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver): "etkinleştirerek" @EnableWebMvcSecurityveya bu fasulyeyi içine kaydederek kaydetmeniz gerekir mvc:argument-resolvers- yukarıda tarif ettiğim şekilde yukarıdaki Spring 3.1 çözümü ile.

@See Spring Security 3.2 Başvurusu, Bölüm 11.2. @AuthenticationPrincipal


Spring-Security 4.0 Çözümü

Bu Bahar 3.2 çözümü gibi çalışır, ancak Bahar 4.0 @AuthenticationPrincipalve AuthenticationPrincipalArgumentResolverbir diğer paketine "taşındı" oldu:

(Ama eski paketlerindeki eski sınıflar hala var, bu yüzden onları karıştırmayın!)

Sadece yazıyor

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

Bu çalışmayı elde etmek için ( org.springframework.security.web.method.annotation.) AuthenticationPrincipalArgumentResolver: "etkinleştirerek" @EnableWebMvcSecurityveya bu fasulyeyi içine kaydederek kaydetmeniz gerekir mvc:argument-resolvers- yukarıda tarif ettiğim şekilde yukarıdaki Spring 3.1 çözümü ile.

<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 Başvurusu, Bölüm 39.3 @AuthenticationPrincipal


Alternatif olarak, getUserDetails () yöntemine sahip bir fasulye oluşturun ve bunu denetleyicinize @Autowire yapın.
sourcedelica

Bu çözümü uygulayarak, resolArgument () üst kısmında bir kesme noktası ayarladım ama benim app asla web argüman çözümleyici adım. Servlet bağlamındaki yay yapılandırmanız kök bağlamda değil, değil mi?
Jay

@Jay: Bu yapılandırma, sunucu uygulaması bağlamının değil, kök bağlamın bir parçasıdır. - (id="applicationConversionService")örnekte kimliği belirtmeyi unuttuğum dikişler
Ralph

11
Spring 3.1 kullanıyorsanız, WebArgumentResolver üzerinden HandlerMethodArgumentResolver'ı önerdikleri belirtilmelidir. Servler bağlamında <annotation-driven> ile yapılandırarak HandlerMethodArgumentResolver'ı çalıştırdım. Bunun dışında, burada yayınlanan yanıtı uyguladım ve her şey harika çalışıyor
Jay

@sourcedelica Neden sadece statik yöntem oluşturmuyoruz?
Alex78191

66

Ralphs Answer zarif bir çözüm sunarken , Spring Security 3.2 ile artık kendi çözümünüzü uygulamanıza gerek yok ArgumentResolver.

Bir UserDetailsuygulamanız varsa CustomUser, bunu yapabilirsiniz:

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

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

Bkz. Bahar Güvenliği Belgeleri: @AuthenticationPrincipal


2
sağlanan bağlantıları okumak istemeyenler için, bu ya @EnableWebMvcSecurityXML tarafından ya da XML ile etkinleştirilmelidir :<mvc:annotation-driven> <mvc:argument-resolvers> <bean class="org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver" /> </mvc:argument-resolvers> </mvc:annotation-driven>
sodik

customUserBoş olup olmadığını nasıl kontrol edebilirim ?
Sajad

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

27

Spring Security, Spring dışındaki diğer çerçevelerle birlikte çalışacak şekilde tasarlanmıştır, bu nedenle Spring MVC ile sıkı bir şekilde bütünleştirilmez. Spring Security, Authenticationnesneyi HttpServletRequest.getUserPrincipal()varsayılan olarak yöntemden döndürür, böylece temel olarak aldığınız şey budur. UserDetailsNesnenizi kullanarak doğrudan bu nesneden elde edebilirsiniz.

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

Nesne türlerinin, kullanılan kimlik doğrulama mekanizmasına bağlı olarak değişebileceğini ( UsernamePasswordAuthenticationTokenörneğin, a alamayabilirsiniz ) ve Authenticationkesinlikle a içermesi gerekmediğini unutmayın UserDetails. Bir dize veya başka bir tür olabilir.

SecurityContextHolderDoğrudan aramak istemiyorsanız , en zarif yaklaşım (takip edeceğim), ihtiyaçlarınıza ve kullanıcı nesnesi türlerine uyacak şekilde özelleştirilmiş kendi özel güvenlik bağlamı erişim arabirimini enjekte etmektir. İlgili yöntemlerle bir arayüz oluşturun, örneğin:

interface MySecurityAccessor {

    MyUserDetails getCurrentUser();

    // Other methods
}

Daha sonra bunu SecurityContextHolderstandart uygulamanızda erişerek , böylece kodunuzu Spring Security'den tamamen ayırarak uygulayabilirsiniz. Ardından, güvenlik bilgilerine veya geçerli kullanıcı hakkındaki bilgilere erişmesi gereken denetleyicilere enjekte edin.

Diğer bir önemli avantaj ise, iplik yerel halkları ve benzerlerini doldurmaktan endişe etmeden test için sabit verilerle basit uygulamalar yapmanın kolay olmasıdır.


Bu yaklaşımı düşünüyordum, ancak a) herhangi bir diş çekme sorunu varsa tam olarak nasıl doğru yol (tm) ve b) yapılacağından emin değildim. Orada sorun olmayacağından emin misiniz? Yukarıda belirtilen ek açıklama yöntemiyle gidiyorum, ancak bunun hala iyi bir yol olduğunu düşünüyorum. Gönderdiğiniz için teşekkürler. :)
Awnry Bear

4
Teknik olarak, SecurityContextHolder'a doğrudan kumandanızdan erişmeyle aynıdır, bu nedenle iş parçacığı ile ilgili herhangi bir sorun olmamalıdır. Sadece tek bir yere çağrı tutar ve test için alternatifleri kolayca enjekte etmenizi sağlar. Aynı yaklaşımı, güvenlik bilgilerine erişmesi gereken diğer web dışı sınıflarda da yeniden kullanabilirsiniz.
Koyun Shaun

Yakaladım ... Ben de öyle düşünüyordum. Ek açıklama yöntemiyle birim sınaması sorunları yaşıyorsam veya Spring ile bağlantıyı azaltmak istiyorsam geri gelmek iyi bir şey olacaktır.
Awnry Bear

@LukeTaylor lütfen bu yaklaşımı daha fazla açıklayabilir misiniz, bahar ve bahar güvenliğinde biraz yeniyim, bu yüzden bunu nasıl uygulayacağımı tam olarak anlayamıyorum. Erişilmesi için bunu nerede uygularım? benim UserServiceImpl?
Cu7l4ss

9

HandlerInterceptorArabirimi uygulayın ve sonra UserDetailsModeli olan her isteğe aşağıdaki gibi enjekte edin :

@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
Teşekkürler @atrain, bu kullanışlı ve zarif. Ayrıca, <mvc:interceptors>uygulama yapılandırma dosyama eklemek zorunda kaldı .
Eric

Şablon üzerinden yetkili kullanıcı almak daha iyidir spring-security-taglibs: stackoverflow.com/a/44373331/548473
Grigory Kislin

9

Spring Security sürüm 3.2'den başlayarak, bazı eski yanıtlar tarafından uygulanan özel işlevler @AuthenticationPrincipal, destekli ek açıklama biçiminde kutudan çıkar AuthenticationPrincipalArgumentResolver.

Kullanımının basit bir örneği:

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

CustomUser öğesinin atanabilir olması gerekir authentication.getPrincipal()

İşte AuthenticationPrincipal ve AuthenticationPrincipalArgumentResolver'ın karşılık gelen Javadoc'ları


1
@nbro Sürüme özgü çözümü eklediğimde, diğer çözümlerin hiçbiri bu çözümü dikkate alacak şekilde güncellenmemişti
geoand

AuthenticationPrincipalArgumentResolver artık kullanımdan kaldırıldı
Igor Donin

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

0

Şablonlarda (ör. JSP) yetkili kullanıcıya ihtiyacınız varsa

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

birlikte

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

0

Bunu deneyebilirsiniz: Spring'den Kimlik Doğrulama Nesnesi kullanarak, denetleyici yönteminde Kullanıcı ayrıntılarını alabiliriz. Aşağıda, argümanla birlikte denetleyici yöntemine Kimlik Doğrulama nesnesi iletilerek örnek kullanıldığında, kimlik doğrulaması yapıldığında ayrıntılar Kimlik Doğrulama Nesnesinde doldurulur.

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

Lütfen cevabınızı detaylandırın.
Nikolai Shevchenko

Cevabımı düzenledim, zaten kimliği doğrulanmış kullanıcının kullanıcı ayrıntılarına sahip Kimlik Doğrulama Nesnesi'ni kullanabiliriz.
Mirza Shujathullah
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.