闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾惧綊鏌熼梻瀵割槮缁惧墽鎳撻—鍐偓锝庝簼閹癸綁鏌i鐐搭棞闁靛棙甯掗~婵嬫晲閸涱剙顥氬┑掳鍊楁慨鐑藉磻閻愮儤鍋嬮柣妯荤湽閳ь兛绶氬鎾閻樻爠鍥ㄧ厱閻忕偛澧介悡顖氼熆鐟欏嫭绀€闁宠鍨块、娆戠磼閹惧墎绐楅梻浣告啞椤棝宕橀敐鍡欌偓娲倵楠炲灝鍔氭繛鑼█瀹曟垿骞橀懜闈涙瀭闂佸憡娲﹂崜娑⑺囬鐔虹瘈闁冲皝鍋撻柛鏇炵仛閻や礁螖閻橀潧浠滄俊顐g箓椤曪綁顢氶埀顒€鐣烽悡搴唵妞ゅ繋鐒﹀▍濠囨煙椤旂瓔娈滈柡浣瑰姈閹柨鈹戦崼婵嗘瘓闂佽娴烽幊鎾诲箟閿涘嫭宕查柛鏇ㄥ幗椤洟鏌熼悜妯诲鞍缂傚秴娲弻鏇熺箾閸喖濮㈤梺鑽ゅ枂閸斿矂鈥旈崘顔嘉ч幖绮光偓鑼嚬缂傚倷绶¢崰妤呭箰閹间焦鍋╅柣鎴f绾偓闂佺粯鍔曠粔鍫曞窗閺嶎厼绠栨繛鍡樻尭缁狅絾绻濋棃娑欐悙妞わ腹鏅犲娲箮閼恒儲鏆犻梺鎼炲妼濠€鍗炍i幇鏉跨閻庢稒锚椤庢挻绻濆▓鍨灍闁糕晛鐗婄粋宥呪攽鐎n亞鐤勯梺闈浥堥弲娑㈡倷婵犲洦鐓忓┑鐐茬仢閳ь剚顨堢划璇差潩椤掑瀵岄梺闈涚墕濡稒鏅堕鍕厽闁哄啯鍨垫晶鎾煟閹垮啫澧存い銏☆殜瀹曟帒螖閳ь剚绂嶆ィ鍐╁仭婵炲棗绻愰顏嗙棯閻愵剚鍊愰柡灞剧⊕閹棃鏁愰崱妯荤槗闁诲孩顔栭崳顕€宕戞繝鍥╁祦婵☆垵鍋愮壕鍏间繆椤栨粎甯涙い蹇曞枛濮婄粯鎷呴懞銉с€婇梺闈╃秶缁犳捇鐛箛娑欐櫢闁跨噦鎷�濠电姷鏁告慨鐑藉极閸涘﹥鍙忛柣鎴f閺嬩線鏌涘☉姗堟敾闁告瑥绻橀弻锝夊箣閿濆棭妫勯梺鍝勵儎缁舵岸寮婚悢鍏尖拻閻庨潧澹婂Σ顔剧磼閻愵剙鍔ゆい顓犲厴瀵鏁愭径濠勭杸濡炪倖甯婇悞锕傚磿閹惧墎纾藉ù锝呮惈灏忛梺鍛婎殕婵炲﹤顕f繝姘亜闁稿繐鐨烽幏濠氭煟鎼淬劍娑у鐟帮工鍗辨い鏂垮⒔绾捐棄霉閿濆懏鎯堥崯鎼佹⒑閸濄儱校闁绘濮撮锝嗙節濮橆儵鈺呮煃閸濆嫬鈧憡绂嶅⿰鍫熲拺闁告稑锕︾粻鎾绘倵濮橆剚鍤囧┑顔瑰亾闂侀潧鐗嗗Λ娑㈠储闁秵鈷戦梻鍫熻儐瑜版帒纾块梺顒€绉撮悞鍨亜閹哄棗浜惧┑鐘亾闂侇剙绉寸粻鏌ユ煏韫囨洖袥婵℃彃鐗撻弻鏇$疀閺囩倫銏ゆ煠閺夎法浠㈤柍瑙勫灴閸┿儵宕卞Δ鍐ф樊婵$偑鍊栧▔锕傚炊椤垶顥夐柣搴$畭閸庨亶藝娴兼潙鐓曢柟瀵稿Х绾惧ジ鎮楅敐搴′航闁稿簺鍎甸弻娑欐償閵忕姴顫庣紓浣介哺鐢偤骞忛悩璇茬闁圭儤鎸婚鎺戔攽閻樻鏆滅紒杈ㄦ礋瀵偆鎷犻懠顒佹闂佺粯姊婚埛鍫ュ极瀹ュ棙鍙忔俊顖氥仒閸氼偊鏌℃径瀣€愭慨濠勭帛閹峰懘宕妷锔锯偓顔尖攽閳╁啨浠犻柛鏂块叄楠炲繒鈧綆鍠栭拑鐔兼煏婢跺牆鍔ら柨娑欑洴濮婅櫣鎲撮崟顐ゎ槰濡炪倖娉﹂崶褏顦ㄩ梺閫炲苯澧撮柟顔煎槻楗即宕橀悙顑芥瀰闁诲孩顔栭崰妤呭箖閸屾凹鍤曟い鏇楀亾鐎规洖銈搁幃銏ゅ传閸曨偅杈堥梻鍌氬€烽懗鍓佸垝椤栨娲冀椤撶偟锛欓梺闈╁瘜閸樻悂宕戦幘鎰佹僵闁绘劦鍓欓锟�
开发学院软件开发Java 面向 Java Web 应用程序的 OpenID,第 2 部分: 为... 阅读

