मैंने सोशल नेटवर्क्स REST API टूल का उपयोग करके विकसित पोर्टल पर प्राधिकरण (पंजीकरण) और उपयोगकर्ता पहचान को लागू करने का निर्णय लिया - विषय अभिनव, सक्रिय रूप से उपयोग किए जाने और उपयोग करने के लिए बहुत सुविधाजनक है। मैं अपनी साइटों पर इस तरह की कार्यक्षमता का उपयोग करने के लिए सभी उपयुक्तताओं और लाभों को सूचीबद्ध नहीं करने जा रहा हूं, लेकिन मैं यह ध्यान रखूंगा कि मैं प्रत्येक साइट के लिए पासवर्ड याद नहीं रखने के लिए बहुत खुश हूं (भले ही मेरे पास कुछ मानक हैं), मेल अग्रेषण के साथ टेड पंजीकरण में भाग लेने के लिए नहीं। और पुष्टिकरण, साथ ही साथ एक बार फिर कैप्चा का सामना नहीं करना पड़ेगा।
एपीआई डेटा की कार्यक्षमता काफी आदिम है, तकनीक सरल है, और कार्यान्वयन काफी समान और सरल है। लेकिन जब आप तकनीक से परिचित हो जाते हैं, तो एक विशेष सामाजिक नेटवर्क के प्रलेखन और एपीआई उदाहरण पर्याप्त नहीं हैं। इसके अलावा, जैसा कि विषय कहता है, उपयोग की जाने वाली भाषा जावा है, जो स्वचालित रूप से उपयोगी जानकारी की मात्रा को कम करती है। और RuNet में वर्णन इतने सारे नहीं हैं। आप कम से कम प्रतिरोध का रास्ता अपना सकते हैं और तीसरे पक्ष के रैस्टफुल उत्पादों का उपयोग कर सकते हैं, लेकिन क) यह प्रक्रिया की पूरी समझ नहीं देता है; बी) आवश्यक प्रक्रिया के स्विचिंग गुणों को कम करता है; ग) अक्सर तीसरे पक्ष के उत्पाद पर शोध करना इसके कार्यान्वयन को विकसित करने की तुलना में अधिक कठिन हो सकता है। हालांकि इस तरह के तीसरे पक्ष के उत्पाद की उपयोगिता विकास को बहुत आसान कर सकती है। हालाँकि, इस समीक्षा में, मैंने व्यक्तिगत रूप से सभी प्रक्रियाओं को अधिकतम रूप से नियंत्रित करने पर जोर दिया है, यहां तक कि सार्वभौमिकता (हम "एक विशिष्ट साइट पर" विशिष्ट कार्यक्षमता "जकड़ लेते हैं, और कुछ ही इसे एक सार्वभौमिक उत्पाद" सभी अवसरों के लिए "बनाते हैं)। इसके अलावा, मुझे न केवल उपयोगकर्ता प्राधिकरण को लागू करने में दिलचस्पी है, बल्कि वसंत सुरक्षा 3 ढांचे द्वारा प्रदान की गई परियोजना सुरक्षा प्रणाली को लागू करने में भी है।
प्लेटफार्मों और उपकरणों का उपयोग किया गया सेट:
स्प्रिंग कोर 3.1 ,
स्प्रिंग एमवीसी 3.1 ,
स्प्रिंग सिक्योरिटी 3.1 ,
हाइबरनेट 4.1 । कार्यान्वयन परियोजना विदेशी है, इसलिए कार्यान्वित सामाजिक नेटवर्क का सेट "उनके लिए" मानक है -
फेसबुक ,
ट्विटर ,
Google+ ,
लिंक्डइन ।
मैं यह नोट करना चाहता हूं कि स्प्रिंग पैकेज में "बॉक्स से बाहर" तैयार एक परियोजना है -
स्प्रिंग सोशल (आज रिलीज 1.0.2), जो उत्पाद के स्प्रिंग फ्रेमवर्क में उल्लेखनीय रूप से समझाया गया है और अन्य स्प्रिंग उत्पादों के साथ उपयोग के लिए डिज़ाइन किया गया है। निश्चित रूप से यह एक पेशेवर समाधान होगा, लेकिन हमारा काम हर चीज को नियंत्रित करना और प्रक्रिया को जितना संभव हो उतना पारदर्शी बनाना है। और सामाजिक के साथ, सब कुछ इतना सहज नहीं है।
1. मॉडल।
मैंने उपयोगकर्ता के ऑब्जेक्ट में
POJO ,
UserDetails और
Entity के संयोजन के लिए कुछ जोखिम भरा और विरोधाभासी पथ लिया। प्रोग्रामिंग दृष्टिकोण के दृष्टिकोण से यह गलत है, लेकिन क) यह बहुत सुविधाजनक है; और b) यह कई परतों के निर्माण को बचाता है, हमें अलग-अलग POJO + Entity, अलग-अलग UserDetails और अलग से DTO करने के लिए बचाता है, जो वास्तव में सामग्री की नकल करता है।
प्रस्तावित मॉडल निर्माण योजना इस प्रकार है:

मैंने दो लेयर्स (AuthUser और DataUser) का चयन किया ताकि प्राधिकरण तर्क और परियोजना के व्यावसायिक तर्क के साथ हस्तक्षेप न करें: आगंतुक, प्रशासक और जो कोई भी उसी तरह लॉग इन हो, लेकिन गुणों का अपना सेट हो। उदाहरण के लिए, मेरे पास मेरी परियोजना में जॉबसेकर्स और नियोक्ता हैं, वे उसी तरह साइट पर जाते हैं, लेकिन पूरी तरह से एक मॉडल संरचना है।
परतों के भीतर संरचना के पृथक्करण के लिए, यह स्पष्ट है - फेसबुक, ट्विटर, आदि और विशेष रूप से मानक प्राधिकरण के साथ प्राप्त क्षेत्रों का सेट, इतना अलग है कि डेटाबेस के निर्माण के दृष्टिकोण से सब कुछ के लिए एक बहुत ही फैला हुआ संरचना बनाना - जरूरत से ज्यादा। स्केलेबिलिटी के लिए, जब एक नए सेवा प्रदाता को जोड़ते हैं, तो ऐसी संरचना के साथ काम करना बेहद असुविधाजनक होगा।
कुछ सूचीबद्ध वस्तुओं की सूची, साथ ही साथ इस्तेमाल की गई एनम कक्षाएं।
AuthUser.java:
@ Entity @ Table(name = "auth_user") @ Inheritance(strategy = InheritanceType.JOINED) public class AuthUser implements Serializable, UserDetails { @ Id @ Column(name = "id") @ GeneratedValue(strategy = GenerationType.AUTO) private Long id; @ Column(name = "identification_name", length = 64, nullable = false) private String identificationName; @ Enumerated(EnumType.STRING) @ Column(name = "type", nullable = false) private AuthorityType type; @ Column(name = "binary_authorities", nullable = false) private Long binaryAuthorities; @ Column(name = "enabled", nullable = false, columnDefinition = "tinyint") private Boolean enabled; @ Transient private Set<Authority> authorities; @ OneToOne(fetch = FetchType.LAZY, orphanRemoval = true) @ Cascade({CascadeType.ALL}) @ JoinColumn(name="user_id") private User user; @ Override public Collection<? extends GrantedAuthority> getAuthorities() { authorities = EnumSet.noneOf(Authority.class); for (Authority authority : Authority.values()) if ((binaryAuthorities & (1 << authority.ordinal())) != 0) authorities.add(authority); return authorities; } public void setAuthority(Set<Authority> authorities) { binaryAuthorities = 0L; for (Authority authority : authorities) binaryAuthorities |= 1 << authority.ordinal(); } @ Override public String getPassword() { return type.name(); } @ Override public String getUsername() { return identificationName; } @ Override public boolean isAccountNonExpired() { return true; } @ Override public boolean isAccountNonLocked() { return true; } @ Override public boolean isCredentialsNonExpired() { return true; }
AuthorityType.java:
public enum AuthorityType implements Serializable { SIMPLE, FACEBOOK, TWITTER, GOOGLE, LINKEDIN; }
Authority.java:
public enum Authority implements GrantedAuthority { NEW_CUSTOMER, CUSTOMER, ADMINISTRATOR; @ Override public String getAuthority() { return toString(); } }
FacebookAuthUser.java:
@ Entity @ Table(name = "facebook_auth_user") public class FacebookAuthUser extends AuthUser { @ Column(name = "first_name", length = 32) private String firstName; @ Column(name = "last_name", length = 32) private String lastName; @ Column(name = "email", length = 64) private String email; @ Column(name = "token", length = 128) private String token;
TwitterAuthUser.java:
@ Entity @ Table(name = "twitter_auth_user") public class TwitterAuthUser extends AuthUser { @ Column(name = "screen_name", length = 64) private String screenName; @ Column(name = "oauth_token", length = 80) private String oauthToken; @ Column(name = "oauth_token_secret", length = 80) private String oauthTokenSecret;
SimpleAuthUser.java:
@ Entity @ Table(name = "simple_auth_user") public class SimpleAuthUser extends AuthUser { @ Column(name = "password", length = 40, nullable = false) private String password; @ Column(name = "uuid", length = 36, nullable = false) private String uuid; @ Override public String getPassword() { return password; }
जैसा कि आप देख सकते हैं, यह "रसायन विज्ञान" के बिना नहीं था:
- मैं उपयोगकर्ता भूमिकाओं को संग्रहीत करने के लिए एक और संरचना (तालिका) नहीं करना चाहता, और खुद को भूमिकाओं के सेट को बदलकर उपयोगकर्ता को कई भूमिकाओं के साथ बंद करने की क्षमता से वंचित करना मूर्खतापूर्ण है। इसलिए, डेटाबेस में मैं भूमिकाओं के सेट के बाइनरी प्रतिनिधित्व को संग्रहीत करता हूं, और स्प्रिंग सुरक्षा सेट को खिलाती है। आधार से पढ़ते समय याद रखने वाली मुख्य बात एक परिवर्तन करना है। Ideologically, अनुवाद तंत्र POJO में रखना गलत है, आपको इसे नियंत्रक या DAO में छोड़ने की आवश्यकता है, लेकिन हम इसे प्रकाशन लागत मानेंगे।
- फ़ील्ड प्रकार (एनम अथॉरिटी टाइप) - यह डेटाबेस में डेटा की कल्पना करने के बजाय किसी विशेष आवश्यकता को सहन नहीं करता है, साथ ही इसमें से किसी एक वर्ग की सदस्यता की खोज की तुलना में user.getType () का उपयोग करना आसान है - उपयोगकर्ता इंस्टेंटऑफ़ TwitterAuthUser , हालांकि यह पूरी तरह से अप्रकाशित है।
- UserDetails इंटरफ़ेस को कार्यान्वित करने के लिए कई तरीकों की आवश्यकता है। विशेष रूप से, IdentName (और getUsername () ) वह क्षेत्र है जहां पहचानकर्ता संग्रहीत होते हैं: फेसबुक के लिए यह FacebookID है , ट्विटर के लिए यह TwitterID है , मानक प्राधिकरण के लिए यह उपनाम या ईमेल है। मेरे मामले में getPassword () विधि एक ही प्रकार लौटाती है, फिर इसका उपयोग स्प्रिंग सिक्योरिटी द्वारा याद रखने वाले तंत्र में कुकीज़ के लिए हैश बनाने के लिए किया जाएगा। प्रत्येक कक्षा में प्रोजेक्ट सुरक्षा बढ़ाने के लिए, आप इस विधि को वास्तव में सुरक्षा डेटा बताकर ओवरराइड कर सकते हैं, जैसा कि मैंने SimpleAuthUser वर्ग में किया था। दूसरों के लिए, यह टोकन या गुप्त टोकन हो सकता है। तरीकों के साथ आगे क्या करना है और संभावित रूप से संबंधित गुण isAccountNonExpired , isAccountNonLocked , isCredentialsNonExpired है - इस तरह के डेटा को आवश्यकतानुसार उपयोग करने का निर्णय लें, इस समीक्षा में मैं इसका उपयोग नहीं करता, सही वापस डूबना।
जैसा कि आप आरेख और कोड में देख सकते हैं, मैंने विभिन्न परतों की वस्तुओं के बीच एक
आलसी संबंध का उपयोग करने का फैसला किया, भले ही वे एक-से-एक के रूप में संबंधित हों। दो लक्ष्य हैं: 1) लेखक अक्सर नियंत्रक और दृश्य में रूपरेखा को झटके देता है, और इसके पीछे निर्भर संरचना को हर जगह खींचने की बहुत इच्छा नहीं होती है, खासकर जब से यह बहुत विस्तारित और बड़े पैमाने पर हो सकती है (जोबसेकर तालिकाओं के लिए मेरी परियोजना में ईएजीआर टुकड़ों की निर्भरता 5- 6, LAZY की गिनती नहीं - ये दोनों टेलीफोन, और पते, और व्यवसायों, और अन्य) हैं, इसलिए, मेरी राय में, पुनर्बीमा को चोट नहीं पहुंचती है। 2) हमें यह नहीं भूलना चाहिए कि ये परतें तर्क की विभिन्न परतों से संबंधित हैं: AuthUser स्प्रिंग सिक्योरिटी फ्रेमवर्क के साथ जुड़ रहा है, और साथ ही, DataUser में परिवर्तन हो सकते हैं, और मैं लगातार निगरानी और अद्यतन नहीं करना चाहता। मैं मानता हूं कि यह निर्णय विवादास्पद है, और अंतिम होने का नाटक नहीं करता है। शायद आपको दूसरे तरीके से जुड़ने की ज़रूरत है, जिससे समस्याओं को सूचीबद्ध किया जा सके, और आप हमेशा व्यावसायिक तर्क से प्राधिकरण बीन खींच सकते हैं। यह डेवलपर्स के विवेक पर बना हुआ है।
DataUser वर्ग और आश्रितों के लिए, ये सरल POJO वर्ग हैं, सीधे DataUser में सभी के लिए गुण शामिल हैं (जैसे, आईडी, फर्स्टनाम, लास्टनेम, ईमेल, लोकेशन), और शेष इसे विशिष्ट से जोड़कर विस्तारित करता है (लिस्टिंग अव्यवहारिक है) ।
2. नियंत्रक।
सिद्धांत रूप में,
प्रमाणीकरण और
प्राधिकरण की शब्दावली में बहुत अंतर नहीं है - प्राधिकरण प्राधिकरण है, इसके अलावा, अलग-अलग नेटवर्क प्रदाता इन शर्तों को अपने तरीके से रेखांकित करते हैं। हालांकि, मेरी रिपोर्ट में मैं स्पष्ट रूप से 2 अवधारणाओं के बीच अंतर करता हूं - पंजीकरण और प्रत्यक्ष प्राधिकरण या लॉगिन (दोनों एक सामाजिक सेवा प्रदाता से प्राधिकरण पर आधारित हैं)। कहते हैं, जब एक मंच में भाग लेते हैं, या एक टिप्पणी सबमिट करते हैं, तो आपको बस लॉग इन करने की आवश्यकता होती है - चाहे वह पहली प्रविष्टि में हो, या सौवें में हो। मैं पंजीकरण के लिए आवेदन करते समय उपयोगकर्ता मॉडल बनाने की आवश्यकता द्वारा पंजीकरण और सरल प्राधिकरण के अलगाव का पीछा करता हूं। और यद्यपि यह आसान तरीके से लागू किया जा सकता है - प्रवेश द्वार पर हम जांचते हैं कि क्या कोई ऐसा व्यक्ति है या नहीं - और पहले लॉगिन के मामले में एक उपयोगकर्ता संरचना बनाएं। लेकिन एक) एक मानक पंजीकरण है और एक दृश्य पृथक्करण बनाने के लिए तार्किक है "यहां एक है, लेकिन यहां दूसरा है" (कुख्यात
प्रयोज्य ); ख) कोई बात नहीं कितनी अपमानजनक है, लेकिन सोशल नेटवर्किंग एपीआई अपने ग्राहकों के बारे में जानकारी प्रदान करने में एकमत नहीं हैं - कहते हैं,
फेसबुक एपीआई एक ईमेल, पहला नाम, अंतिम नाम, लिंग, स्थान प्रदान करता है;
Twitter API - screen_name देता है, जो "पहले नाम अंतिम नाम" नहीं हो सकता है, ईमेल नहीं देता है (उनके पास वास्तविक और आभासी के बीच स्पष्ट रूप से अंतर करने की स्थिति है);
Google+ API पहले नाम, अंतिम नाम, ईमेल प्रदान करता है, लेकिन स्थान के बारे में कुछ भी नहीं;
लिंक्डइन एपीआई - नाम, उपनाम, लिंग, स्थान, लेकिन ईमेल नहीं भेजता है। चूंकि मेरा प्रोजेक्ट आगंतुक के व्यक्तिगत डेटा (एक भर्ती कंपनी के लिए एक परियोजना) के साथ बहुत निकटता से जुड़ा हुआ है, पंजीकरण के साथ-साथ, मुझे कुछ फ़ील्ड भरने की आवश्यकता का संकेत मिलता है (शुरुआत में, फेसबुक उपयोगकर्ताओं के अलावा, सभी को कम से कम कुछ निर्दिष्ट करना था, अब यह सरल है और केवल इस तरह की आवश्यकता मौजूद है) ट्विटर उपयोगकर्ता, हालांकि मैं एक पूर्ण इनकार को बाहर नहीं करता हूं, और जब आवश्यकता होती है तो खेतों में भरना - कहते हैं, "ज़ोन" पर जाने की कोशिश करते समय, जहां ऐसी जानकारी बस आवश्यक होगी)। इसलिए, मेरा कार्यान्वयन कुछ हद तक फुलाया जाता है, हालांकि यह केवल प्राधिकरण तंत्र को अधिक समझने में मदद करता है।
मैं आपको याद दिलाना चाहता हूं कि काम (या परीक्षण) के लिए आपको प्रत्येक सामाजिक नेटवर्क में अपना खुद का एप्लिकेशन बनाने की जरूरत है, और काम करने के लिए इसकी सेटिंग्स का उपयोग करें। Facebook के लिए, यह
Developers.facebook.com/apps , Twitter के लिए -
dev.twitter.com/apps , Google+ के लिए -
code.google.com/apis/console , लिंक्डइन के लिए -
www.linkedin.com/secure/developer है । एप्लिकेशन बनाते समय, 3 पैरामीटर महत्वपूर्ण हैं जो प्रत्येक प्रदाता के पास है: एक कुंजी (या एपीआई कुंजी, उपभोक्ता कुंजी, क्लाइंट आईडी), एक गुप्त कुंजी (ऐप गुप्त, उपभोक्ता रहस्य, ग्राहक रहस्य, गुप्त कुंजी) और एक पुनर्निर्देशित पता (वे कहते हैं, हाल ही में तक प्रदाताओं में से कुछ को लोकलहोस्ट काम करने के लिए पुनर्निर्देशित नहीं किया गया है, लेकिन आज यह सत्यापित है कि हर कोई
http: // localhost: 8080 / myproject ) जैसे पते के साथ काम करता है। आप वहां अन्य मापदंडों को भी कॉन्फ़िगर कर सकते हैं, जिसमें एप्लिकेशन लोगो भी शामिल है, हालांकि, लिंक्डइन के लिए आवश्यक है कि छवि का लिंक एसएसएल (अतुलनीय इच्छा) हो।
फेसबुक और Google+ लंबे समय से नए
OAuth 2 प्रोटोकॉल का उपयोग कर रहे हैं, Twitter और लिंक्डइन अभी भी पुराने
OAuth प्रोटोकॉल का उपयोग कर रहे हैं (Google+ ने 20 अप्रैल 2012 तक OAuth के पहले संस्करण का समर्थन किया)। मेरे विवेक पर (हालांकि मैं सोच नहीं सकता कि एक अलग राय थी), OAuth 2 के साथ काम करना अतुलनीय रूप से सरल और अधिक सुविधाजनक है, हालांकि, इस तथ्य के बावजूद कि यह काफी लोकप्रिय है, यह अभी भी मानक के रूप में अनुमोदित नहीं है। ऑपरेशन का सिद्धांत काफी आदिम है (सबसे लोकप्रिय योजना):

इसलिए, उपयोगकर्ता वेब पेज पर पंजीकरण बटन में से एक पर क्लिक करता है:

(और मैं पृष्ठ पर किसी भी "अतिरिक्त" कार्यक्षमता को नहीं छोड़ता, केवल एक पते जैसे
www.myproject.com/registration/facebook , आदि के साथ एक बटन), अनुरोध नियंत्रक (फेसबुक के लिए) पर जाता है:
@ RequestMapping(value = "/registrate/facebook", method = RequestMethod.POST) public ModelAndView facebookRegistration() throws Exception { return new ModelAndView(new RedirectView(FACEBOOK_URL + "?client_id=" + FACEBOOK_API_KEY + + "&redirect_uri=" + FACEBOOK_URL_CALLBACK_REGISTRATION + + "&scope=email,user_location&state=registration", true, true, true)); }
स्कोप के लिए पैरामीटर
डेवलपर. facebook.com/docs/authentication/permissions (ट्विटर के लिए -
dev.twitter.com/docs/platform-objects/users , Google+) -
Developers.google.com/accounts/Oocs/OAuth2Login# पर देखे जा सकते हैं।
userinfocall , LinkedIn -
developer.linkedin.com/documents/profile-fields ), मैं अभी यहां एक युगल लाया हूं। रीडायरेक्ट_यूरी डोमेन को आवेदन के पंजीकृत पते से मेल खाना चाहिए। राज्य एक "मुक्त" पैरामीटर है, मैं इसे आगे की कार्रवाइयों के लिए एक सेमाफोर के रूप में उपयोग करता हूं - पंजीकरण, साइनइन, ऑटोसिग्निन।
तब उपयोगकर्ता प्राधिकरण के फेसबुक लॉगिन पेज पर "रीडायरेक्ट" करता है, जहां वह एप्लिकेशन को अपने डेटा का उपयोग करने की अनुमति देगा, और यदि अनुमतियाँ मूल अनुमतियों से परे हैं, तो उन्हें प्राधिकरण विंडो में सूचीबद्ध किया जाएगा।
प्राधिकरण के बाद,
FACEBOOK_IRL_CALLBACK_REGISTRATION को मैप करने वाला हमारा कंट्रोलर कॉल प्राप्त करता है (क्लाइंट द्वारा किसी भी निर्णय पर - लॉग इन, कैंसिल, रिटर्न)। स्प्रिंग MVC हमें मैपिंग करके अनुरोधों को फ़िल्टर करने की अनुमति देता है (इस मामले में, मेरी परियोजना की मैपिंग दी गई है):
@ RequestMapping(value = "/callback/facebook", method = RequestMethod.GET) public class FacebookController extends ExternalController implements Constants { @ RequestMapping(value = "/registration", params = "code") public ModelAndView registrationAccessCode(@ RequestParam("code") String code, HttpServletRequest request) throws Exception { String authRequest = Utils.sendHttpRequest("GET", FACEBOOK_URL_ACCESS_TOKEN, new String[]{"client_id", "redirect_uri", "client_secret", "code"}, new String[]{FACEBOOK_API_KEY, FACEBOOK_URL_CALLBACK_REGISTRATION, FACEBOOK_API_SECRET, code}); String token = Utils.parseURLQuery(authRequest).get("access_token"); String tokenRequest = Utils.sendHttpRequest("GET", FACEBOOK_URL_ME, new String[]{"access_token"}, new String[]{token}) Map<String, Json> userInfoResponse = Json.read(tokenRequest).asJsonMap(); String email = userInfoResponse.get("email").asString().toLowerCase(); String id = userInfoResponse.get("id").asString();
सुविधा और एकात्मक उपयोग के लिए, इस सूची में उपयोग किए जाने वाले यूटिल्स वर्ग के कुछ स्थिर तरीके:
public static String sendHttpRequest(String methodName, String url, String[] names, String[] values) throws HttpException, IOException { if (names.length != values.length) return null; if (!methodName.equalsIgnoreCase("GET") && !methodName.equalsIgnoreCase("POST")) return null; HttpMethod method; if (methodName.equalsIgnoreCase("GET")) { String[] parameters = new String[names.length]; for (int i = 0; i < names.length; i++) parameters[i] = names[i] + "=" + values[i]; method = new GetMethod(url + "?" + StringUtils.join(parameters, "&")); } else { method = new PostMethod(url); for (int i = 0; i < names.length; i++) ((PostMethod) method).addParameter(names[i], values[i]); method.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); } new HttpClient().executeMethod(method); return getStringFromStream(method.getResponseBodyAsStream()); } public static Map<String, String> parseURLQuery(String query) { Map<String, String> result = new HashMap<String,String>(); String params[] = query.split("&"); for (String param : params) { String temp[] = param.split("="); try { result.put(temp[0], URLDecoder.decode(temp[1], "UTF-8")); } catch (UnsupportedEncodingException exception) { exception.printStackTrace(); } } return result; }
स्थिरांक:
final public static String FACEBOOK_API_KEY = "XXXXXXXXXXXXXXXX"; final public static String FACEBOOK_API_SECRET = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; final public static String FACEBOOK_URL = "https://www.facebook.com/dialog/oauth"; final public static String FACEBOOK_URL_ACCESS_TOKEN = "https://graph.facebook.com/oauth/access_token"; final public static String FACEBOOK_URL_ME = "https://graph.facebook.com/me"; final public static String FACEBOOK_URL_CALLBACK_REGISTRATION = SITE_ADDRESS + "/callback/facebook/registration"; final public static String FACEBOOK_URL_CALLBACK_SIGNIN = SITE_ADDRESS + "/callback/facebook/signin";
हर कोई अपने विवेक से
JSON लाइब्रेरी का उपयोग कर सकता है, मैंने mjson लाइब्रेरी (
http://sharegov.blogspot.com/2011/06/json-library.html ) का उपयोग किया है - छोटे, सुविधाजनक और बिना क्रमांकन के।
जैसा कि आप देख सकते हैं, प्रक्रिया सरल है और विशेष प्रश्नों का कारण नहीं होना चाहिए। मैं यह भी ध्यान रखना चाहता हूं कि फेसबुक स्थान पैरामीटर देता है, जिन मूल्यों से आप
Google मैप्स एपीआई (
http://maps.googleapis.com/maps/api/geocode/json पर ) "पर्ची" कर सकते हैं और एक सुविधाजनक रूप में जियोलोकेशन को खींच सकते हैं (मानकों के अनुसार) गूगल मैप्स)। यह स्पष्ट है कि यह तभी किया जा सकता है जब ग्राहक ने अपने फेसबुक अकाउंट में न केवल देश का स्थान दर्शाया हो।
Google+ के लिए साइन अप एक समान तरीके से होता है, एकमात्र अंतर यह है कि उनके सिस्टम में
कॉलबैक URL पूरी तरह से एप्लिकेशन सेटिंग्स में निर्दिष्ट एक से मेल खाना चाहिए। इस प्रकार, सभी पुनर्निर्देश केवल एक मानचित्रण पर गिरेंगे। प्रक्रियाओं को अलग करने के लिए, लौटे
राज्य पैरामीटर का उपयोग करना सुविधाजनक है:
@ RequestMapping(value = "/callback/google", method = RequestMethod.GET) public class GoogleController extends ExternalController implements Constants { @ RequestMapping(value = {"/", ""}, params = "code") public ModelAndView googleProxy(@ RequestParam("code") String code, @ RequestParam("state") String state, HttpServletRequest request, HttpServletResponse response) throws Exception { ... } @ RequestMapping(value = {"/", ""}, params = "error") public ModelAndView googleErrorProxy(@RequestParam("error") String error, @RequestParam("state") String state, HttpServletRequest request) throws Exception { ... } }
पते और वापसी मापदंडों के अपवाद के साथ शेष क्रियाएं, समान हैं।
OAuth प्रमाणीकरण (Twitter और LinkedIn) के साथ स्थिति अलग है। मैं संपूर्ण प्राधिकरण श्रृंखला के माध्यम से गया था, लेकिन टोकन के साथ अनुरोध के गठन के कारण यह बहुत असुविधाजनक है - उन्हें एक विशेष तरीके से "सरेस से जोड़ा हुआ" होना चाहिए, बेस 64 पैक करें, समय के साथ पैरामीटर और अन्य जोड़तोड़ जोड़ें। और जो सबसे आश्चर्यजनक है - इन सामाजिक नेटवर्क के डेवलपर्स के लिए अनुभाग इन प्रक्रियाओं को प्रदर्शित नहीं करते हैं। यद्यपि यह एक मानक है, इसलिए, गणना एक मानक दृष्टिकोण के लिए जाती है। किसी भी स्थिति में, "मैन्युअल रूप से" लागू किया गया प्राधिकरण, आपके आवेदन को विकसित करने के लिए कोई दिलचस्पी नहीं है। मैं तृतीय-पक्ष मुक्त पुस्तकालयों का उपयोग करने की सलाह देता हूं जो इसे आसान बनाते हैं। उदाहरण के लिए, विशेष रूप से ट्विटर के लिए एक पुस्तकालय है -
twitter4j.jar । मैंने
मुंशी-जावा लाइब्रेरी (
http://github.com/fernandezpablo85/scribe-java ) का उपयोग किया, जो
एमआईटी लाइसेंस के अधिकारों के तहत वितरित की जाती है। पैकेज में
Digg API ,
Facebook API ,
Flickr API ,
Freelancer API ,
Google API ,
LinkedIn API ,
Skyrock API ,
Tumblr API ,
Twitter API ,
Vkontakte API ,
Yahoo API और एक दर्जन 2 अन्य शामिल हैं।
मुंशी पुस्तकालय का उपयोग करते हुए ट्विटर के लिए पंजीकरण प्रक्रिया इस तरह दिखाई देगी। पंजीकरण पृष्ठ से प्राधिकरण के लिए ग्राहक अनुरोध नियंत्रक:
@ RequestMapping(value = "/registrate/twitter", params = "action", method = RequestMethod.POST) public ModelAndView twitterRegistrationJobseeker(HttpServletRequest request) throws Exception { OAuthService service = new ServiceBuilder().provider(TwitterApi.class) .apiKey(TWITTER_CONSUMER_KEY).apiSecret(TWITTER_CONSUMER_SECRET) .callback(TWITTER_URL_CALLBACK_REGISTRATION).build(); Token requestToken = service.getRequestToken(); request.getSession().setAttribute("twitter", service); request.getSession().setAttribute("request_token", requestToken); return new ModelAndView(new RedirectView(service.getAuthorizationUrl(requestToken), true, true, true)); }
ट्विटर कॉलबैक कंट्रोलर:
@ RequestMapping(value = "/callback/twitter", method = RequestMethod.GET) public class TwitterController extends ExternalController implements Constants { @ RequestMapping(value = "/registration", params = "oauth_verifier") public ModelAndView registrationAccessCode(@ RequestParam("oauth_verifier") String verifier, HttpServletRequest request, HttpServletResponse response) throws Exception { OAuthService service = (OAuthService) request.getSession().getAttribute("twitter"); Token accessToken = service.getAccessToken((Token) request.getSession().getAttribute("request_token"), new Verifier(verifier)); OAuthRequest oauthRequest = new OAuthRequest(Verb.GET, TWITTER_URL_CREDENTIALS); service.signRequest(accessToken, oauthRequest); Map<String, Json> userInfoResponse = Json.read(oauthRequest.send().getBody()).asJsonMap(); String twitterId = userInfoResponse.get("id").asString();
फिर, सब कुछ बहुत सरल और सस्ती है। लिंक्डइन एपीआई के माध्यम से पंजीकरण बिल्कुल उसी तरह से किया जाता है।
अंतिम - एक मानक तरीके से पंजीकरण। मानक - यही कारण है कि यह मानक है, मैंने कोड नहीं दिया है, मैं सिर्फ यह स्पष्ट करूंगा कि परिणामस्वरूप हम एक सरल प्रकार की वस्तु बनाते हैं, जो प्रमाणिकता से विरासत में मिली है
SimpleAuthUser user = new SimpleAuthUser(); user.setAuthority(EnumSet.of(Authority.NEW_CUSTOMER)); user.setEnabled(false); user.setIdentificationName(email); user.setPassword(passwordEncoder.encodePassword(password, email)); user.setType(AuthenticationType.SIMPLE); user.setUser(customer); user.setUuid(uuid); authenticationDAO.put(user);
इस मामले के लिए, प्राधिकरण
NEW_CUSTOMER की आवश्यकता थी - एक पंजीकृत उपयोगकर्ता को पंजीकरण (मानक अभ्यास) की पुष्टि की आवश्यकता है, और इसलिए) एक अलग भूमिका है; ख) इसे स्प्रिंग सिक्योरिटी को अधिकृत करने की अनुमति नहीं है (सक्षम = गलत)।
साइट पर प्राधिकरण
एक साधारण वसंत
आवेदन-संदर्भ-सुरक्षा.एक्सएमएल :
<security:global-method-security secured-annotations="enabled" jsr250-annotations="enabled" pre-post-annotations="enabled" proxy-target-class="true"/> <security:http auto-config="true" use-expressions="true"> <security:intercept-url pattern="/**" access="permitAll"/> <security:form-login login-page="/signin"/> <security:logout invalidate-session="true" logout-success-url="/" logout-url="/signout"/> <security:remember-me services-ref="rememberMeService" key="someRememberMeKey"/> </security:http> <security:authentication-manager alias="authenticationManager"> <security:authentication-provider ref="authenticationProvider"/> </security:authentication-manager> <bean id="authenticationProvider" class="myproject.security.CustomAuthenticationProvider"/> <bean id="rememberMeService" class="myproject.security.RememberMeService"> <property name="key" value="someRememberMeKey"/> <property name="userDetailsService" ref="userDetailsService"/> </bean> <bean id="userDetailsService" class="myproject.security.CustomUserDetailsManager"/> <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder"/>
CustomUserDetailsManager.java:
public class CustomUserDetailsManager implements UserDetailsService { @ Resource private AuthenticationDAO authenticationDAO; @ Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return authenticationDAO.findAuthUser(username); } }
CustomUserAuthentication.java:
public class CustomUserAuthentication implements Authentication { private String name; private Object details; private UserDetails user; private boolean authenticated; private Collection<? extends GrantedAuthority> authorities; public CustomUserAuthentication(UserDetails user, Object details) { this.name = user.getUsername(); this.details = details; this.user = user; this.authorities = user.getAuthorities(); authenticated = true; } @ Override public String getName() { return name; } @ Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @ Override public Object getCredentials() { return user.getPassword(); } @ Override public Object getDetails() { return details; } @ Override public Object getPrincipal() { return user; } @ Override public boolean isAuthenticated() { return authenticated; } @ Override public void setAuthenticated(boolean authenticated) throws IllegalArgumentException { this.authenticated = authenticated; } }
CustomAuthenticationProvider.java
(वर्ग पूरी तरह से बेवकूफ है, लेकिन स्प्रिंग सिक्योरिटी को
ऑथेंटिकेशनपॉइडर इंटरफेस के उत्तराधिकारी को खिलाने की जरूरत है, लेकिन अर्थ के संदर्भ में निकटतम
PreAuthenticatedAuthenticationProvider उपयुक्त नहीं है)
public class CustomAuthenticationProvider implements AuthenticationProvider { @ Override public Authentication authenticate(Authentication authentication) throws AuthenticationException {
और शायद सुरक्षा को व्यवस्थित करने के लिए सबसे "अड़चन" जगह है
स्मरण तंत्र का कार्यान्वयन। सिद्धांत रूप में, सब कुछ पहले से ही व्यवस्थित है ताकि
रिमेम्बर सेवा का संचालन पूरी तरह से
टोकनबेडरैमरेमेमेर्स सर्विसेज के कार्यान्वयन के साथ हो, एक स्पष्टीकरण के साथ: स्वचालित क्लाइंट प्राधिकरण के लिए सभी डेटा हमारे डेटाबेस में हैं, हालांकि, ऐसी स्थिति हो सकती है जब कोई उपयोगकर्ता साइट पर लॉग ऑन करता है और फिर खाता हटा देता है। सामाजिक नेटवर्क का उपयोग किया।
और यह एक संघर्ष का पता लगाता है - हम उपयोगकर्ता को अधिकृत करते हैं, लेकिन वास्तव में वह वहां नहीं है, अर्थात, तृतीय-पक्ष सेवाओं के माध्यम से प्राधिकरण के मुख्य सिद्धांतों का उल्लंघन किया जाता है। यही है, जब याद रखें तंत्र को ट्रिगर किया जाता है, तो हमें स्वचालित रूप से आने वाले ग्राहक की जांच करनी चाहिए। प्रत्येक नेटवर्क प्रदाता के एपीआई में ऐसे तंत्र होते हैं, लेकिन हमें सही स्टेज पर जांचने के लिए स्प्रिंग के रिमेमे के काम में "वेज" करना चाहिए। दुर्भाग्य से, यह एक वर्ग को विस्तारित करने के लिए काम नहीं करेगा ( AbstractRememberMeServices हमारे पास अंतिम रूप देने के लिए आवश्यक विधि है ), इसलिए हमें पूरी तरह से वर्ग को फिर से परिभाषित करना होगा। मेरी विधि अधिक बोझिल है, मैं अंत से चला गया, और सामान्य मानव आलस्य मुझे एक सरल विकल्प पर फिर से करने की अनुमति नहीं देता है। मैंने AbstractRememberMeServices वर्ग को पूरी तरह से फिर से परिभाषित किया TokenBasedRememberMeServices ,
public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) – , «» :
Class<? extends ExternalController> controller = externalControllers.get(user.getPassword()); if (controller != null && !controller.newInstance().checkAccount(user)) return null;
, :
private Map<String, Class<? extends ExternalController>> externalControllers; public CustomRememberMeService() { externalControllers = new HashMap<String, Class<? extends ExternalController>>(){{ put(AuthenticationType.FACEBOOK.name(), FacebookController.class); put(AuthenticationType.TWITTER.name(), TwitterController.class); put(AuthenticationType.GOOGLE.name(), GoogleController.class); put(AuthenticationType.LINKEDIN.name(), LinkedinController.class); }}; }
( RememberMe ).
REMEMBER_ME_FILTER , autoLogin, . , , . – , , , «».
ExternalController checkAccount(user) . callback-
ExternalController :
public abstract class ExternalController { public abstract boolean checkAccount(UserDetails user) throws Exception; }
. , Facebook :
public boolean heckAccount(UserDetails user) throws Exception { FacebookAuthUser facebookUser = (FacebookAuthUser) user; String authRequest = Utils.sendHttpRequest("GET", FACEBOOK_URL_ME, new String[]{"access_token"}, new String[]{facebookUser.getToken()}); Map<String, Json> tokenInfoResponse = Json.read(authRequest).asJsonMap(); return tokenInfoResponse.get("error") == null && tokenInfoResponse.get("id").asString().equalsIgnoreCase(facebookUser.getIdentificationName()); }
और ट्विटर के लिए: public boolean checkAccount(UserDetails user) throws Exception { TwitterAuthUser twitterUser = (TwitterAuthUser) user; OAuthService service = new ServiceBuilder().provider(TwitterApi.class).apiKey(TWITTER_CONSUMER_KEY).apiSecret(TWITTER_CONSUMER_SECRET).build(); OAuthRequest oauthRequest = new OAuthRequest(Verb.GET, TWITTER_URL_CREDENTIALS); service.signRequest(new Token(twitterUser.getOauthToken(), twitterUser.getOauthTokenSecret()), oauthRequest); String response = oauthRequest.send().getBody(); Map<String, Json> info = Json.read(request).asJsonMap(); return info.get("id").asString().equalsIgnoreCase(twitterUser.getIdentificationName()); }
आदि
सीधे साइट पर ही प्राधिकरण (लॉगिन, साइन इन) पंजीकरण के समान है। उपयोगकर्ता पृष्ठ पर जाता है, "लॉगिन" पर क्लिक करता है और उसे प्राधिकरण में पुनर्निर्देशित भी करता है:
सर्वर को भेजने वाली एकमात्र चीज पैरामीटर है - "साइनइन" या "ऑटोसिग्निन", इसके आधार पर कि "साइन इन स्वचालित रूप से" चेकबॉक्स पर क्लिक किया जाता है। इसके अलावा, सब कुछ पंजीकरण के समान परिदृश्य के अनुसार होता है, केवल पैरामीटर बदलता है, कॉलबैक URL और सभी गुंजाइश या अनुमति को हटा दें- हमें केवल क्लाइंट आईडी और उसके टोकन प्राप्त करने की आवश्यकता है। नियंत्रक विधियों में उपयुक्त जांच के बाद, मैं आपको डेटाबेस में टोकन को अधिलेखित करने की सलाह देता हूं। और यद्यपि, उदाहरण के लिए, फेसबुक ने मेरे परीक्षणों के दौरान ग्राहक टोकन को नहीं बदला, लेकिन Google+ हर बार ऐसा करता है। मुझे नहीं पता कि कितनी बार "परिवर्तन" होता है, इसलिए मैं इसे प्रत्येक access_token (वास्तव में, प्रदाता द्वारा हर गैर-स्वचालित प्राधिकरण में) के बाद फिर से लिखता हूं ।और सबसे महत्वपूर्ण बिंदु वसंत सुरक्षा में उपयोगकर्ता का प्रत्यक्ष प्राधिकरण है (उदाहरण के लिए, फेसबुक की नियंत्रक का उपयोग करके अनुपालन और प्रदाता के एपीआई से अधिकार प्राप्त करने के लिए), उदाहरण के रूप में: @ RequestMapping(value = "/signin", params = "code") public ModelAndView signInAccessCode(@ RequestParam("code") String code, @ RequestParam("state") String state, HttpServletRequest request, HttpServletResponse response) throws Exception { String accessRequest = Utils.sendHttpRequest("GET", FACEBOOK_URL_ACCESS_TOKEN, new String[]{"client_id", "redirect_uri", "client_secret", "code"}, new String[]{FACEBOOK_API_KEY, FACEBOOK_URL_CALLBACK_SIGNIN, FACEBOOK_API_SECRET, code}); String token = Utils.parseURLQuery(accessRequest).get("access_token"); Map<String, Json> userInfoResponse = Json.read(Utils.sendHttpRequest("GET", FACEBOOK_URL_ME, new String[]{"access_token"}, new String[]{token})).asJsonMap(); FacebookAuthUser user = (FacebookAuthUser) authenticationDAO.findAuthUser(userInfoResponse.get("id").asString(), AuthenticationType.FACEBOOK); if (user == null) {
अब, ऑटो-लॉगिन चेकबॉक्स चयनित होने के साथ, ग्राहक स्वचालित रूप से लॉग इन हो जाएगा। तदनुसार, अगर कोई चेकमार्क नहीं है, तो याद रखें सेवा की लॉगआउट विधि के लिए एक कॉल कुकीज़ को मिटा देगा (यह और कुछ नहीं करता है)। वैसे, "/ लॉगआउट" लिंक पर क्लिक करने से प्राधिकरण हटा दिया जाता है और कुकीज़ स्वचालित रूप से साफ़ हो जाती हैं। यह ऊपर स्प्रिंग सुरक्षा कॉन्फिगर में संबंधित लाइन द्वारा प्रदान किया गया है। इस विधि का उपयोग मानक प्राधिकरण के लिए "खराब" भी किया जा सकता है: चेक पास करने के बाद (तालिका में उपयोगकर्ता को खोजने, पासवर्ड हैश, आदि को समेटते हुए), हम मैन्युअल रूप से इसे अधिकृत करते हैं: Authentication authentication = customAuthenticationProvider.trust(user); if (autosignin) customRememberMeService.onLoginSuccess(request, response, authentication); else customRememberMeService.logout(request, response, authentication);
. , , – , RememberMe , , TokenBasedRememberMeServices.
Spring Security, ,
@Secured(«CUSTOM_ROLE») , ( , ). Spring Security –
@PreAuthorize ,
@PostFilter :
@PreAuthorize(«hasRole('ADMINISTRATOR')»), @PreAuthorize(«hasRole({'CUSTOMER', 'ADMINISTRATOR'})»)। यह केवल सुरक्षा में स्प्रिंग सुरक्षा कॉन्फ़िगरेशन में इसे निर्दिष्ट करने के लिए आवश्यक है : वैश्विक-विधि-सुरक्षा पैरामीटर ।इसी तरह, आप एक दृश्य में ( JSP में ) स्प्रिंग सुरक्षा की सुविधाओं और क्षमताओं का लाभ उठा सकते हैं । उदाहरण के लिए:
<%@ taglib prefix="core" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> ... <sec:authorize access="isAuthenticated()"> <div id="userBox"> <span>Welcome, <sec:authentication property="principal.user.firstName"/>!</span> </div> </sec:authorize>
, ( ).
jsp- jsp- ( , - « – , — », , / – ; , , – , , , ):
<%@ page import="org.springframework.security.core.context.SecurityContextHolder" %> <%@ page import="myproject.auth.AuthUser" %> <% Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); AuthUser authUser = null; if (!(principal instanceof String)) authUser = (AuthUser) principal; %> ... <input type="text" <%= authUser == null ? "" : "disabled=\"disabled\"" %> name="your_name" value="<%= authUser == null ? "" : authUser.getUser().getFirstName()%>"/> ... <% if (authUser == null) { %> <div id="recaptcha_widget"> <div id="recaptchaContainer"> ... </div> </div> <% } %>
, - .
«» , (-): Runtime Exception, user ( getUser()) , null.
OpenSessionInViewइस मामले में आश्रित वस्तु के अतिरिक्त लोड के बिना, यह मदद नहीं करेगा, क्योंकि यहां HTTP सत्र अलग हैं। इसलिए, आपको लोडिंग पर तुरंत निर्भर वस्तु को लोड करना होगा, लेकिन यह दृष्टिकोण को विरोधाभासी बनाता है जिसके कारण आलसी कनेक्शन असाइन किया गया है - ऑब्जेक्ट लोड किया जाएगा और निर्भर ऑब्जेक्ट को लोड किए गए अपडेट नहीं करेगा, इस मामले में ईएजीईआर कनेक्शन स्थापित करना आसान है। मैंने एक नए सत्र के उद्घाटन के साथ आमतौर पर इस्तेमाल किए जाने वाले सत्रफैक्टरी.गेटसर्किट सेशन () की जगह लेते हुए सर्टिफिकेशनडीएओ में यह निर्णय लिया : सेशनफैक्टरी यूटिल्स.ओपनसेशन (सेशनफैक्टरी)। शायद यह स्मृति के मामले में सबसे किफायती समाधान नहीं है, लेकिन मैंने अभी तक इस सवाल को नहीं पूछा है और इस विषय पर ध्यान नहीं दिया है। मुझे लगता है कि वर्तमान सत्र की उपस्थिति के लिए चेक सेट करके, आप फ़िल्टर या OpenSessionInView इंटरसेप्टर को मना कर सकते हैं , वास्तव में इसके काम की जगह ले सकते हैं।पाठ आवश्यक से अधिक निकला, यह सुनिश्चित करने के लिए कि विवादास्पद या गलत क्षण भी हैं, लेकिन मैंने उन कठिनाइयों के समाधान को प्रतिबिंबित करने की कोशिश की, जो मैंने स्वयं परिकल्पित तंत्र को लागू करने में सामना किया था।