स्प्रिंग सुरक्षा / स्प्रिंगएमवीसी में एक प्रमाणित उपयोगकर्ता को मैन्युअल रूप से कैसे सेट करें


107

एक नया उपयोगकर्ता 'नया खाता' फ़ॉर्म जमा करने के बाद, मैं उस उपयोगकर्ता को मैन्युअल रूप से लॉग इन करना चाहता हूं ताकि उन्हें बाद के पृष्ठ पर लॉगिन न करना पड़े।

वसंत सुरक्षा इंटरसेप्टर के माध्यम से जाने वाला सामान्य फॉर्म लॉगिन पृष्ठ ठीक काम करता है।

नए-खाता-प्रपत्र नियंत्रक में मैं एक उपयोगकर्ता नामप्रणाली बना रहा हूं।

SecurityContextHolder.getContext().setAuthentication(authentication);

उसी पृष्ठ पर मैं बाद में जाँचता हूँ कि उपयोगकर्ता के साथ लॉग इन है:

SecurityContextHolder.getContext().getAuthentication().getAuthorities();

यह प्रमाणीकरण में पहले सेट किए गए अधिकारियों को देता है। सब ठीक हैं।

लेकिन जब इसी कोड को अगले पेज पर लोड किया जाता है, तो प्रमाणीकरण टोकन सिर्फ UserAnonymous है।

मैं स्पष्ट नहीं हूं कि पिछले अनुरोध पर मैंने जो प्रमाणीकरण सेट किया था, उसे क्यों नहीं रखा। कोई विचार?

  • क्या यह सत्र आईडी के साथ सही तरीके से सेट नहीं होने के कारण हो सकता है?
  • क्या ऐसा कुछ है जो संभवतः किसी तरह मेरे प्रमाणीकरण को अधिलेखित कर रहा है?
  • शायद मुझे प्रमाणीकरण को बचाने के लिए एक और कदम की आवश्यकता है?
  • या क्या मुझे किसी भी अनुरोध के बजाय पूरे सत्र में प्रमाणीकरण की घोषणा करने की आवश्यकता है?

बस कुछ विचारों की तलाश है जो मुझे यह देखने में मदद करें कि यहां क्या हो रहा है।


1
आप मेरे जवाब का अनुसरण कर सकते हैं stackoverflow.com/questions/4824395/…
एलेक्सा नोव

2
पाठक, इस प्रश्न के उत्तर से सावधान रहें यदि वे आपको ऐसा करने के लिए कहते हैं SecurityContextHolder.getContext().setAuthentication(authentication):। यह काम करता है, और आम है, लेकिन गंभीर कार्यक्षमता की कमियां हैं जो आपको मिलेंगी यदि आप बस ऐसा करते हैं। : अधिक जानकारी के लिए, मेरे सवाल है, और जवाब को देखने के stackoverflow.com/questions/47233187/...
बकरी

जवाबों:


62

मुझे थोड़ी देर पहले भी आपकी यही समस्या थी। मुझे विवरण याद नहीं है, लेकिन निम्नलिखित कोड में मेरे लिए काम करने वाली चीजें मिलीं। इस कोड का उपयोग स्प्रिंग वेबफ्लो प्रवाह के भीतर किया जाता है, इसलिए RequestContext और ExternalContext कक्षाएं। लेकिन जो हिस्सा आपके लिए सबसे अधिक प्रासंगिक है वह है doAutoLogin विधि।

public String registerUser(UserRegistrationFormBean userRegistrationFormBean,
                           RequestContext requestContext,
                           ExternalContext externalContext) {

    try {
        Locale userLocale = requestContext.getExternalContext().getLocale();
        this.userService.createNewUser(userRegistrationFormBean, userLocale, Constants.SYSTEM_USER_ID);
        String emailAddress = userRegistrationFormBean.getChooseEmailAddressFormBean().getEmailAddress();
        String password = userRegistrationFormBean.getChoosePasswordFormBean().getPassword();
        doAutoLogin(emailAddress, password, (HttpServletRequest) externalContext.getNativeRequest());
        return "success";

    } catch (EmailAddressNotUniqueException e) {
        MessageResolver messageResolvable 
                = new MessageBuilder().error()
                                      .source(UserRegistrationFormBean.PROPERTYNAME_EMAIL_ADDRESS)
                                      .code("userRegistration.emailAddress.not.unique")
                                      .build();
        requestContext.getMessageContext().addMessage(messageResolvable);
        return "error";
    }

}


