عند استخدام الأمن الربيع، ما هي الطريقة الصحيحة للحصول على اسم المستخدم الحالي (أي SecurityContext) المعلومات في الفول؟

StackOverflow https://stackoverflow.com/questions/248562

سؤال

ولدي تطبيق ويب الربيع MVC الذي يستخدم الأمن الربيع. أريد أن أعرف اسم المستخدم دخوله حاليا في المستخدم. أنا باستخدام التعليمات البرمجية المتكررة أدناه. هل هذه هي الطريقة المقبولة؟

وأنا لا أحب أن الدعوة إلى أسلوب ثابت داخل وحدة التحكم هذه - أن الهزائم الغرض كله من الربيع، IMHO. هل هناك طريقة لتكوين التطبيق لديك SecurityContext الحالي، أو مصادقة الحالية، حقن بدلا من ذلك؟

  @RequestMapping(method = RequestMethod.GET)
  public ModelAndView showResults(final HttpServletRequest request...) {
    final String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();
    ...
  }
هل كانت مفيدة؟

المحلول

إذا كنت تستخدم <لأ href = "http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-arguments" يختلط = "noreferrer"> الربيع 3 ، وأسهل طريقة هي:

 @RequestMapping(method = RequestMethod.GET)   
 public ModelAndView showResults(final HttpServletRequest request, Principal principal) {

     final String currentUser = principal.getName();

 }

نصائح أخرى

لقد تغير الكثير في العالم منذ الربيع الإجابة على هذا السؤال. وقد مبسطة ربيع الحصول على المستخدم الحالي في وحدة تحكم. للحبوب أخرى، اعتمدت الربيع الاقتراحات المؤلف وتبسيط حقن "SecurityContextHolder. المزيد من التفاصيل في هذه التعليقات.


وهذا هو الحل لقد انتهى الأمر الذهاب مع. بدلا من استخدام SecurityContextHolder في وحدة التحكم الخاصة بي، وأنا أريد لحقن شيء الذي يستخدم SecurityContextHolder تحت غطاء محرك السيارة ولكن خلاصات بعيدا تلك الفئة مثل المفرد من قانون بلدي. لقد وجدت أي وسيلة للقيام بذلك بخلاف المتداول اجهة بلدي، كما يلي:

public interface SecurityContextFacade {

  SecurityContext getContext();

  void setContext(SecurityContext securityContext);

}

والآن، تحكم بلدي (أو أيا كان POJO) قد تبدو هذه:

public class FooController {

  private final SecurityContextFacade securityContextFacade;

  public FooController(SecurityContextFacade securityContextFacade) {
    this.securityContextFacade = securityContextFacade;
  }

  public void doSomething(){
    SecurityContext context = securityContextFacade.getContext();
    // do something w/ context
  }

}

و، بسبب واجهة كونه نقطة الفصل، وحدة اختبار واضح ومباشر. في هذا المثال يمكنني استخدام Mockito:

public class FooControllerTest {

  private FooController controller;
  private SecurityContextFacade mockSecurityContextFacade;
  private SecurityContext mockSecurityContext;

  @Before
  public void setUp() throws Exception {
    mockSecurityContextFacade = mock(SecurityContextFacade.class);
    mockSecurityContext = mock(SecurityContext.class);
    stub(mockSecurityContextFacade.getContext()).toReturn(mockSecurityContext);
    controller = new FooController(mockSecurityContextFacade);
  }

  @Test
  public void testDoSomething() {
    controller.doSomething();
    verify(mockSecurityContextFacade).getContext();
  }

}

وتطبيق الافتراضي واجهة يبدو مثل هذا:

public class SecurityContextHolderFacade implements SecurityContextFacade {

  public SecurityContext getContext() {
    return SecurityContextHolder.getContext();
  }

  public void setContext(SecurityContext securityContext) {
    SecurityContextHolder.setContext(securityContext);
  }

}

وأخيرا، التكوين إنتاج الربيع يبدو مثل هذا:

<bean id="myController" class="com.foo.FooController">
     ...
  <constructor-arg index="1">
    <bean class="com.foo.SecurityContextHolderFacade">
  </constructor-arg>
</bean>

ويبدو أكثر من سخيفة القليل الذي الربيع، حاوية حقن تبعية كل شيء، لم تزود وسيلة لحقن شيئا من هذا القبيل. وأنا أفهم ورثت SecurityContextHolder من acegi، ولكن لا يزال. الشيء هو، وانهم قريبة جدا - إذا كان SecurityContextHolder فقط جالبة للحصول على سبيل المثال SecurityContextHolderStrategy الأساسي (الذي هو واجهة)، هل يمكن حقن ذلك. في الحقيقة، أنا حتى rel="noreferrer"> لهذا الغرض.

وآخر شيء واحد - لقد تغيرت تماما إلى حد كبير الجواب كان لي هنا من قبل. تحقق من التاريخ إذا كنت غريبة ولكن، كما أشار زميل العمل بالنسبة لي، سيكون جوابي السابقة لا تعمل في بيئة متعددة الخيوط. وSecurityContextHolderStrategy الأساسية المستخدمة من قبل SecurityContextHolder هو، بشكل افتراضي، مثيل ThreadLocalSecurityContextHolderStrategy، الذي يخزن SecurityContexts في ThreadLocal. وبالتالي، فإنه ليس بالضرورة أن يكون فكرة جيدة لحقن SecurityContext مباشرة في الفول في وقت التهيئة - قد تحتاج إلى استردادها من ThreadLocal في كل مرة، في بيئة متعددة الخيوط، حتى يتم استرداد واحدة صحيحة

وأوافق على أن الحاجة إلى الاستعلام SecurityContext لينتن للمستخدم الحالي، يبدو وسيلة للغاية الامم المتحدة والربيع للتعامل مع هذه المشكلة.

وكتبت ثابت "المساعد" الطبقة للتعامل مع هذه المشكلة. أنها قذرة في أنه وسيلة العالمي وثابت، ولكنني أحسب بهذه الطريقة إذا قمنا بتغيير أي شيء متعلق الأمن، على الأقل ليس لدي سوى لتغيير التفاصيل في مكان واحد:

/**
* Returns the domain User object for the currently logged in user, or null
* if no User is logged in.
* 
* @return User object for the currently logged in user, or null if no User
*         is logged in.
*/
public static User getCurrentUser() {

    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal()

    if (principal instanceof MyUserDetails) return ((MyUserDetails) principal).getUser();

    // principal object is either null or represents anonymous user -
    // neither of which our domain User object can represent - so return null
    return null;
}


/**
 * Utility method to determine if the current user is logged in /
 * authenticated.
 * <p>
 * Equivalent of calling:
 * <p>
 * <code>getCurrentUser() != null</code>
 * 
 * @return if user is logged in
 */
public static boolean isLoggedIn() {
    return getCurrentUser() != null;
}

لجعلها تظهر فقط في صفحات JSP الخاص بك، يمكنك استخدام الربيع الأمن بطاقة ليب:

http://static.springsource.org/spring-security /site/docs/3.0.x/reference/taglibs.html

لاستخدام أي من العلامات، ويجب أن يكون أمن taglib أعلن في JSP الخاص بك:

<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>

وبعد ذلك في صفحة التخطيط الاستراتيجي المشترك تفعل شيئا مثل هذا:

<security:authorize access="isAuthenticated()">
    logged in as <security:authentication property="principal.username" /> 
</security:authorize>

<security:authorize access="! isAuthenticated()">
    not logged in
</security:authorize>

ملحوظة: كما ذكر في تصريحات @ SBerg413، سوف تحتاج إلى إضافة

<اقتباس فقرة>   

واستخدام تعبيرات = "صحيح"

لعلامة "HTTP" في التكوين security.xml لهذا العمل.

إذا كنت تستخدم أمان الربيع النسخة> = 3.2، يمكنك استخدام الشرح @AuthenticationPrincipal:

@RequestMapping(method = RequestMethod.GET)
public ModelAndView showResults(@AuthenticationPrincipal CustomUser currentUser, HttpServletRequest request) {
    String currentUsername = currentUser.getUsername();
    // ...
}

وهنا، CustomUser هو كائن مخصص التي تطبق UserDetails التي يتم إرجاعها من قبل UserDetailsService المخصصة.

يمكن العثور

ومزيد من المعلومات في في @ AuthenticationPrincipal الفصل من مستندات مرجعية الأمن الربيع.

وأحصل على المستخدم موثقا من قبل HttpServletRequest.getUserPrincipal ()؛

مثال:

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.support.RequestContext;

import foo.Form;

@Controller
@RequestMapping(value="/welcome")
public class IndexController {

    @RequestMapping(method=RequestMethod.GET)
    public String getCreateForm(Model model, HttpServletRequest request) {

        if(request.getUserPrincipal() != null) {
            String loginName = request.getUserPrincipal().getName();
            System.out.println("loginName : " + loginName );
        }

        model.addAttribute("form", new Form());
        return "welcome";
    }
}

في الربيع 3+ لديك ديك الخيارات التالية.

والخيار 1:

@RequestMapping(method = RequestMethod.GET)    
public String currentUserNameByPrincipal(Principal principal) {
    return principal.getName();
}

والخيار 2:

@RequestMapping(method = RequestMethod.GET)
public String currentUserNameByAuthentication(Authentication authentication) {
    return authentication.getName();
}

والخيار 3:

@RequestMapping(method = RequestMethod.GET)    
public String currentUserByHTTPRequest(HttpServletRequest request) {
    return request.getUserPrincipal().getName();

}

والخيار 4: يتوهم احد: التحقق من ذلك لمزيد من التفاصيل

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

نعم، احصائيات سيئة عموما - عموما، ولكن في هذه الحالة، ثابت هو رمز الأكثر أمانا يمكنك كتابة. منذ سياق الأمان بربط مجموعة الرئيسي مع موضوع قيد التشغيل حاليا، فإن الرمز الأكثر أمنا الوصول إلى ثابت من موضوع مباشرة قدر الإمكان. إخفاء الوصول راء فئة مجمع الذي يتم حقنه توفر للمهاجمين مع المزيد من النقاط للهجوم. انهم لن تحتاج إلى الوصول إلى رمز (والذي سيكون لديهم صعوبة في تغيير إذا تم التوقيع على جرة)، وانهم فقط بحاجة إلى وسيلة لتجاوز التكوين، الذي يمكن القيام به في وقت التشغيل أو الانزلاق بعض XML داخل CLASSPATH. فحتى باستخدام حقن الشرح في التعليمات البرمجية الموقعة يكون للتجاوز مع XML خارجي. هذا XML يمكن حقن نظام تشغيل مع مدير المارقة. وربما هذا هو السبب في الربيع تفعل شيئا حتى الامم المتحدة والربيع مثل في هذه القضية.

وأود أن تفعل هذا فقط:

request.getRemoteUser();

لالتطبيق الربيع MVC الماضي كتبت، لم أكن حقن حامل SecurityContext، ولكن لدى وحدة تحكم قاعدة أن كان لي طريقتين المرافق المرتبطة بهذا ... isAuthenticated () وgetUsername (). داخليا فإنها أسلوب ثابت ندعو لكم وصفه.

وعلى الأقل ثم انها فقط في وضع مرة واحدة إذا كنت بحاجة إلى ريفاكتور لاحق.

هل يمكن استخدام الربيع AOP aproach. على سبيل المثال إذا كان لديك بعض الخدمات، وهذا يحتاج إلى معرفة أصل الحالي. هل يمكن تقديم الشرح مخصص أيPrincipal، التي تشير إلى أن هذه الخدمة يجب أن يكون مدير المدرسة تعتمد.

public class SomeService {
    private String principal;
    @Principal
    public setPrincipal(String principal){
        this.principal=principal;
    }
}