面向 Java Web 应用程序的 OpenID,第 2 部分: 为单点登录身份验证编写 OpenID 提供者

 2010-04-27 00:00:00 来源:WEB开发网 闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾惧綊鏌熼梻瀵割槮缁惧墽鎳撻—鍐偓锝庝簻椤掋垺銇勯幇顖毿撻柟渚垮妼椤粓宕卞Δ鈧獮濠勭磽閸屾艾鈧懓顫濋妸鈺佺疅缂佸顑欓崥瀣煕椤愵偅绶氱紓鍐╂礋濮婂宕掑▎鎴М濠电姭鍋撻梺顒€绉甸幆鐐哄箹濞n剙濡肩紒鎰殜閺屸€愁吋鎼粹€茬敖婵炴垶鎸哥粔鐢稿Φ閸曨垰鍐€妞ゆ劦婢€濞岊亪姊虹紒妯诲蔼闁稿海鏁诲濠氭晲婢跺﹤宓嗛梺缁樺姈缁佹挳宕戦幘璇叉嵍妞ゆ挻绋戞禍鐐叏濡厧浜鹃悗姘炬嫹闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾惧綊鏌熼梻瀵割槮缁惧墽鎳撻—鍐偓锝庝簼閹癸綁鏌i鐐搭棞闁靛棙甯掗~婵嬫晲閸涱剙顥氬┑掳鍊楁慨鐑藉磻濞戔懞鍥偨缁嬫寧鐎梺鐟板⒔缁垶宕戦幇鐗堢厵缂備焦锚缁椦囨煃瑜滈崜锕傚矗閸愵煈娼栭柛婵嗗珔瑜斿畷鎯邦槾濞寸姴銈稿铏规嫚閼碱剛顔夐梺鐓庣秺缁犳牠骞冩ィ鍐╁€婚柦妯侯槼閹芥洟姊洪棃娑辨濠碘€虫喘瀹曘垽鎮介崨濞炬嫼闁荤喐鐟ョ€氱兘宕箛娑欑厱闁绘ê纾晶鐢告煏閸℃鈧湱缂撴禒瀣窛濠电姴瀚獮鍫ユ⒑绾懎顥嶉柟娲讳簽濡叉劙寮撮悢鍝勨叞闂傚倸鍊风欢姘缚瑜嶇叅闁靛牆娲犻崑鎾愁潩椤愩垹绁梺缁樹緱閸o綁鐛幒鎳虫棃鍩€椤掑倻涓嶉柨婵嗘缁♀偓闂傚倸鐗婄粙鎴﹀汲濞嗗緷鐟扳堪閸垻鏆梺鍝勭焿缂嶄焦鎱ㄩ埀顒勬煃閹増纭炬繝鈧潏銊х彾闁哄洨鍠撶弧鈧┑顔斤供閸橀箖宕㈤悽鍛娾拺缂備焦锚婵箓鏌涢幘鏉戝摵闁诡喗蓱濞煎繘濡搁妶鍥╃暰闂備礁婀辩划顖滄暜閻愬瓨娅犳繛鎴炴皑绾捐偐绱撴担璐細婵炴彃顕埀顒冾潐濞叉牕鐣烽鍐簷闂備礁鎲¢崝鏇㈠疮閻樿绀堟繝闈涚墛瀹曞弶绻涢幋鐐ㄧ細闁哄棗妫楅埞鎴︽偐鏉堫偄鍘¢梺杞扮劍閻楁粎妲愰幘瀛樺濞寸姴顑呴幗鐢告⒑閸︻厽鍤€婵炲眰鍊濋幃楣冩倻閽樺顔婇梺瑙勬儗閸樹粙宕撻悽鍛娾拺闁荤喐婢橀幃渚€鏌i幒鐐电暤闁诡喗顨婇獮鏍ㄦ媴閸忓瀚藉┑鐐舵彧缁插潡宕曢妶澶婂惞闁逞屽墴濮婃椽骞栭悙娴嬪亾閺嶎厽鍋嬮柣妯垮吹瀹撲線鐓崶銊р姇闁哄懏鎮傞弻銊╂偆閸屾稑顏�婵犵數濮烽弫鍛婃叏閻戣棄鏋侀柛娑橈攻閸欏繘鏌i幋锝嗩棄闁哄绶氶弻娑樷槈濮楀牊鏁鹃梺鍛婄懃缁绘垿濡甸崟顖氱闁告鍋熸禒鑲╃磽娴e搫顎岄柛銊ョ埣瀵鈽夐姀鐘电杸闂傚倸鐗婄粙鎺楁倶閸儲鍊甸柣鐔哄閸熺偟绱掔拠鎻掓殻濠碉紕鏁诲畷鐔碱敍濮橀硸鍟嬮梺璇查叄濞佳囧箺濠婂牊鍋╁┑鍌氭啞閳锋垹鐥鐐村婵炲吋鍔栫换娑㈡嚑椤掆偓閺嬪孩銇勯銏㈢缂佽鲸甯掕灒闁兼祴鏅滈崵宀勬⒒娓氣偓閳ь剛鍋涢懟顖涙櫠椤旇偐鏆嗛柨婵嗙墕閸斿灚銇勯敂鐣屽弨闁诡噯绻濇俊鑸靛緞鐎n剙甯鹃梻浣稿閸嬪懐鎹㈤崘顔肩;妞ゅ繐鎳愮粻鍓р偓鐟板閸犳洜鑺辨繝姘畾闁绘柨鍚嬮埛鎴︽倵閸︻厼校闁靛棗鍟撮弻銈夊礃閼碱剙鐓熼悗瑙勬礃缁诲牓寮崘顔肩劦妞ゆ帒瀚ч埀顒佹瀹曟﹢顢欓崲澹洦鐓曢柍鈺佸枤濞堟﹢鏌i悢绋垮婵﹥妞介幃鈩冩償閳╁啯鐦i梻浣虹帛閻楁洟濡剁粙璺ㄦ殾闁绘垶岣跨弧鈧梺鎼炲劀閸愩劎銈梻鍌欑窔濞佳勵殽韫囨洘顫曢柡鍥ュ灩閸屻劍銇勮箛鎾跺闁抽攱鍨块弻鐔兼嚃閳轰椒绮堕梺鍛婃⒐椤ㄥ﹪寮婚敓鐘插窛妞ゆ棃鏁慨鍥╃磽娴gǹ鈧湱鏁悢濡撳洨鈧潧鎽滅壕濂稿级閸稑濡肩紒妤佺缁绘盯鎮℃惔锝囶啋闂佺硶鏂侀崜婵嬪箯閸涘瓨鐓ラ悗锝呯仛缂嶆姊婚崒姘偓宄懊归崶褜娴栭柕濞炬櫆閸婂潡鏌ㄩ弴鐐测偓鍝ョ不閺屻儲鐓曢柕澶樺枛婢ь噣鏌$€b晝绐旈柡宀€鍠栧畷婊嗩槾閻㈩垱鐩弻锟犲川椤旇棄鈧劙鏌$仦璇插闁诡喓鍊濆畷鎺戔槈濮楀棔绱�闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾惧綊鏌熼梻瀵割槮缁惧墽鎳撻—鍐偓锝庝簻椤掋垺銇勯幇顖毿撻柟渚垮妼椤粓宕卞Δ鈧獮濠勭磽閸屾艾鈧懓顫濋妸鈺佺疅缂佸顑欓崥瀣煕椤愵偅绶氱紓鍐╂礋濮婂宕掑▎鎴М濠电姭鍋撻梺顒€绉甸幆鐐哄箹濞n剙濡肩紒鎰殜閺屸€愁吋鎼粹€茬敖婵炴垶鎸哥粔鐢稿Φ閸曨垰鍐€妞ゆ劦婢€濞岊亪姊虹紒妯诲蔼闁稿海鏁诲濠氭晲婢跺﹤宓嗛梺缁樺姈缁佹挳宕戦幘璇叉嵍妞ゆ挻绋戞禍鐐叏濡厧浜鹃悗姘炬嫹  闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾惧綊鏌熼梻瀵割槮缁炬儳缍婇弻锝夊箣閿濆憛鎾绘煕閵堝懎顏柡灞诲€濆畷顐﹀Ψ閿旇姤鐦庡┑鐐差嚟婵潧顫濋妸褎顫曢柟鎹愵嚙绾惧吋绻涢崱妯虹瑨闁告﹫绱曠槐鎾寸瑹閸パ勭彯闂佹悶鍔岄悥鍏间繆閹绢喖绀冩い鏃傚帶缁愭盯姊洪崫鍕垫Ч闁搞劌缍婂畷銏犆洪鍛偓鍨殽閻愯尙浠㈤柛鏃€纰嶉妵鍕晜鐠囪尙浠搁悗瑙勬穿缁绘繈鐛惔銊﹀殟闁靛/鍐ㄥ闂傚倸饪撮崑鍕洪敃鈧叅闁哄秲鍔庢晶锟犳⒒閸屾瑦绁版い鏇嗗應鍋撻崹顐㈡诞鐎规洘绮撻幃銏$附婢跺绋侀梻浣瑰劤缁绘劕锕㈡潏鈺侇棜闁稿繘妫跨换鍡樸亜閺嶃劎顣查柟顖氱墛閵囧嫰顢曢姀鈶裤垺銇勯鍕殻濠碘€崇埣瀹曞崬螖閳ь剟锝炴惔銊︹拺闁告稑饪撮悞濂告煕閵夋垵鎳庡銊モ攽閻橆喖鐏辨繛澶嬬閻у矂姊虹紒妯荤叆闁告艾顑夐幃锟犲Ψ閳哄倸鈧敻鏌ㄥ┑鍡涱€楅柛妯绘尦閺屾稓鈧綆鍋呯亸鎵磼缂佹ḿ绠撴い顐g箞椤㈡ê鈽夊▎蹇d紪闂備浇宕甸崰鎰垝鎼淬垺娅犳俊銈呭暞閺嗘粓鏌熼悜妯诲暗妞も晜褰冮湁闁绘挸娴烽幗鐘绘煟閹惧瓨绀嬮柡宀€鍠栭獮鍡氼檨闁搞倗鍠栭弻宥夋寠婢舵ɑ鈻堥梺鍝勮嫰缁夊墎妲愰幒鎳崇喖鎳¢妶鍛辈濠碉紕鍋戦崐銈夊磻閸涱垱宕查柛顐犲劚缁犵姵绻濇繝鍌涘櫣闁哄鐗婃穱濠囶敍濠垫劕娈銈呯箰缂嶅﹤顫忔繝姘<婵炲棙鍩堝Σ顕€姊虹涵鍜佸殝缂佽鲸娲滈崚鎺戔枎閹惧磭顓洪梺鎸庢煥閹碱偅绂嶆總鍛娾拺闁告繂瀚峰Σ褰掓煕閵娧冩灈鐎殿喗鎮傚顕€宕奸悢鍝勫箰闂備礁鎲¢崝褏寰婇懞銉ь洸闁告挆鍛紳婵炶揪缍€濞咃絿鏁☉姘辩<閻庯綆鍋呯亸鎵磼閸屾稑娴柡浣稿暣瀹曟帒顫濋幉瀣簥濠电姵顔栭崰妤呮晝閳哄懎绀堥柨鏇炲€归崐鐢告煙閹澘袚闁抽攱甯掗湁闁挎繂鎳忛幉鎼佹煥濞戞ḿ肖缂佽鲸甯¢、姘跺川椤撶姳妗撴俊銈囧Х閸嬬偤宕归崹顔炬殾闁割偅娲﹂弫鍡涙煕鐏炵偓鐨戦柕鍫畵濮婅櫣鎷犻幓鎺戞瘣缂傚倸绉村Λ娆戠矉瀹ュ鍐€妞ゆ挾鍋熼崫妤佺箾鐎电ǹ孝妞ゆ垵妫濋幃鈥斥枎閹剧补鎷哄銈嗘尪閸斿酣鎮鹃崡鐑嗙唵鐟滄粓宕归柆宥呂﹂柛鏇ㄥ灠濡﹢鏌涢…鎴濇灀闁稿鎸鹃幉鎾礋椤撴稒鏁靛┑鐘垫暩婵潙煤閿曞倸纾归梺鍨儍娴滄粓鐓崶銊﹀碍妞ゅ繈鍊濋弻娑氣偓锝庡亝瀹曞瞼鈧鍠栭埀顒傚櫏濡俱劌鈹戦悙鎻掓倯闁绘娲熼崺鐐哄箣閿旇棄浜归柣搴℃贡婵挳藟濠靛鈷戠紒瀣劵椤箓鏌涢弬鍧楀弰妤犵偛顦~銏犵暆閳ь剟鎮块埀顒€鈹戦悙鏉戠仸瀹€锝呮健閹潡宕ㄧ€涙ǚ鎷洪梺闈╁瘜閸欌偓婵$偓鎮傞弻娑㈡偐閹颁焦鐤侀悗娈垮枟閻擄繝鐛弽銊﹀闁革富鍘煎鎶芥⒒娴h櫣甯涙繛鍙夌墵瀹曟劙宕烽娑樹壕婵ḿ鍋撶€氾拷