private void doAutoLogin(String username, String password, HttpServletRequest request) {

    try {
        // Must be called from request filtered by Spring Security, otherwise SecurityContextHolder is not updated
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
        token.setDetails(new WebAuthenticationDetails(request));
        Authentication authentication = this.authenticationProvider.authenticate(token);
        logger.debug("Logging in with [{}]", authentication.getPrincipal());
        SecurityContextHolder.getContext().setAuthentication(authentication);
    } catch (Exception e) {
        SecurityContextHolder.getContext().setAuthentication(null);
        logger.error("Failure in autoLogin", e);
    }

}

2
धन्यवाद, कोड मुझे यह जानने में मददगार है कि मैं सही क्षेत्र में समस्या निवारण कर रहा हूँ। ऐसा लगता है कि मेरे पास एक धूम्रपान बंदूक है, यह मैनुअल प्रमाणीकरण के बाद एक नया सत्र आईडी बना रहा है, लेकिन पुराने सत्र आईडी को अभी भी कुकी से पहचाना जा रहा है। अब समझ में क्यों नहीं आएगा, लेकिन कम से कम मैं स्पष्ट रूप से ट्रैक पर हूं। धन्यवाद!
डेविड पार्क

4
इस मार्गदर्शन के बाद किसी को भी इस संबंधित मुद्दे को देखना चाहिए: stackoverflow.com/questions/4824395/…
डेविड पार्स

14
क्या आप यह समझा सकते हैं कि आपको प्रमाणीकरण कैसे मिल रहा है।
Piderider

1
@ s1moner3d आपको आईओसी के माध्यम से इसे प्राप्त करने में सक्षम होना चाहिए -> \ @Autowired
हार्टमुट

1
@Configuration public class WebConfig extends WebSecurityConfigurerAdapter { @Bean @Override public AuthenticationManager authenticationProvider() throws Exception { return super.authenticationManagerBean(); } }
slisnychyi

66

मुझे कोई अन्य पूर्ण समाधान नहीं मिला इसलिए मैंने सोचा कि मैं अपना पोस्ट करूंगा। यह थोड़ा हैक हो सकता है, लेकिन इसने समस्या को उपरोक्त समस्या को हल कर दिया है:

public void login(HttpServletRequest request, String userName, String password)
{

    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, password);

    // Authenticate the user
    Authentication authentication = authenticationManager.authenticate(authRequest);
    SecurityContext securityContext = SecurityContextHolder.getContext();
    securityContext.setAuthentication(authentication);

    // Create a new session and add the security context.
    HttpSession session = request.getSession(true);
    session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
}

7
+1 - इससे मुझे मदद मिली! मुझे SPRING_SECURITY_CONTEXT अपडेट याद आ रहा था। ... लेकिन यह "गंदा" कैसे है?
l3dx

12
तुम कहाँ authenticationManagerसे प्राप्त करते हो
इसहाक

2
ऑथेंटिकेशन मेनेजर को आपकी क्लास में इस तरह से आॅटो किया गया है। और आपके xml कॉन्फ़िगरेशन में बीन इंजेक्शन भी होना चाहिए, इसलिए वसंत जानता है कि क्या इंजेक्शन लगाना है।

1
ऑथेंटिकेशनसवाइसआईएमपीएल का कार्यान्वयन कहां है? यह वर्ग क्या रखता है?
प्रात_

3
नया सत्र बनाना क्यों आवश्यक है? क्या SecurityContext हैंडल नहीं करता है?
व्लाद मैनुअल मूरैन

17

अंततः समस्या की जड़ का पता लगाया।

