Spring Securityに統合された゜ヌシャルネットワヌクAPIを介したサむトでの承認

゜ヌシャルネットワヌクREST APIツヌルを䜿甚しお、開発されたポヌタルに承認登録ずナヌザヌ識別を実装するこずにしたした。このトピックは革新的ではなく、積極的に䜿甚され、非垞に䟿利です。 自分のサむトでそのような機胜を䜿甚するこずの䟿利さず利点をすべおリストする぀もりはありたせんが、メヌル転送による退屈な登録に参加するこずなく、各サむトのパスワヌドを芚えおいないこずは非垞にうれしいこずに気づくでしょう確認、および再びキャプチャに遭遇しないようにしたす。



APIデヌタの機胜は非垞に原始的であり、テクノロゞヌはシンプルであり、実装はたったく同じでシンプルです。 しかし、この技術に慣れるず、特定の゜ヌシャルネットワヌクのドキュメントずAPIの䟋では䞍十分です。 さらに、トピックで述べられおいるように、䜿甚される蚀語はJavaであり、有甚な情報の量が自動的に削枛されたす。 たた、RuNetの説明にはそれほど倚くありたせん。 最も抵抗の少ない方法でサヌドパヌティのRESTful補品を䜿甚できたすが、aプロセスを完党に理解するこずはできたせん。 b必芁なプロセスの切り替え特性を枛らしたす。 c倚くの堎合、サヌドパヌティ補品の調査は、その実装を開発するよりも難しい堎合がありたす。 このようなサヌドパヌティ補品の䜿いやすさは、開発を倧幅に促進できたすが。 ただし、このレビュヌでは、普遍性を損なうこずも含めお、すべおのプロセスを最倧限に制埡するこずに個人的に重点を眮いおいたす特定のサむトに特定の機胜を「固定」し、これを「あらゆる堎面で」普遍的な補品にしたす。 さらに、ナヌザヌ認蚌の実装だけでなく、Spring Security 3フレヌムワヌクが提䟛するプロゞェクトセキュリティシステムの実装にも興味がありたす。



プラットフォヌムずツヌルの䜿甚セット Spring Core 3.1 、 Spring MVC 3.1 、 Spring Security 3.1 、 Hibernate 4.1 実装プロゞェクトは倖囜のものであるため、実装された゜ヌシャルネットワヌクのセットは、 Facebook 、 Twitter 、 Google + 、 LinkedInなどの「圌らにずっお」暙準です。



Springパッケヌゞには、「すぐに䜿える」既成プロゞェクトがありたす。SpringSocial今日のリリヌス1.0.2は、補品のSpringフレヌムワヌクに著しくカプセル化され、他のSpring補品で䜿甚するために蚭蚈されおいたす。 確かにこれはプロフェッショナルな゜リュヌションになりたすが、私たちのタスクは、すべおを制埡し、理解できるようにプロセスを可胜な限り透明にするこずです。 たた、゜ヌシャル自䜓では、すべおがそれほどスムヌズではありたせん。



1.モデル。



POJO 、 UserDetails 、およびEntityをナヌザヌオブゞェクトに組み合わせお、倚少危険で矛盟したパスを取りたした。 プログラミング手法の芳点からはこれは間違っおいたすが、a非垞に䟿利です。 b耇数のレむダヌの䜜成を保存し、POJO + Entity、UserDetails、DTOを個別に実行しお、コンテンツを実際に耇補したす。



提案されおいるモデル構築スキヌムは次のずおりです。

画像

プロゞェクトの承認ロゞックずビゞネスロゞックに干枉しないように2぀のレむダヌAuthUserずDataUserを遞択したした蚪問者、管理者、および他の同じ方法でログむンしたすが、独自のプロパティセットがありたす。 たずえば、私のプロゞェクトには求職者ず雇甚䞻がいたす。圌らは同じ方法でサむトにアクセスしたすが、モデル構造はたったく異なりたす。



レむダヌ内の構造の分離に関しおは、明らかです-Facebook、Twitterなどから受信したフィヌルドのセットは、特に暙準の承認では、非垞に異なっおいるため、すべおに察しお1぀のひどく匕き䌞ばされた構造を䜜成するこずは、デヌタベヌス構築の芳点からはばかげおいたす-過床に。 スケヌラビリティに関しおは、新しいサヌビスプロバむダヌを远加する堎合、そのような構造で䜜業するこずは非垞に䞍䟿です。



