वसंत के माध्यम से प्रतिष्ठित प्रमाणीकरण


262

समस्या:
हमारे पास स्प्रिंग एमवीसी-आधारित रेस्टफुल एपीआई है जिसमें संवेदनशील जानकारी है। एपीआई को सुरक्षित किया जाना चाहिए, हालांकि प्रत्येक अनुरोध के साथ उपयोगकर्ता की साख (उपयोगकर्ता / पास कॉम्बो) भेजना वांछनीय नहीं है। अन्य दिशा निर्देशों (और आंतरिक व्यावसायिक आवश्यकताओं) के अनुसार, सर्वर को स्टेटलेस रहना चाहिए। एपीआई एक अन्य सर्वर द्वारा मैशअप-शैली दृष्टिकोण में भस्म हो जाएगा।

आवश्यकताएँ:

  • ग्राहक .../authenticateक्रेडेंशियल के साथ (असुरक्षित URL) अनुरोध करता है ; सर्वर सुरक्षित टोकन देता है जिसमें सर्वर के लिए भविष्य के अनुरोधों को मान्य करने और स्टेटलेस रहने के लिए पर्याप्त जानकारी होती है। यह संभवतः स्प्रिंग सिक्योरिटी के रिमेम्बर-मी टोकन के समान जानकारी से युक्त होगा ।

  • क्लाइंट विभिन्न (संरक्षित) URL के लिए बाद में अनुरोध करता है, पहले प्राप्त टोकन को क्वेरी पैरामीटर (या, कम वांछनीय रूप से, HTTP अनुरोध हेडर) के रूप में जोड़कर।

  • क्लाइंट को कुकीज़ स्टोर करने की उम्मीद नहीं की जा सकती है।

  • चूंकि हम पहले से ही वसंत का उपयोग करते हैं, इसलिए समाधान को वसंत सुरक्षा का उपयोग करना चाहिए।

हम इस काम को करने की कोशिश कर रहे दीवार के खिलाफ अपना सिर पीट रहे हैं, इसलिए उम्मीद है कि कोई व्यक्ति पहले ही इस समस्या को हल कर चुका होगा।

उपरोक्त परिदृश्य को देखते हुए, आप इस विशेष आवश्यकता को कैसे हल कर सकते हैं?


49
हाय क्रिस, मुझे यकीन नहीं है कि क्वेरी पैरामीटर में टोकन सबसे अच्छा विचार है। HTTPS या HTTP की परवाह किए बिना लॉग में दिखाई देगा। हेडर शायद अधिक सुरक्षित हैं। सिर्फ आपकी जानकारी के लिए। हालांकि महान सवाल। +1
jmort253

1
स्टेटलेस के बारे में आपकी क्या समझ है? आपका टोकन आवश्यकता स्टेटलेस की मेरी समझ से टकराती है। Http प्रमाणीकरण जवाब मुझे केवल स्टेटलेस कार्यान्वयन के लिए लगता है।
मार्कस मल्कुश

9
@MarkusMalkusch स्टेटलेस सर्वर के पूर्व संचार का ज्ञान किसी दिए गए क्लाइंट के साथ होता है। HTTP परिभाषा के अनुसार स्टेटलेस है, और सत्र कुकीज़ इसे स्टेटफुल बनाते हैं। टोकन के जीवनकाल (और स्रोत, उस मामले के लिए) अप्रासंगिक हैं; सर्वर केवल इस बात की परवाह करता है कि यह मान्य है और इसे उपयोगकर्ता को वापस भेजा जा सकता है (सत्र नहीं)। एक पहचान टोकन पास करना, इसलिए, राज्य के साथ हस्तक्षेप नहीं करता है।
क्रिस कैशवेल

1
@ChrisCashwell आप कैसे सुनिश्चित करते हैं कि ग्राहक द्वारा टोकन को खराब / उत्पन्न नहीं किया जा रहा है? क्या आप टोकन को एन्क्रिप्ट करने के लिए सर्वर-साइड पर एक निजी कुंजी का उपयोग करते हैं, क्लाइंट को प्रदान करते हैं, और फिर भविष्य के अनुरोधों के दौरान इसे डिक्रिप्ट करने के लिए उसी कुंजी का उपयोग करते हैं? स्पष्ट रूप से बेस 64 या कुछ अन्य आक्षेप पर्याप्त नहीं होंगे। क्या आप इन टोकन की "मान्यता" के लिए तकनीकों पर विस्तार से बता सकते हैं?
क्रेग ओटिस

