سؤال

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

والمتطلبات الأمنية لدينا هي جميلة الأساسية. المستخدم يحتاج فقط لتكون قادرة على توفير اسم المستخدم وكلمة المرور لتكون قادرة على الوصول إلى أجزاء معينة من الموقع (مثل الحصول على معلومات حول حسابه)؛ وهناك عدد قليل من الصفحات على موقع (أسئلة وأجوبة، دعم، الخ) حيث يجب أن تعطى مستخدم مجهول الوصول.

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

ويبدو أن يوفر الأمن والربيع "السياق" الكائن (لكل موضوع) لتكون قادرة على الوصول إلى اسم المستخدم المعلومات / الرئيسية من أي مكان في التطبيق الخاص بك ...

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

... الذي يبدو جدا الامم المتحدة والربيع مثل وهذا الكائن هو المفرد (العالمي)، بطريقة ما.

وسؤالي هو: إذا كان هذا هو طريقة قياسية للوصول إلى معلومات حول مصادقة المستخدم في الأمن الربيع، ما هي الطريقة المقبولة لحقن كائن مصادقة في SecurityContext بحيث يكون متاح لبلدي وحدة الاختبارات عندما تكون الوحدة اختبارات تتطلب مصادقة المستخدم؟

هل أحتاج إلى سلك هذا الأمر في طريقة تهيئة كل حالة اختبار؟

protected void setUp() throws Exception {
    ...
    SecurityContextHolder.getContext().setAuthentication(
        new UsernamePasswordAuthenticationToken(testUser.getLogin(), testUser.getPassword()));
    ...
}

وهذا يبدو مطول للغاية. هل توجد طريقة أسهل؟

والكائن SecurityContextHolder نفسه يبدو جدا الامم المتحدة والربيع، مثل ...

هل كانت مفيدة؟

المحلول

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

وقبل أن نبدأ في استخدام الأمن الربيع، ونحن من شأنه أن يخلق الفول راقب جلسة في حاوية لتخزين مدير المدرسة، وضخ هذه إلى "AuthenticationService" (المفرد) ثم حقن هذه الحبة إلى الخدمات الأخرى التي تحتاج إلى معرفة مدير الحالي.

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

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

نصائح أخرى

وفقط تفعل ذلك بالطريقة المعتادة ثم أدخله باستخدام SecurityContextHolder.setContext() في فئة اختبار، على سبيل المثال:

وحدة التحكم:

Authentication a = SecurityContextHolder.getContext().getAuthentication();

والاختبار:

Authentication authentication = Mockito.mock(Authentication.class);
// Mockito.whens() for your authorization object
SecurityContext securityContext = Mockito.mock(SecurityContext.class);
Mockito.when(securityContext.getAuthentication()).thenReturn(authentication);
SecurityContextHolder.setContext(securityContext);

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

ذكر

وcliff.meyers اتجاه واحد من حوله - خلق بنفسك نوع "الرئيسي" وحقن مثيل في مستهلكين. الربيع << لأ href = "http://static.springframework.org/spring/docs/2.5.x/reference/beans.html#beans-factory-scopes-other-injection" يختلط = "noreferrer"> اوب: راقب بالوكالة /> العلامة قدم في 2.x جنبا إلى جنب مع تعريف الفول نطاق الطلب، ويمكن أن يكون الدعم مصنع طريقة تذكرة إلى رمز أكثر قابلية للقراءة.

ويمكن أن تعمل مثل التالية:

public class MyUserDetails implements UserDetails {
    // this is your custom UserDetails implementation to serve as a principal
    // implement the Spring methods and add your own methods as appropriate
}

public class MyUserHolder {
    public static MyUserDetails getUserDetails() {
        Authentication a = SecurityContextHolder.getContext().getAuthentication();
        if (a == null) {
            return null;
        } else {
            return (MyUserDetails) a.getPrincipal();
        }
    }
}

public class MyUserAwareController {        
    MyUserDetails currentUser;

    public void setCurrentUser(MyUserDetails currentUser) { 
        this.currentUser = currentUser;
    }

    // controller code
}

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