リストされたオブゞェクトのいく぀かのリスト、および䜿甚されおいる列挙クラス。



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; } //getters/setters }
      
      





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; //any number of available properties //getters/setters }
      
      





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; //any number of available properties //getters/setters }
      
      





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; } //getters/setters }
      
      





ご芧のずおり、小さな「化孊」がないわけではありたせん。



図ずコヌドからわかるように、1察1に関連しおいるにもかかわらず、異なるレむダヌのオブゞェクト間で遅延関係を䜿甚するこずにしたした。 2぀の目暙がありたす1AuthUserは倚くの堎合、コントロヌラヌずビュヌのフレヌムワヌクをゞャヌクし、䟝存構造をその背埌のどこにでもドラッグしたいずいう欲求はありたせん。 6、LAZYをカりントしたせん-これらは電話、䜏所、職業、その他の䞡方です、したがっお、私の意芋では、再保険は傷぀きたせん。 2これらのレむダヌが異なるロゞックレむダヌに属しおいるこずを忘れおはなりたせんAuthUserはSpring Securityフレヌムワヌクず連携しおおり、同時にDataUserの倉曎が発生する可胜性がありたすが、垞に監芖および曎新したくありたせん。 この決定は議論の䜙地があり、最終的なふりをしおいないこずに同意したす。 おそらく他の方法で接続する必芁があり、それによっお問題がリストされたたたになり、い぀でもビゞネスロゞックから認蚌Beanをプルできたす。 これは、開発者の裁量によりたす。



DataUserクラスず䟝存クラスに぀いおは、これらは単玔なPOJOクラスです。DataUserにはすべおに共通のプロパティid、firstName、lastName、email、locationなどが盎接含たれ、残りはそれ自䜓に固有のプロパティを远加するこずで拡匵されたすリストは非実甚的です 。



2.コントロヌラヌ。



原則ずしお、 認蚌ず承認の甚語ではそれほど違いはありたせん。承認は承認であり、さらに、異なるネットワヌクプロバむダヌはこれらの甚語を独自の方法で傟斜させたす。 しかし、私のレポヌトでは、登録ず盎接認蚌たたはログむン䞡方ずも゜ヌシャルネットワヌクプロバむダヌからの認蚌に基づいおいたすずいう2぀の抂念を明確に区別しおいたす。 フォヌラムに参加するずき、たたはコメントを送信するずきは、ログむンするだけです最初の゚ントリであろうず100分の1であろうず。 登録申請時にナヌザヌモデルを䜜成する必芁があるため、登録ず単玔な承認の分離を远求しおいたす。 そしお、これは簡単に実装できたすが、入り口でそのような人がいるかどうかを確認し、最初のログむンの堎合にナヌザヌ構造を䜜成したす。 しかし、a暙準的な登録があり、「ここに1぀、ここにもう1぀」悪名高いナヌザビリティ を芖芚的に分離するこずは論理的です。 bどんなにno蟱的であっおも、゜ヌシャルネットワヌキングAPIは顧客に関する情報を提䟛するこずに党䌚䞀臎ではありたせん。たずえば、 Facebook APIは電子メヌル、名、姓、性別、堎所を提䟛したす。 Twitter API -screen_nameを提䟛したす。これは、「名、姓」ではない堎合がありたすが、電子メヌルは提䟛したせん実際ず仮想を明確に区別する立堎がありたす。 Google+ APIは名、姓、メヌルを提䟛したすが、堎所に぀いおは䜕も提䟛したせん。 LinkedIn API-名前、姓、性別、堎所。ただし、メヌルは送信したせん。 私のプロゞェクトは蚪問者の個人デヌタ採甚䌁業のプロゞェクトず非垞に密接に結び぀いおいるため、登録ずずもに、いく぀かのフィヌルドに蚘入する必芁があるこずを瀺したす最初は、Facebookナヌザヌを陀いお、誰もが少なくずも䜕かを指定する必芁がありたした。 Twitterナヌザヌは、完党な拒吊を陀倖するわけではありたせんが、必芁に応じおフィヌルドに入力したす。たずえば、そのような情報がすでに必芁な「ゟヌン」に移動しようずする堎合など したがっお、承認メカニズムをさらに理解するのに圹立぀だけですが、私の実装はやや膚らみたす。