6
यद्यपि यह दिनांकित है और मैंने 2 वर्षों में कोड को छुआ या अद्यतन नहीं किया है, मैंने इन अवधारणाओं पर और विस्तार करने के लिए एक Gist बनाया है। gist.github.com/ccashwell/dfc05dd8bd1a75d189d1
क्रिस कैशवेल

जवाबों:


190

हम इस काम को ओपी में वर्णित रूप से प्राप्त करने में कामयाब रहे, और उम्मीद है कि कोई और समाधान का उपयोग कर सकता है। यहाँ हमने क्या किया:

सुरक्षा संदर्भ को इस तरह सेट करें:

<security:http realm="Protected API" use-expressions="true" auto-config="false" create-session="stateless" entry-point-ref="CustomAuthenticationEntryPoint">
    <security:custom-filter ref="authenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
    <security:intercept-url pattern="/authenticate" access="permitAll"/>
    <security:intercept-url pattern="/**" access="isAuthenticated()" />
</security:http>

<bean id="CustomAuthenticationEntryPoint"
    class="com.demo.api.support.spring.CustomAuthenticationEntryPoint" />

<bean id="authenticationTokenProcessingFilter"
    class="com.demo.api.support.spring.AuthenticationTokenProcessingFilter" >
    <constructor-arg ref="authenticationManager" />
</bean>

जैसा कि आप देख सकते हैं, हमने एक रिवाज बनाया है AuthenticationEntryPoint, जो मूल रूप से सिर्फ एक रिटर्न देता है 401 Unauthorizedयदि अनुरोध हमारे द्वारा फ़िल्टर श्रृंखला में प्रमाणित नहीं किया गया था AuthenticationTokenProcessingFilter

CustomAuthenticationEntryPoint :

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {
        response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Authentication token was either missing or invalid." );
    }
}

प्रमाणीकरणटोकैनप्रोसेसिंगफिल्टर :

public class AuthenticationTokenProcessingFilter extends GenericFilterBean {

    @Autowired UserService userService;
    @Autowired TokenUtils tokenUtils;
    AuthenticationManager authManager;

    public AuthenticationTokenProcessingFilter(AuthenticationManager authManager) {
        this.authManager = authManager;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        @SuppressWarnings("unchecked")
        Map<String, String[]> parms = request.getParameterMap();

        if(parms.containsKey("token")) {
            String token = parms.get("token")[0]; // grab the first "token" parameter

            // validate the token
            if (tokenUtils.validate(token)) {
                // determine the user based on the (already validated) token
                UserDetails userDetails = tokenUtils.getUserFromToken(token);
                // build an Authentication object with the user's info
                UsernamePasswordAuthenticationToken authentication = 
                        new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails((HttpServletRequest) request));
                // set the authentication into the SecurityContext
                SecurityContextHolder.getContext().setAuthentication(authManager.authenticate(authentication));         
            }
        }
        // continue thru the filter chain
        chain.doFilter(request, response);
    }
}

जाहिर है, TokenUtilsकुछ प्रिवी (और बहुत मामला-विशिष्ट) कोड होता है और आसानी से साझा नहीं किया जा सकता है। यहाँ इसका इंटरफ़ेस है:

public interface TokenUtils {
    String getToken(UserDetails userDetails);
    String getToken(UserDetails userDetails, Long expiration);
    boolean validate(String token);
    UserDetails getUserFromToken(String token);
}

आपको एक अच्छी शुरुआत के लिए उतरना चाहिए। खुश कोडिंग। :)


क्या टोकन को अनुरोध के साथ भेजने पर टोकन को प्रमाणित करना आवश्यक है। सीधे उपयोगकर्ता नाम की जानकारी कैसे प्राप्त करें और वर्तमान संदर्भ / अनुरोध में सेट करें?
फिशर

1
@ देखें कि मैं उन्हें कहीं स्टोर नहीं करता ... टोकन का पूरा विचार यह है कि इसे हर अनुरोध के साथ पारित किया जाना चाहिए, और इसकी वैधता (इसलिए validate(...)विधि) का निर्धारण करने के लिए इसे आंशिक रूप से विघटित किया जा सकता है । यह महत्वपूर्ण है क्योंकि मैं चाहता हूं कि सर्वर स्टेटलेस रहे। मुझे लगता है कि आप वसंत का उपयोग करने की आवश्यकता के बिना इस दृष्टिकोण का उपयोग कर सकते हैं।
क्रिस कैशवेल