核心提示:OpenID 是一个可靠的身份管理和身份验证解决方案,在世界各地有许多用户,面向 Java Web 应用程序的 OpenID,第 2 部分: 为单点登录身份验证编写 OpenID 提供者,它让最终用户可以使用一个得到广泛认可的用户 ID 访问许多网站和其他在线资源,在 第 1 部分 中,与任何规范一样,OpenID 身

OpenID 是一个可靠的身份管理和身份验证解决方案,在世界各地有许多用户。它让最终用户可以使用一个得到广泛认可的用户 ID 访问许多网站和其他在线资源。在 第 1 部分 中,我介绍了 OpenID 身份验证规范,讲解了如何使用 openid4java 库实现把它集成到 Java Web 应用程序中。

第 1 部分主要关注 OpenID 依赖方 (RP),RP 是使用 OpenID 进行注册和身份验证的在线资源(比如网站或 MP3)。OpenID 身份验证 规范的另一半是 OpenID 提供者 (OP)。OP 帮助用户申请 OpenID,对用户进行身份验证以登录与 OpenID 兼容的 Web 资源。

目前已经有许多 OpenID 提供者(包括 第 1 部分 中讨论的 Java Web 应用程序注册系统所用的 OP,myOpenID),在大多数情况下不需要自己创建 OP。