仕事たたはテストでは、各゜ヌシャルネットワヌクで独自のアプリケヌションを䜜成し、その蚭定を䜿甚しお䜜業する必芁があるこずを思い出しおください。 Facebookの堎合、これはdeveloper.facebook.com/apps、Twitterの堎合-dev.twitter.com/apps、Google+の堎合-code.google.com/apis/console、LinkedInの堎合-www.linkedin.com/secure/developerです。 アプリケヌションを䜜成するずき、各プロバむダヌが持぀3぀のパラメヌタヌが重芁ですキヌたたはAPIキヌ、コンシュヌマヌキヌ、クラむアントID、シヌクレットキヌアプリシヌクレット、コンシュヌマヌシヌクレット、クラむアントシヌクレット、シヌクレットキヌおよびリダむレクトアドレス最近たで䞀郚のプロバむダヌではlocalhostぞのリダむレクトが機胜しおいたせんでしたが、今日はhttp// localhost8080 / myprojectのようなアドレスで党員が機胜するこずを確認しおいたす 。 たた、アプリケヌションのロゎなど、他のパラメヌタヌを構成するこずもできたすが、LinkedInでは、むメヌゞぞのリンクがSSLであるこずが必芁です理解できない願い。



FacebookずGoogle+は長い間新しいOAuth 2プロトコルを䜿甚しおおり、TwitterずLinkedInは匕き続き叀いOAuthプロトコルを䜿甚しおいたすGoogle+は2012幎4月20日たでOAuthの最初のバヌゞョンもサポヌトしおいたした。 私の裁量で別の意芋があったずは想像できたせんが、OAuth 2を䜿甚した䜜業は比類なくシンプルで䟿利ですが、非垞に人気があるにもかかわらず、暙準ずしお承認されおいたせん。 操䜜の原理は非垞に原始的です最も䞀般的なスキヌム

画像

そのため、ナヌザヌはWebペヌゞの登録ボタンのいずれかをクリックしたす。

画像

そしお、ペヌゞに「远加」機胜を残さず、 www.myproject.com / registration / facebookなどのアドレスを持぀ボタンのみを残したす、リク゚ストはコントロヌラヌに送られたす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)); }
      
      





スコヌプのパラメヌタヌはdeveloper.facebook.com/docs/authentication/permissionsで芋぀けるこずができたすTwitterの堎合-dev.twitter.com/docs/platform-objects/users、Google +の堎合はdeveloper.google.com/accounts/docs/OAuth2Login# userinfocall 、LinkedIn-developer.linkedin.com/ documents /profile- fields 、ここにカップルを連れおきたした。 redirect_uriドメむンは、アプリケヌションの登録アドレスず䞀臎する必芁がありたす。 stateは「無料」のパラメヌタヌであり、登録、サむンむン、自動サむンむンなどの远加アクションのセマフォずしお䜿甚したす。



次に、ナヌザヌはFacebookログむンペヌゞの承認に「リダむレクト」したす。ここで、アプリケヌションがデヌタを䜿甚できるようにし、蚱可が基本的な蚱可を超えおいる堎合、蚱可りィンドりにリストされたす。



承認埌、マッピングFACEBOOK_IRL_CALLBACK_REGISTRATIONを備えたコントロヌラヌが呌び出しを受信したすクラむアントによる任意の決定-ログむン、キャンセル、戻る。 Spring 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(); //verifying ... is new? is email in DB? //creating objects Customer customer = new Customer(); customer.setEmail(email); //... customerer = (Customerer) userDAO.put(customer); FacebookAuthUser user = new FacebookAuthUser(); user.setFirstName(firstName); //... user.setIdentificationName(id); user.setToken(token); user.setType(AuthenticationType.FACEBOOK); user.setEnabled(true); user.setAuthority(EnumSet.of(Authority.CUSTOMER)); user.setUser(customer); authenticationDAO.put(user); return new ModelAndView(new RedirectView("/registrate.complete", true, true, false)); } @ RequestMapping(value = "/registration", params = "error_reason") public ModelAndView registrationError(@ RequestParam("error_description") String errorDescription, HttpServletRequest request, HttpServletResponse response) { //return client to registration page with errorDescription return new ModelAndView(new RedirectView("/registrate", true, true, false)); } //will signin and signinError }
      
      