1
यदि क्लाइंट एक ब्राउज़र है, तो टोकन को कैसे संग्रहीत किया जा सकता है? या क्या आपको प्रत्येक अनुरोध के लिए प्रमाणीकरण को फिर से करना होगा?
शुरुआत_

2
महान युक्तियाँ। @ChrisCashwell - वह हिस्सा जो मुझे नहीं मिल रहा है वह वह है जहां आप उपयोगकर्ता क्रेडेंशियल्स को मान्य करते हैं और एक टोकन वापस भेजते हैं? मुझे लगता है कि यह कहीं न कहीं / सर्टिफिकेट एंड पॉइंट के इम्प्लांट में होना चाहिए। क्या मैं सही हू ? यदि नहीं तो क्या लक्ष्य / प्रमाणिकता है?
योनातन मामन

3
प्रमाणीकरण प्रबंधक के अंदर क्या है?
MoienGK

25

आप डाइजेस्ट एक्सेस ऑथेंटिकेशन पर विचार कर सकते हैं । अनिवार्य रूप से प्रोटोकॉल इस प्रकार है:

  1. क्लाइंट से अनुरोध किया जाता है
  2. सर्वर एक अद्वितीय नॉन स्ट्रिंग के साथ प्रतिक्रिया करता है
  3. क्लाइंट नॉन के साथ एक उपयोगकर्ता नाम और पासवर्ड (और कुछ अन्य मान) md5 हैशेड की आपूर्ति करता है; इस हैश को HA1 के नाम से जाना जाता है
  4. सर्वर तब क्लाइंट की पहचान को सत्यापित करने और अनुरोधित सामग्रियों की सेवा करने में सक्षम है
  5. नॉनस के साथ संचार तब तक जारी रह सकता है जब तक कि सर्वर एक नया नॉनस सप्लाई नहीं करता (रिप्ले हमलों को खत्म करने के लिए एक काउंटर का उपयोग किया जाता है)

यह सभी संचार हेडर के माध्यम से किए जाते हैं, जो कि jmort253 बताते हैं, आमतौर पर url मापदंडों में संवेदनशील सामग्री के संचार की तुलना में अधिक सुरक्षित है।

डाइजेस्ट एक्सेस ऑथेंटिकेशन स्प्रिंग सिक्योरिटी द्वारा समर्थित है । ध्यान दें कि, हालांकि डॉक्स का कहना है कि आपके पास आपके क्लाइंट के सादे-टेक्स्ट पासवर्ड तक पहुंच होनी चाहिए, यदि आप अपने क्लाइंट के लिए HA1 हैश हैं, तो आप सफलतापूर्वक प्रमाणित कर सकते हैं ।


1
हालांकि यह एक संभावित दृष्टिकोण है, कई गोल यात्राएं जो एक टोकन को पुनः प्राप्त करने के लिए बनाई जानी चाहिए, यह थोड़ा अवांछनीय बनाता है।
क्रिस कैशवेल

यदि आपका क्लाइंट HTTP ऑथेंटिकेशन स्पेसिफिकेशन को फॉलो करता है, तो वे राउंड ट्रिप केवल पहली कॉल पर होते हैं और जब 5. होते हैं।
मार्कस मलकस

5

जानकारी ले जाने वाले टोकन के बारे में, JSON वेब टोकन ( http://jwt.io ) एक शानदार तकनीक है। मुख्य अवधारणा सूचना तत्वों (दावों) को टोकन में एम्बेड करना है, और फिर पूरे टोकन पर हस्ताक्षर करना है ताकि सत्यापन का अंत सत्यापित कर सके कि दावे वास्तव में विश्वसनीय हैं।

मैं इस जावा कार्यान्वयन का उपयोग करता हूं: https://bitbucket.org/b_c/jose4j/wiki/Home

एक स्प्रिंग मॉड्यूल (वसंत-सुरक्षा-jwt) भी है, लेकिन मैंने इसका समर्थन नहीं किया।


2

आप JSON WebTokens के साथ OAuth का उपयोग क्यों नहीं शुरू करते हैं

http://projects.spring.io/spring-security-oauth/

OAuth2 एक मानकीकृत प्राधिकरण प्रोटोकॉल / रूपरेखा है। आधिकारिक OAuth2 विनिर्देश के अनुसार :

आप अधिक जानकारी यहाँ पा सकते हैं

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