जब मैं सुरक्षा संदर्भ मैन्युअल रूप से बनाता हूं तो कोई सत्र ऑब्जेक्ट नहीं बनता है। केवल जब अनुरोध समाप्त हो जाता है तो प्रसंस्करण वसंत सुरक्षा तंत्र को पता चलता है कि सत्र ऑब्जेक्ट शून्य है (जब यह अनुरोध संसाधित होने के बाद सत्र के लिए सुरक्षा संदर्भ को संग्रहीत करने का प्रयास करता है)।

अनुरोध के अंत में स्प्रिंग सिक्योरिटी एक नया सत्र ऑब्जेक्ट और सत्र आईडी बनाता है। हालाँकि यह नया सत्र आईडी कभी भी ब्राउज़र के लिए नहीं बनता है क्योंकि यह ब्राउज़र के जवाब के बाद, अनुरोध के अंत में होता है। इसके कारण नया सत्र ID (और इसलिए सुरक्षा संदर्भ मेरे उपयोगकर्ता पर मैन्युअल रूप से लॉग इन होता है) तब खो जाता है जब अगले अनुरोध में पिछला सत्र ID होता है।


4
ईमानदारी से यह कुछ भी से अधिक वसंत सुरक्षा में एक डिजाइन दोष की तरह लगता है। अन्य भाषाओं में बहुत सारी रूपरेखाएँ लिखी गई हैं, जिनसे कोई समस्या नहीं होगी, लेकिन स्प्रिंग सिक्योरिटी बस टूट जाती है।
chubbsondubs

3
और समाधान है?
s1moner3d

2
और उपाय क्या है?
थियागो

6

क्या हो रहा है की एक बेहतर तस्वीर पाने के लिए डिबग लॉगिंग चालू करें।

आप बता सकते हैं कि HTTP प्रतिक्रियाओं में दिए गए शीर्ष लेखों को देखने के लिए ब्राउज़र साइड डिबगर का उपयोग करके सत्र कुकीज़ सेट किया जा रहा है या नहीं। (अन्य तरीके भी हैं।)

एक संभावना यह है कि SpringSecurity सुरक्षित सत्र कुकीज़ सेट कर रहा है, और आपके अगले पृष्ठ में "http" URL के बजाय "https" URL है। (ब्राउज़र "http" URL के लिए एक सुरक्षित कुकी नहीं भेजेगा।)


धन्यवाद ये सभी बहुत उपयोगी और प्रासंगिक सुझाव थे!
डेविड पार्क

5

सर्वलेट 2.4 में नया फ़िल्टरिंग फ़ीचर मूल रूप से उस प्रतिबंध को कम करता है जो फ़िल्टर केवल एप्लिकेशन सर्वर द्वारा वास्तविक अनुरोध प्रसंस्करण से पहले और बाद में अनुरोध प्रवाह में काम कर सकता है। इसके बजाय, सर्वलेट 2.4 फ़िल्टर अब हर डिस्पैच बिंदु पर अनुरोध डिस्पैचर के साथ बातचीत कर सकता है। इसका मतलब यह है कि जब एक वेब संसाधन दूसरे संसाधन के लिए अनुरोध करता है (उदाहरण के लिए, उसी एप्लिकेशन में जेएसपी पेज पर अनुरोध को अग्रेषित करने वाला एक सर्वलेट), तो लक्षित संसाधन द्वारा अनुरोध को संभालने से पहले एक फ़िल्टर काम कर सकता है। इसका यह भी अर्थ है कि एक वेब संसाधन में अन्य वेब संसाधनों से आउटपुट या फ़ंक्शन शामिल होना चाहिए (उदाहरण के लिए, एक जेएसपी पृष्ठ जिसमें कई अन्य जेएसपी पृष्ठों से आउटपुट भी शामिल है), सर्वलेट 2.4 फ़िल्टर प्रत्येक शामिल संसाधनों से पहले और बाद में काम कर सकते हैं। ।

उस सुविधा को चालू करने के लिए, जिसकी आपको आवश्यकता है:

web.xml

<filter>   
    <filter-name>springSecurityFilterChain</filter-name>   
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 
</filter>  
<filter-mapping>   
    <filter-name>springSecurityFilterChain</filter-name>   
    <url-pattern>/<strike>*</strike></url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>

RegistrationController

return "forward:/login?j_username=" + registrationModel.getUserEmail()
        + "&j_password=" + registrationModel.getPassword();

अच्छी जानकारी, लेकिन उपयोगकर्ता नाम और पासवर्ड को यूआरएल में डालना बुरा है। 1) कोई पलायन नहीं किया जाता है, इसलिए विशेष चरित्र वाले एक उपयोगकर्ता नाम या पासवर्ड को सुरक्षा शोषण वेक्टर के रूप में तोड़ने, या उससे भी बदतर होने की संभावना है। 2) urls में पासवर्ड ख़राब हैं क्योंकि url अक्सर डिस्क में लॉग इन हो जाते हैं, जो सुरक्षा के लिए बहुत बुरा है - आपके सभी पासवर्ड प्लेनेट में बस वहीं बैठे रहते हैं।
बकरी

1

मैं एक एक्जिट एप्लिकेशन का परीक्षण करने की कोशिश कर रहा था और एक परीक्षण स्थापित करने के बाद अचानक यह एक स्पष्ट कारण के साथ काम करना बंद कर दिया।

मुझे काम करने के लिए उपरोक्त उत्तर नहीं मिल सके, इसलिए मेरा समाधान था कि परीक्षण के माहौल में वसंत के इस बिट को छोड़ दें। मैंने इस तरह वसंत के चारों ओर एक सीम पेश किया:

public class SpringUserAccessor implements UserAccessor
{
    @Override
    public User getUser()
    {
        SecurityContext context = SecurityContextHolder.getContext();
        Authentication authentication = context.getAuthentication();
        return (User) authentication.getPrincipal();
    }
}

उपयोगकर्ता यहाँ एक कस्टम प्रकार है।

मैं इसे एक ऐसी कक्षा में लपेट रहा हूं, जिसमें परीक्षण कोड के लिए बसंत को बाहर निकालने का विकल्प है।

public class CurrentUserAccessor
{
    private static UserAccessor _accessor;

    public CurrentUserAccessor()
    {
        _accessor = new SpringUserAccessor();
    }

    public User getUser()
    {
        return _accessor.getUser();
    }

    public static void UseTestingAccessor(User user)
    {
        _accessor = new TestUserAccessor(user);
    }
}

परीक्षण संस्करण बस इस तरह दिखता है:

public class TestUserAccessor implements UserAccessor
{
    private static User _user;

    public TestUserAccessor(User user)
    {
        _user = user;
    }

    @Override
    public User getUser()
    {
        return _user;
    }
}

कॉलिंग कोड में मैं अभी भी डेटाबेस से लोड एक उचित उपयोगकर्ता का उपयोग कर रहा हूं:

    User user = (User) _userService.loadUserByUsername(username);
    CurrentUserAccessor.UseTestingAccessor(user);

यदि आप वास्तव में सुरक्षा का उपयोग करने की आवश्यकता है, तो जाहिर तौर पर यह अभ्यस्त उपयुक्त नहीं होगा, लेकिन मैं परीक्षण परिनियोजन के लिए बिना सुरक्षा सेटअप के साथ चल रहा हूं। मैंने सोचा कि कोई और भी ऐसी ही स्थिति में भाग सकता है। यह एक पैटर्न है जिसका उपयोग मैंने पहले स्थैतिक निर्भरता को खत्म करने के लिए किया है। दूसरा विकल्प यह है कि आप रैपर क्लास की स्थिरता बनाए रख सकते हैं, लेकिन मैं इसे पसंद करता हूं क्योंकि कोड की निर्भरताएं अधिक स्पष्ट होती हैं क्योंकि आपको करंटUserAccessor को उन कक्षाओं में पास करना होता है जहां इसकी आवश्यकता होती है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.