وبعد ذلك في نصيحتك، وهو ما أعتقد يحتاج إلى تمديد MethodBeforeAdvice، تأكد من أن خدمة معينة لديها الشرحPrincipal وحقن اسم الرئيسي، أو تعيينها إلى "ANONYMOUS" بدلا من ذلك.

والمشكلة الوحيدة هي أنه حتى بعد مصادقة مع الأمن الربيع، والفول المستخدم / الرئيسي غير موجود في الحاوية، لذلك تبعية بضخ سيكون من الصعب. قبل كنا الأمن ربيع نحن من شأنه أن يخلق الفول راقب الجلسة التي كان مدير المدرسة الحالي، وضخ أن إلى "AuthService"، ومن ثم حقن تلك الخدمة إلى معظم الخدمات الأخرى في التطبيق. حتى تلك الخدمات بكل بساطة دعوة authService.getCurrentUser () للحصول على وجوه. إذا كان لديك مكان في التعليمات البرمجية حيث يمكنك الحصول على إشارة إلى نفس الرئيسي في الدورة، يمكنك ببساطة تعيين كخاصية على هاتفك الفول راقب الدورة.

وهذه محاولة

<اقتباس فقرة>   

والمصادقة مصادقة =   . SecurityContextHolder.getContext () getAuthentication ()؛
  سلسلة اسم المستخدم = authentication.getName ()؛

والحل الأمثل إذا كنت تستخدم الربيع 3 وتحتاج الرئيسية موثق في وحدة التحكم الخاصة بك هو أن تفعل شيئا من هذا القبيل:

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;

    @Controller
    public class KnoteController {
        @RequestMapping(method = RequestMethod.GET)
        public java.lang.String list(Model uiModel, UsernamePasswordAuthenticationToken authToken) {

            if (authToken instanceof UsernamePasswordAuthenticationToken) {
                user = (User) authToken.getPrincipal();
            }
            ...

    }

وأنا باستخدام الشرح @AuthenticationPrincipal في صفوف @Controller وكذلك في تلك @ControllerAdvicer المشروح. مثال:.

@ControllerAdvice
public class ControllerAdvicer
{
    private static final Logger LOGGER = LoggerFactory.getLogger(ControllerAdvicer.class);


    @ModelAttribute("userActive")
    public UserActive currentUser(@AuthenticationPrincipal UserActive currentUser)
    {
        return currentUser;
    }
}

وأين UserActive هي الطبقة يمكنني استخدام الخدمات للمستخدمين بتسجيل الدخول، ويمتد من org.springframework.security.core.userdetails.User. شيء من هذا القبيل:

public class UserActive extends org.springframework.security.core.userdetails.User
{

    private final User user;

    public UserActive(User user)
    {
        super(user.getUsername(), user.getPasswordHash(), user.getGrantedAuthorities());
        this.user = user;
    }

     //More functions
}

من السهل حقا.

وتحديد Principal ك تبعية في طريقة التحكم الخاصة بك والربيع سوف تضخ مصادقة المستخدم الحالي في طريقتك في الاحتجاج.

وأود أن تشاركونني سيلة لدعم تفاصيل المستخدم على الصفحة freemarker. كل شيء بسيط جدا ويعمل بشكل ممتاز!

وعليك فقط أن تضع rerequest مصادقة على default-target-url (الصفحة بعد شكل تسجيل الدخول) هذا هو بلدي طريقة كونترولر لهذه الصفحة:

@RequestMapping(value = "/monitoring", method = RequestMethod.GET)
public ModelAndView getMonitoringPage(Model model, final HttpServletRequest request) {
    showRequestLog("monitoring");


    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    String userName = authentication.getName();
    //create a new session
    HttpSession session = request.getSession(true);
    session.setAttribute("username", userName);

    return new ModelAndView(catalogPath + "monitoring");
}

وهذا هو قانون بلدي فتل:

<@security.authorize ifAnyGranted="ROLE_ADMIN, ROLE_USER">
<p style="padding-right: 20px;">Logged in as ${username!"Anonymous" }</p>
</@security.authorize> 

وهذا كل شيء، سيظهر اسم المستخدم في كل صفحة من بعد إذنه.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top