<bean id="userDetails" class="MyUserHolder" factory-method="getUserDetails" scope="request">
    <aop:scoped-proxy/>
</bean>

<bean id="controller" class="MyUserAwareController">
    <property name="currentUser" ref="userDetails"/>
    <!-- other props -->
</bean>

وبفضل سحر اوب: العلامة راقب-بروكسي، وسوف يطلق على getUserDetails طريقة ثابتة في كل مرة طلب HTTP الجديد يأتي في وستحل أي إشارات إلى الملكية currentUser بشكل صحيح. الآن وحدة اختبار يصبح تافها:

protected void setUp() {
    // existing init code

    MyUserDetails user = new MyUserDetails();
    // set up user as you wish
    controller.setCurrentUser(user);
}

وآمل أن يساعد هذا!

ودون الإجابة على سؤال حول كيفية إنشاء وحقن أجسام مصادقة، الربيع الأمن 4.0 يوفر بعض البدائل ترحيب عندما يتعلق الأمر الاختبار. الشرح @WithMockUser تمكن المطور لتحديد مستخدم وهمية (مع السلطات اختياري، اسم المستخدم، كلمة المرور و الأدوار) بطريقة مرتبة:

@Test
@WithMockUser(username = "admin", authorities = { "ADMIN", "USER" })
public void getMessageWithMockUserCustomAuthorities() {
    String message = messageService.getMessage();
    ...
}

وهناك أيضا خيار لاستخدام @WithUserDetails لمحاكاة عاد UserDetails من UserDetailsService، منها مثلا.

@Test
@WithUserDetails("customUsername")
public void getMessageWithUserDetailsCustomUsername() {
    String message = messageService.getMessage();
    ...
}
يمكن العثور على <ع> مزيد من التفاصيل في في @ WithMockUser و في @ WithUserDetails فصول في مستندات مرجعية الأمن الربيع (من التي تم نسخها الأمثلة المذكورة أعلاه)

وأنا شخصيا أود أن مجرد استخدام Powermock جنبا إلى جنب مع Mockito أو Easymock للسخرية من SecurityContextHolder.getSecurityContext ثابت () في الاختبار وحدة / التكامل منها مثلا.

@RunWith(PowerMockRunner.class)
@PrepareForTest(SecurityContextHolder.class)
public class YourTestCase {

    @Mock SecurityContext mockSecurityContext;

    @Test
    public void testMethodThatCallsStaticMethod() {
        // Set mock behaviour/expectations on the mockSecurityContext
        when(mockSecurityContext.getAuthentication()).thenReturn(...)
        ...
        // Tell mockito to use Powermock to mock the SecurityContextHolder
        PowerMockito.mockStatic(SecurityContextHolder.class);

        // use Mockito to set up your expectation on SecurityContextHolder.getSecurityContext()
        Mockito.when(SecurityContextHolder.getSecurityContext()).thenReturn(mockSecurityContext);
        ...
    }
}

وباعتراف الجميع هناك قدرا كبيرا من التعليمات البرمجية لوحة المراجل هنا أي يسخر كائن مصادقة، لو كانت تسخر من SecurityContext لإعادة مصادقة وأخيرا يسخرون من SecurityContextHolder للحصول على SecurityContext، ولكن لها مرن جدا ويسمح لك لاختبار وحدة لسيناريوهات مثل كائنات مصادقة فارغة وما إلى ذلك دون الحاجة إلى تغيير التعليمات البرمجية (غير اختبار)

<ع> استخدام ثابت في هذه الحالة هو أفضل وسيلة لكتابة رمز آمن.

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

وسألت نفسي نفس السؤال على <لأ href = "https://stackoverflow.com/questions/248562/when-using-spring-security-what-is-the-proper-way-to-obtain-current -username-ط "> هنا ، ومجرد نشر جوابا التي وجدت في الآونة الأخيرة. الجواب القصير هو: حقن SecurityContext، والرجوع إلى SecurityContextHolder فقط في التكوين الربيع الخاص بك للحصول على SecurityContext