在一种场景中构建自己的 OP 是有意义的:应用程序集群中的多个应用程序共享可信网络中的资源。在这种情况下,可能希望创建一个安全的 “闭环” 系统。这让用户可以同时登录所有应用程序,而不必分别登录每个应用程序,非常方便。让集群中的一个应用程序作为 OP,就可以为所有应用程序建立单点登录身份验证。

在本文中,我们要在闭环架构中编写一个 OpenID 提供者以保护许多应用程序。首先讨论一下单点登录身份验证的好处和结构,然后为集群架构编写一个简单的 OpenID 提供者。我们仍然使用 openid4java 库提供身份验证系统的核心运行时功能,从而确保我们的 OpenID 提供者符合 OpenID 身份验证规范。

单点登录身份验证

在某些企业场景中,与把所有功能构建为单一应用程序相比,把具有不同功能的应用程序组合起来更有意义。这样的应用程序集群常常是 B2B 的核心,每个参与方都提供某些服务,以此增加整个业务体系的价值。

开发这种集群的困难在于身份验证;让每个应用程序分别对最终用户进行身份验证是不可行的,至少从最终用户的角度来说不行。

在使用 OpenID 标准进行身份验证的集群系统中,每个参与的应用程序都把身份验证委托给 OP。每个应用程序确信对其功能和资源的访问是安全的,而最终用户在每次会话中只需登录一次。