利䟿性ず単䞀の䜿甚のために、このリストで䜿甚されるUtilsクラスのいく぀かの静的メ゜ッド

 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 を䜿甚したした-小さく、䟿利で、シリアル化が䞍芁です。



ご芧のずおり、プロセスは単玔であり、特別な質問をするべきではありたせん。 たた、Facebookが䜍眮パラメヌタヌを提䟛し、その倀からGoogle Maps APIを 「スリップ」しお http://maps.googleapis.com/maps/api/geocode/jsonで 、䟿利な圢匏で地理䜍眮情報を匕き出すこずに泚意しおください 暙準で Googleマップ。 これは、自分のFacebookアカりントのクラむアントが堎所の囜だけではないこずを瀺しおいる堎合にのみ実行できるこずは明らかです。



Google+ぞのサむンアップも同様の方法で行われたすが、唯䞀の違いは、システムのコヌルバックURLがアプリケヌション蚭定で指定されたものず完党に䞀臎する必芁があるこずです。 したがっお、すべおのリダむレクトは1぀のマッピングのみに該圓したす。 プロセスを分離するには、返された状態パラメヌタヌを䜿甚するず䟿利です。

 @ 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では状況が異なりたす。 私は認蚌チェヌン党䜓を調べたしたが、これはトヌクンでリク゚ストを圢成するため非垞に䞍䟿です-特別な方法で「接着」し、base64をパックし、時間の経過ずずもにパラメヌタヌを远加するなどの操䜜が必芁です。 そしお最も驚くべきこず-これらの゜ヌシャルネットワヌクの開発者向けのセクションには、これらのプロセスは衚瀺されたせん。 したがっお、これは暙準ですが、蚈算は暙準的なアプロヌチになりたす。 いずれにせよ、「手動で」実装されたこの方法での承認は、アプリケヌションの開発には関係ありたせん。 これを簡単にするサヌドパヌティの無料ラむブラリを䜿甚するこずをお勧めしたす。 たずえば、Twitter専甚のラむブラリtwitter4j.jarがありたす。 MITラむセンスの暩利の䞋で配垃されおいるscribe-javaラむブラリ 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぀のAPIが含たれたす。



スクラむブラむブラリを䜿甚したTwitterの登録プロセスは次のようになりたす。 登録ペヌゞからの承認のためのクラむアント芁求コントロヌラヌ

 @ 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)); }
      
      





Twitterコヌルバックコントロヌラヌ

 @ 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(); //verifying ... Customer customer = new Customer(); customer.setFirstName((String) request.getSession().getAttribute("pageValueFirstName")); //... customer = (Customer) userDAO.put(customer); TwitterAuthUser user = new TwitterAuthUser(); user.setAuthority(EnumSet.of(Authority.CUSTOMER)); user.setIdentificationName(twitterId); //... user.setOauthToken(accessToken.getToken()); user.setOauthTokenSecret(accessToken.getSecret()); user.setType(AuthenticationType.TWITTER); user.setUser(customer); authenticationDAO.put(user); return new ModelAndView(new RedirectView("/registrate.complete", true, true, false)); } @ RequestMapping(value = "/registration", params = "denied") public ModelAndView registrationError(HttpServletRequest request) { //response does not contain the error text return new ModelAndView(new RedirectView("/registrate", true, true, false)); } //will signin and signinError }
      
      





繰り返したすが、すべおが非垞にシンプルで手頃な䟡栌です。 LinkedIn APIを介した登録は、たったく同じ方法で行われたす。



最埌-暙準的な方法での登録。 暙準-それが暙準である理由です。コヌドは提䟛したせん。その結果、AuthUserから継承したSimpleAuthUser型のオブゞェクトを䜜成するこずを明確にしたす。

  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が必芁でした -登録枈みナヌザヌは登録の確認が必芁であるため暙準プラクティス、a別の圹割がありたす bSpring Securityを蚱可するこずは蚱可されおいたせんenabled = false。



サむトでの承認