وأود أن نلقي نظرة على الطبقات اختبار مجردة الربيع وكائنات وهمية والتي تحدثت عن <لأ href = "http://static.springframework.org/spring/docs/2.5.x/reference/testing.html" يختلط = "نوفولو noreferrer"> هنا . أنها توفر وسيلة قوية لصناعة السيارات في الأسلاك الخاصة بك الربيع إدارة الكائنات صنع الوحدة والتكامل الاختبار أسهل.

عام

في هذه الأثناء (منذ الإصدار 3.2، في عام 2013، وذلك بفضل <لأ href = "https://jira.spring.io/browse/SEC-2298؟page=com.atlassian.jira.plugin.system .issuetabpanels: كل tabpanel "يختلط =" نوفولو "> SEC-2298) يمكن حقنها المصادقة إلى أساليب MVC باستخدام الشرح <لأ href =" http://docs.spring.io/spring-security /site/docs/3.2.3.RELEASE/reference/htmlsingle/#mvc-authentication-principal "يختلط =" نوفولو "> @ AuthenticationPrincipal :

@Controller
class Controller {
  @RequestMapping("/somewhere")
  public void doStuff(@AuthenticationPrincipal UserDetails myUser) {
  }
}

تحليل الاختبارات

في اختبار وحدتك يمكنك الاتصال من الواضح أن هذا الأسلوب مباشرة. في اختبارات التكامل باستخدام org.springframework.test.web.servlet.MockMvc يمكنك استخدام org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user() لحقن المستخدم مثل هذا:

mockMvc.perform(get("/somewhere").with(user(myUserDetails)));

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

mockMvc.perform(get("/somewhere").with(sessionUser(myUserDetails)));
/* ... */
private static RequestPostProcessor sessionUser(final UserDetails userDetails) {
    return new RequestPostProcessor() {
        @Override
        public MockHttpServletRequest postProcessRequest(final MockHttpServletRequest request) {
            final SecurityContext securityContext = new SecurityContextImpl();
            securityContext.setAuthentication(
                new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities())
            );
            request.getSession().setAttribute(
                HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext
            );
            return request;
        }
    };
}

والمصادقة هي خاصية موضوع في بيئة الخادم في نفس الطريق كما هو خاصية من عملية في نظام التشغيل. ومن شأن وجود مثيل الفول للوصول إلى معلومات المصادقة يكون التكوين غير مريح وفوق الأسلاك دون أي فائدة.

وفيما يتعلق المصادقة اختبار هناك عدة طرق كيف يمكنك ان تجعل حياتك أسهل. بلدي المفضل هو جعل @Authenticated الشرح العرف وتنفيذ اختبار المستمع، الذي يدير ذلك. تحقق DirtiesContextTestExecutionListener للإلهام.

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

وبالطبع أنا على استعداد لرؤية تلك الميزات الجديدة في الأمن ربيع 4.0 من شأنها أن تجعل تجاربنا أسهل.

package [myPackage]

import static org.junit.Assert.*;

import javax.inject.Inject;
import javax.servlet.http.HttpSession;

import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;

@ContextConfiguration(locations={[my config file locations]})
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public static class getUserConfigurationTester{

    private MockMvc mockMvc;

    @Autowired
    private FilterChainProxy springSecurityFilterChain;

    @Autowired
    private MockHttpServletRequest request;

    @Autowired
    private WebApplicationContext webappContext;

    @Before  
    public void init() {  
        mockMvc = MockMvcBuilders.webAppContextSetup(webappContext)
                    .addFilters(springSecurityFilterChain)
                    .build();
    }  


    @Test
    public void testTwoReads() throws Exception{                        

    HttpSession session  = mockMvc.perform(post("/j_spring_security_check")
                        .param("j_username", "admin_001")
                        .param("j_password", "secret007"))
                        .andDo(print())
                        .andExpect(status().isMovedTemporarily())
                        .andExpect(redirectedUrl("/index"))
                        .andReturn()
                        .getRequest()
                        .getSession();

    request.setSession(session);

    SecurityContext securityContext = (SecurityContext)   session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);

    SecurityContextHolder.setContext(securityContext);

        // Your test goes here. User is logged with 
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top