我们来研究一下单点登录身份验证系统中的参与方。注意,下面讨论的架构基于 第 1 部分 中开发的示例应用程序。

OpenID 依赖方 (RP)

OpenID 依赖方 是网站或其他在线资源,它们要求对其内容的访问是安全的。RP 使用 OpenID 提供者 (OP) 验证用户的身份。RP 还可以使用 Simple Registration (SReg) 和/或 Attribute Exchange (AX) 扩展注册或识别用户的相关信息。当请求 OP 验证用户的身份时,RP 通过调用 openid4java 库发出 SReg 和 AX 请求。

OpenID 提供者 (OP)

OpenID 提供者为所有参与的应用程序提供身份验证。通过调用 openid4java 库成功地验证用户的身份之后,OP 就会满足来自 RP 的 SReg 和 AX 请求。在本文讨论的单点登录架构中,OP 处于中心位置。

编写 OpenID 提供者

在前一篇文章中,讲解了如何使用 openid4java 为 Java Web 应用程序注册系统编写依赖方。在本文中,我们按相似的过程编写 OpenID 提供者。openid4java 确保 OpenID 提供者符合 OpenID 身份验证规范,因为所有 OpenID 基础设施已经编写好了。

关于示例应用程序

示例应用程序的目的是演示 OpenID RP 和 OP 如何协作以防止未授权的资源访问。示例应用程序的流程非常明确:

用户试图访问受保护的资源。

RP 请求 OP 验证用户的身份。

如果用户还没有登录的话,OP 验证用户的身份。

RP 判断登录的用户是否有权访问受保护的资源。

示例应用程序包含 RP 和 OP 的代码,这样您可以看到它们的协作方式。在真实的场景中,不会把这两个组件部署在同一个应用程序中 — 完全没有理由这么做! — 但是把它们放在一起有助于研究它们的交互方式。

示例应用程序中的代码清单

本节中的代码清单演示 OP(和 RP)如何通过调用 openid4java API 使用 OpenID。您可能会注意到示例应用程序实际上需要的代码非常少。openid4java 确实大大简化了开发。RP 使用的代码基本上与在 第 1 部分 中看到的代码差不多,关于 RP 内部原理的更多信息参见第 1 部分。我会指出几处差异(主要与第 1 部分中没有讨论的 AX 相关)。

与为第 1 部分编写的应用程序一样,这个应用程序也使用 Wicket 作为 UI。为了减少示例应用程序中 Wicket 的内存占用量,我把 OP 用来调用 openid4java 的代码隔离在它自己的 Java 类 OpenIdProviderService 中(在 com.makotogroup.sample.model 中)。

OpenIdProviderService.java 包含几个方法,它们与 openid4java API 的使用方法对应:

getServerManager() 配置并返回 openid4java ServerManager 类的引用。

getOpEndpointUrl() 返回 OP 从 RP 接收请求的位置的端点 URL。

processAssociationRequest() 应 RP 的请求使用 openid4java 关联 OP。

sendDiscoveryResponse() 把发现响应发送给 RP。

createAuthResponse() 创建在处理身份验证请求之后发送给 RP 的 openid4java AuthResponse 消息。

buildAuthResponse() 是处理 OpenID Simple Registration 和 Attribute Exchange 请求的核心方法。

启动示例应用程序的方法是,运行 Ant [REF] 并构建 WAR 目标,然后把它复制到 Tomcat webapps 目录并启动 Tomcat。

OpenID 身份验证:步骤

当用户试图访问依赖方 (RP) 的受保护资源时,RP 要确认用户的身份是真实的(身份验证),然后决定是否授予用户访问权(授权)。本文的重点是身份验证,所以如果 OpenID 提供者 (OP) 验证了用户的身份,示例应用程序就会授予对受保护资源的访问权。在真实的场景中,RP 还会执行某种授权。

在运行示例应用程序时,会看到一个包含受保护资源的屏幕。这个过程中会发生以下事件,下面几节详细讨论这些事件:

请求访问受保护资源:用户试图访问 RP 网站上的受保护资源。

RP 执行发现:RP 向 OP 发送发现请求以建立连接和执行关联。

OP 响应发现请求:OP 通过 SReg、Attribute Exchange (AX) 或 OpenID Provider Authentication Policy (AP) 扩展发送回一个 XRDS (eXtensible Resource Descriptor Sequence),以此响应发现请求。XRDS 确认这个 OP 是用户的 OpenID 服务提供者。