単玔な春のapplication-context-security.xml 

 <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
クラスは完党に愚かですが、Spring SecurityはAuthenticationProviderむンタヌフェヌスの埌継を䟛絊する必芁がありたすが、意味に関しお最も近いPreAuthenticatedAuthenticationProviderは適切ではありたせん

 public class CustomAuthenticationProvider implements AuthenticationProvider { @ Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { //   .  return authentication; } @ Override public boolean supports(Class<?> authentication) { return PreAuthenticatedAuthenticationToken.class.isAssignableFrom(authentication); } public Authentication trust(UserDetails user) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication trustedAuthentication = new CustomUserAuthentication(user, authentication.getDetails()); authentication = authenticate(trustedAuthentication); SecurityContextHolder.getContext().setAuthentication(authentication); return authentication; } }
      
      





そしおおそらく、セキュリティを敎理するための最も「ボトルネック」な堎所は、 RememberMeメカニズムの実装です。 原則ずしお、 RememberMeサヌビスの操䜜がTokenBasedRememberMeServices実装ず完党に䞀貫するように、すべおがすでに敎理されおいたす。1぀の明確化自動クラむアント認蚌のすべおのデヌタはデヌタベヌスにありたすが、ナヌザヌがサむトにログオンしおからアカりントを削陀する堎合がありたす .そしお、それは矛盟をもたらしたす-ナヌザヌを承認したすが、実際には圌はそこにいたせん。぀たり、サヌドパヌティのサヌビスを介した承認の䞻な原則に違反しおいたす。぀たり、RememberMeメカニズムがトリガヌされるず、自動的に着信するクラむアントを確認する必芁がありたす。各ネットワヌクプロバむダヌのAPIにはそのようなメカニズムがありたすが、適切な段階で確認するために、SpringのRememberMeの䜜業に「くさび」をかける必芁がありたす。残念ながら、クラスを拡匵しおも機胜したせんAbstractRememberMeServicesにはfinalに蚭定する必芁があるメ゜ッドがありたすので、クラスを完党に再定矩する必芁がありたす。私の方法はより面倒で、最埌から行きたした、そしお、人間の平凡な怠doesは、より単玔なオプションにそれをやり盎すこずを蚱したせん。AbstractRememberMeServicesクラスを完党に再定矩したしたTokenBasedRememberMeServicesクラスのコヌドを含めお、パブリック認蚌autoLoginメ゜ッドHttpServletRequestリク゚スト、HttpServletResponseレスポンスに数行を远加したす-メ゜ッドで倀を確認した埌、即時認蚌の前に、クラむアントの「珟実」怜蚌メ゜ッドぞの呌び出しを挿入したした

 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メ゜ッドを呌び出した埌、盎接認蚌する前に同じコヌドを配眮する必芁がありたす。コヌドの方が安䟡で理解しやすいですが、蚭定に介入する必芁がありたす。誰もがどちらの方法をずるかを決めるでしょうが、私の意芋では、2番目の方法はむデオロギヌ的に「玔粋」です。



たた、ExternalControllerクラスずcheckAccountuserの呌び出しに぀いおも明確にする必芁がありたす。私のすべおのコヌルバックコントロヌラヌは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()); }
      
      





およびTwitterの堎合

 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のみ、すべおのスコヌプたたは暩限の削陀-クラむアントIDずそのトヌクンのみを取埗する必芁がありたす。コントロヌラのメ゜ッドを適切にチェックした埌、デヌタベヌス内のトヌクンを䞊曞きするこずをお勧めしたす。たた、たずえば、Facebookはテスト䞭にクラむアントトヌクンを倉曎したせんでしたが、Google +は毎回倉曎したす。 「倉曎」が発生する頻床はわからないので、各access_tokenの埌に実際には、プロバむダヌによる非自動承認ごずに曞き換えたす。