RP 请求验证用户的身份:RP 向 OP 询问是否可以验证用户的身份。如果登录成功,RP 使用 SReg 和/或 AX 扩展请求某些用户信息。

OP 验证用户的身份:如果用户没有登录或者用户会话无效,就要求用户提供登录凭证。如果身份验证成功,OP 就通知 RP 并发送通过 SReg 和/或 AX 请求的数据。

RP 授予访问权:授予用户对受保护资源的访问权。在真实的场景中,大多数 RP 会在授予访问权之前检查用户的授权。

下面详细讨论每个步骤。

为什么要使用 AX 扩展?

这个示例应用程序使用 OpenID SReg 和 AX 扩展在 OP 和 RP 之间传递用户信息。这两个扩展都让 OP 和 RP 可以高效地通信。SReg 提供有限的可交换属性,而 AX 实际上可以用来交换任何信息,只要 OP 和 RP 都把它定义为属性。在集群场景中,每个可信的应用程序 (RP) 还可能定义自己的定制的 “厂商扩展”。这是改进 OP 和 RP 之间的通信的另一种方法。本文后面会进一步讨论 AX 扩展。

请求访问受保护资源

示例应用程序 包含一个受保护资源。当应用程序启动并访问 RP URL (http://localhost:8080/openid-provider-sample-app/) 时,装载以下页面:

图 1. 示例应用程序的主页面
面向 Java Web 应用程序的 OpenID,第 2 部分: 为单点登录身份验证编写 OpenID 提供者

当用户单击这个链接时,执行清单 1 中的代码:

清单 1. 包含受保护资源的应用程序主页面

package com.makotogroup.sample.wicket; 
. . . 
public class OleMainPage extends WebPage { 
 public OleMainPage() { 
  add(new OleMainForm("form")); 
 } 
 public class OleMainForm extends Form { 
  public OleMainForm(String id) { 
   super(id); 
   add(new PageLink("openIdRegistrationPage", new IPageLink() { 
    public Page getPage() { 
     return new OpenIdRegistrationPage(); 
    } 
    public Class<? extends WebPage> getPageIdentity() { 
     return OpenIdRegistrationPage.class; 
    } 
   })); 
  } 
 } 
} 

请注意清单 1 中的粗体代码。当用户单击图 1 所示的链接时,Wicket 把用户带到 OpenIdRegistrationPage(资源)。这时,调用链接的目的地,这会运行 OpenIdRegistrationPage 类的构造器。这个类有两个作用:

作为初始调用的入口点。

作为身份验证成功之后从 OP “回调” 的目标。

在发出初始调用以访问这个页面时,没有传递 Wicket PageParameters,RP 知道需要请求 OP 验证用户的身份。

RP 执行发现

为了在 RP 和 OP 之间通信,RP 必须对 OP 执行发现。从编程的角度来看,这很简单(同样是由于 openid4java 简化了编程),但这是一个重要的步骤,所以我把代码分解出来讨论一下。

RP 使用下面的代码(取自 OpenIdRegistrationPage 的构造器)发送发现请求:

 DiscoveryInformation discoveryInformation = 
  RegistrationService.performDiscoveryOnUserSuppliedIdentifier( 
     OpenIdProviderService.getOpEndpointUrl()); 

在这段代码中,RP 做两件事:

对 OP 的端点 URL 执行发现。

把本身与 OP 关联起来。(对 Diffie-Hellman 密钥交换和关联期间发生的其他活动的详细解释参见 第 1 部分。)

接下来,由 OP 处理 RP 的发现请求。

OP 响应发现请求

请记住,在示例应用程序的 RP 和 OP 端都运行 openid4java。因此,在发现 OP 的过程中,openid4java 的 RP 端向 OP 的端点 URL 发送一个空的请求。端点 URL 是联系 OP 的位置,OP 在这里接收所有来自 RP 的请求。OP 必须处理这个请求。看一下 OpenIdProviderService.getOpEndpointUrl(),会注意到端点 URL 是 http://localhost:8080/openid-provider-sample-app/sample/OpenIdLoginPage。

当 RP 向 OP 发送空的请求时,Wicket 构造 OpenIdLoginPage 并运行它的构造器,见清单 2:

清单 2. OP 入口点

 
 public OpenIdLoginPage(PageParameters parameters) throws IOException { 
  super(parameters); 
  if (parameters.isEmpty()) { 
   // Empty request. Assume discovery request... 
   OpenIdProviderService.sendDiscoveryResponse (getResponse()); 
 . . . 

注意,如果 OP 接收到空的请求,它会假设这是发现请求。然后,它创建一个 XRDS 文档并发送回请求者。

清单 3 给出 sendDiscoveryRequest() 的代码:

清单 3. 发送对发现请求的响应

 
 public static void sendDiscoveryResponse (Response response) throws IOException { 
  // 
  response.setContentType("application/xrds+xml"); 
  OutputStream outputStream = response.getOutputStream(); 
  String xrdsResponse = OpenIdProviderService.createXrdsResponse(); 
  // 
  outputStream.write(xrdsResponse.getBytes()); 
  outputStream.close(); 
 } 

这个 XRDS 文档对于 openid4java 的 RP 端的正确运行很重要。

当 RP 收到 OP 发送的 XRDS 文档时,它知道它已经联系到了这个用户的 OP。然后,RP 创建身份验证请求并发送给 OP。

RP 请求验证用户的身份

RP 请求 OP 确认是否可以验证用户的身份。它执行的一系列调用见清单 4(取自构造器):

清单 4. RP 代码把身份验证委托给 OP

 
 DiscoveryInformation discoveryInformation = 
  RegistrationService.performDiscoveryOnUserSuppliedIdentifier( 
     OpenIdProviderService.getOpEndpointUrl()); 
 MakotoOpenIdAwareSession session = 
  (MakotoOpenIdAwareSession)getSession(); 
 session.setDiscoveryInformation(discoveryInformation, true); 
 AuthRequest authRequest = 
  RegistrationService.createOpenIdAuthRequest( 
     discoveryInformation, 
     RegistrationService.getReturnToUrl()); 
 getRequestCycle().setRedirect(false); 
 getResponse().redirect(authRequest.getDestinationUrl(true)); 

首先,RP 通过端点 URL 联系 OP。这个调用可能看起来有点儿奇怪,但是请记住,在这个场景中应用程序集群使用一个可信的伙伴作为 OP。从 RP 的角度来看,验证用户提供的身份只需发现 OP 的位置,让 openid4java 构造后续交互所需的对象。OP 负责处理身份验证机制。

接下来,获取当前的 Wicket Session,把从 openid4java 获取的 DiscoveryInformation 存储起来供以后使用。我编写了一个特殊的 Session 子类 MakotoOpenIdAwareSession,这样便于在 Session 中存储 openid4java 对象。

然后,用从 openid4java 获取的 DiscoveryInformation 对象创建身份验证请求。这个对象告诉 Wicket 重定向到哪里以执行身份验证调用。

这些步骤与第 1 部分中的步骤相同。我在这里重复解释它们是因为本文使用的示例应用程序架构与第 1 部分的代码不太一样。我还希望您查看 OP 端的 API 调用,能够把它们联系在一起。

现在,RP 等待 OP 发送身份验证响应。在讨论下一个步骤之前,我们先看一下 Attribute Exchange 在用户身份验证中的作用。

OpenID Attribute Exchange 扩展

在第 1 部分中,我们简要讨论了 Simple Registration (SReg) 扩展,可以用 SReg 在 RP 和 OP 之间交换特定的信息集(由 SReg 规范定义)。看一下本文示例应用程序中的 createOpenIdAuthRequest() 方法,会注意到 RP 使用另一个扩展 OpenID Attribute Exchange (AX) 向 OP 请求信息。

与 SReg 扩展一样,OpenID Attribute Exchange (AX) 用于在 RP 和 OP 之间以一致的标准的方式交换信息。但是与 SReg 不同,AX 允许 OpenID 依赖方和提供者交换不受限制的信息,只要 RP 和 OP 都支持 AX 扩展。

简单地说,RP 通过消息请求 OP 提供特定的信息,OP 在消息中发送回这些信息。这些消息编码在浏览器重定向到的 URL 中,但是 openid4java 使用对象让代码可以使用这些信息。

RP 使用 FetchRequest 类发出 AX 请求。得到消息对象的引用之后,添加它希望从 OP 返回的属性,见清单 5:

清单 5. 包含属性的 RP FetchRequest

 
AuthRequest ret = obtainSomehow(); 
// Create AX request to get favorite color 
FetchRequest fetchRequest = FetchRequest.createFetchRequest(); 
fetchRequest.addAttribute("favoriteColor", 
    "http://makotogroup.com/schema/1.0/favoriteColor", 
    false); 
ret.addExtension(fetchRequest); 

当 OP 把信息发送回 RP 时,使用相同的构造,见清单 6:

清单 6. OP 发送回请求的属性

 
if (authRequest.hasExtension(AxMessage.OPENID_NS_AX)) { 
 MessageExtension extensionRequestObject = 
   authRequest.getExtension(AxMessage.OPENID_NS_AX); 
 FetchResponse fetchResponse = null; 
 Map<String, String> axData = new HashMap<String, String>(); 
 if (extensionRequestObject instanceof FetchRequest) { 
  FetchRequest axRequest = (FetchRequest)extensionRequestObject; 
  ParameterList parameters = axRequest.getParameters(); 
  fetchResponse = FetchResponse.createFetchResponse( 
    axRequest, axData); 
  if (parameters.hasParameter("type.favoriteColor")) { 
    axData.put("favoriteColor", registrationModel.getFavoriteColor()); 
   fetchResponse.addAttribute("favoriteColor", 
     "http://makotogroup.com/schema/1.0/favoriteColor", 
     registrationModel.getFavoriteColor()); 
  } 
   authResponse.addExtension(fetchResponse); 
 } else { 
  // ERROR 
 } 
} 

定义的每个属性有一个简单的名称和相关联的 URI。在这里,属性的简单名称是 FavoriteColor,它的 URI 是 http://makotogroup.com/schema/1.0/favoriteColor。

另外,属性必须能够转换为字符串 (以这种方式发送数据字段的示例见示例应用程序)。在定义要在 RP 和 OP 之间交换的属性时,两端对于属性的定义必须一致;除此之外,没有任何限制!

现在,讨论下一个应用程序交互步骤。

OP 验证用户的身份

在上一步中,身份验证请求已经到达了 OP 的端点 URL。接下来,OP 分解请求以决定后续操作。OP 打开请求,获取它的模式,模式可能是关联或身份验证。

清单 7. OP 处理关联请求

 
//From (OpenIdLoginPage's constructor): 
 
public OpenIdLoginPage(PageParameters parameters) throws IOException { 
  super(parameters); 
  . . . 
  if ("associate".equals(mode)) { 
    OpenIdProviderService.processAssociationRequest(getResponse(), requestParameters); 
   } 
  . . . 
} 
 
//From (OpenIdProviderService): 
 
 public static void processAssociationRequest(Response response, ParameterList request) 
    throws IOException { 
  Message message = getServerManager().associationResponse(request); 
  sendPlainTextResponse(response, message); 
 } 
 private static void sendPlainTextResponse(Response response, Message message) 
    throws IOException { 
  response.setContentType("text/plain"); 
  OutputStream os = response.getOutputStream(); 
  os.write(message.keyValueFormEncoding().getBytes()); 
  os.close(); 
 } 

在清单 7 中,OpenIdLoginPage 的构造器(示例应用程序中 OP 的入口点)首先分解请求。模式表明这是一个关联请求,所以它把关联机制委托给 openid4java,openid4java 的代码包装在 OpenIdProviderService.java 中。把关联响应发送回 RP。

RP 确认已经建立关联之后(实际上是 openid4java 确认之后),RP 向 OP 发送另一个调用。OP 再次分解并处理请求。大多数情况下,这是一个 checkid_authentication 请求。

清单 8 给出 OpenIdLoginPage 构造器中的代码:

清单 8. openid4java 分解 checkid_authentication 请求

 
 public OpenIdLoginPage(PageParameters parameters) throws IOException { 
  super(parameters); 
   . . . 
   else if ("checkid_immediate".equals(mode) 
    || 
     "checkid_setup".equals(mode) 
    || 
     "check_authentication".equals(mode)) { 
    if (((MakotoOpenIdAwareSession)getSession()).isLoggedIn()) { 
     // Create AuthResponse from session variables... 
     sendSuccessfulResponse(); 
    } 
  add(new OpenIdLoginForm("form")); 
  . . 
 } 

注意清单 8 中的两行粗体代码。在第一行粗体代码中,OpenIdLoginPage 发送回一个成功的响应。首先,OpenIdLoginPage 使用一个 Session 对象判断用户是否已经登录了(登录的用户应该不必再次登录)。如果用户已经登录了,它返回一个成功的身份验证消息。这就是示例应用程序中实现单点登录的方法。

如果用户还没有登录,Wicket 就创建一个登录表单,用户可以在其中输入用户凭证,见图 2:

图 2. OP 向未验证身份的用户显示登录屏幕
面向 Java Web 应用程序的 OpenID,第 2 部分: 为单点登录身份验证编写 OpenID 提供者

查看原图(大图)

如果用户成功地验证身份,就发送回一个成功的响应并把一些信息(具体地说是 DiscoveryInformation 对象)存储在 Session 中。这就是单点登录的底层机制。

现在,把浏览器重新定向到 RP 的 "return-to" URL 并发送成功的身份验证响应。

RP 授予访问权

如果用户成功地登录,OP 会发送回一个成功的 AuthResponse 消息。现在,由 RP 授予用户访问权。如果用户通过了 OP 的身份验证,示例应用程序会自动地授予访问权。另外,OP 发送 RP 请求的所有用户信息。在图 3 中,RP 在它的注册屏幕上显示信息:

图 3. 示例应用程序的注册页面显示从 OP 获取的信息
面向 Java Web 应用程序的 OpenID,第 2 部分: 为单点登录身份验证编写 OpenID 提供者

查看原图(大图)

结束语

在本文中,您看到了如何使用 OpenID 身份验证 规范为伙伴应用程序集群建立单点登录身份验证。如果伙伴应用程序可以相互信任,就可以以其中的一个伙伴作为 OpenID 提供者 (OP),实现单点登录身份验证。

使用 OpenID 进行身份验证和数据交换可以确保所有参与的伙伴应用程序在身份验证和授权方面保持一致。OpenID 是一个得到广泛采用的标准,有许多资源可以帮助您学习和调试 OpenID 身份验证 实现。

如果想详细了解本文中实现的单点登录架构,请研究源代码。只需把它构建为 WAR,部署到 Tomcat,然后运行它!一定要打开 TRACE 日志记录,查看日志输出,日志会揭示本文中没有讨论的应用程序细节。

与任何规范一样,OpenID 身份验证很复杂,但是 openid4java 大大简化了使用它的过程。

下载

描述名字大小下载方法
示例应用程序的源代码openid-provider-sample-app.zip4.5KBHTTP

Tags:面向 Java Web

编辑录入:爽爽 [复制链接] [打 印]
[]
  • 好
  • 好的评价 如果觉得好,就请您
      0%(0)
  • 差
  • 差的评价 如果觉得差,就请您
      0%(0)
赞助商链接