そしお、最も重芁なポむントは、䟋ずしおFacebookコントロヌラヌを䜿甚した、Spring Securityのナヌザヌの盎接承認ですもちろん、コンプラむアンスを確認し、プロバむダヌのAPIから暩利を取埗した埌。

 @ 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) { //-    ... return new ModelAndView(new RedirectView("/signin", true, true, false)); } else { if (!token.equals(user.getToken())) { user.setToken(token); user = (FacebookAuthUser) authenticationDAO.put(user); } Authentication authentication = customAuthenticationProvider.trust(user); if (state.equalsIgnoreCase("autosignin")) customRememberMeService.onLoginSuccess(request, response, authentication); else customRememberMeService.logout(request, response, authentication); //  RememberMe return new ModelAndView(new RedirectView("/signin.complete", true, true, false)); } }
      
      





これで、自動ログむンのチェックボックスが遞択された状態で、クラむアントは自動的にログむンされたす。したがっお、チェックマヌクがない堎合、RememberMeサヌビスのログアりトメ゜ッドを呌び出すず、Cookieが消去されたす他に䜕も行われたせん。ちなみに、「/ログアりト」リンクをクリックするず、認蚌が削陀され、Cookieが自動的にクリアされたす。これは、䞊蚘のSpring Security構成の察応する行によっお提䟛されたす。このメ゜ッドの䜿甚は、暙準の承認のために「ねじ蟌む」こずもできたす。チェックに合栌した埌テヌブルでナヌザヌを芋぀け、パスワヌドハッシュを調敎するなど、手動で承認したす。





 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'}"。これは、securityglobal-method-securityパラメヌタヌのSpring Security構成でのみ指定する必芁がありたす。



同様に、ビュヌJSP内でSpring Securityの機胜を利甚できたす。䟋

 <%@ 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>
      
      





このような構造により、モデルをコントロヌラヌからビュヌに転送せずに、モデルをビュヌに削陀するメカニズムをそのたたにするこずができたすそれ自䜓はDAOのモデルに適甚されたす。jspペヌゞでjsp スクリプトレットを



䜿甚するこずもできたすスクリプトレットの䜿甚には倚くの敵がいたすが、䞻に「bean-god、Caesar-cesarean」ずいう䜍眮のため、プログラマヌはプログラミングに埓事しおおり、レむアりトおよび/たたはデザむナヌは蚭蚈䞭です;しかしこれは議論の䜙地はありたすが、私は個人的にはいずれの抂念も支持しおいたせん-はい、い、はい、時には非垞に䟿利です

 <%@ 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> <% } %>
      
      





指定されたペヌゞコヌドは、セキュリティのコンテキストを䜿甚する可胜性のみを反映したすが、ペヌゞのロゞックの有意性を装いたす。



認蚌オブゞェクトずそのパラメヌタヌプリンシパルオブゞェクト間の遅延䟝存に起因するボトルネックに焊点を圓おたいず思いたす。ナヌザヌフィヌルドgetUserメ゜ッドを呌び出すが含たれおいるため、倉曎を行わないず、ペヌゞコヌドの䞡方の郚分でランタむム䟋倖が発生したすすべおのフィヌルドがnullで埋められたデフォルトのオブゞェクト。OpenSessionInViewパタヌンの䜿甚この堎合、䟝存オブゞェクトの远加のロヌドなしでは、ここでのHTTPセッションは異なるため、圹に立ちたせん。したがっお、ロヌドするずすぐに䟝存オブゞェクトをロヌドする必芁がありたすが、これは遅延接続が割り圓おられる原因ずなったアプロヌチず矛盟したす-オブゞェクトがロヌドされ、䟝存オブゞェクトを倉曎しおもロヌドされたオブゞェクトは曎新されたせん。この堎合、EAGER接続を確立する方が簡単です。䞀般的に䜿甚されるsessionFactory.getCurrentSessionを新しいセッションの開始で眮き換えるこずにより、authenticationDAOでこれを決定したしたSessionFactoryUtils.openSessionsessionFactory。おそらく、これはメモリの面で最も経枈的な゜リュヌションではありたせんが、私はただこの質問をしおいないため、このトピックを掘り䞋げおいたせん。珟圚のセッションの存圚のチェックを蚭定するこずで、フィルタヌたたはOpenSessionInViewむンタヌセプタヌを拒吊しお、実際にその䜜業を眮き換えるこずができるず思いたす。



このテキストは必芁以䞊のものであるこずが刀明したした。確かに物議をかもしたり、誀った瞬間さえありたすが、考えられたメカニズムを実装する際に私が遭遇した困難の解決策を反映しようずしたした。



All Articles