SCMLife.com

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 4920|回复: 1

[推荐] 在 Worklight 中开发基于适配器的安全验证

[复制链接]
发表于 2013-2-17 15:44:44 | 显示全部楼层 |阅读模式
本帖最后由 技术狂人 于 2013-2-17 15:59 编辑
2 ~: i1 W7 o3 Q4 x" b- E7 Q) W9 C& }5 F
Worklight 安全验证简介
1 P1 w- s7 u# Z3 _: _; X+ S# |安全验证是应用开发中的一个重要的组成部分,对移动应用开发亦是如此,Worklight 作为一个业界领先的移动应用开发和管理平台,提供了完善的安全验证框架。
7 B8 G! A4 {% U" [6 H  `Worklight 安全验证框架中的基本概念
  b  `, ]2 K' Q5 f( n1 y; `安全验证用于防止对某些特定资源的未经许可的访问,在 Worklight 中,我们使用其验证框架来保护 Worklight 中的 实体(entity),如 应用适配器 以及 静态资源: J: y- ]% @5 t5 T2 h
Worklight 提供了声明式的方式来配置保护规则,来实现对实体的未经授权的访问保护,声明规则由下面两个概念组成:
4 |4 I. _) z, B+ Y% t' z4 X
  • security test: 由一个或多个 ream 组成,用于保护 Worklight 的实体。
  • realm: realm 定义了处理用户验证的业务逻辑,它又由以下几个概念组成。
    0 Q4 P9 [5 L# K; y! c# ?# V; K
    • challenge handler:客户端组件,用于侦测服务器端是不是在发送一个需要安全验证的请求,如果是, 则收集用户身份信息,并发送至服务器端。
    • authenticator:服务器端组件,用于收集客户端发来的用户身份信息。
    • login module:服务器端组件,用于接收 authenticator 收集到的用户身份信息,验证该身份并创建用户身份对像。4 |8 [9 C$ U& [* o1 L
Worklight 服务器端可以定义一个或多个 authenticator,authenticator 又分为下面三种类型:  b2 z* t* O2 Q6 G
  • 基于表单的 authenticator:用于基于表单的安全验证请求。默认实现为 com.worklight.core.auth.ext.FormBasedAuthenticator。
  • 基于适配器的 authenticator:用于使用适配器过程(procedure,类似于 Java 方法或函数的概念)来 搜集和验证 用户身份。默认实现为 com.worklight.integration.auth.AdapterAuthenticator。
  • 基于 HTTP 头的 authenticator: 同上述 authenticator 不一样,该 authenticator 并不需要由客户端提交用户身份信息,而是直接从 HTTP 头中检查相关的属性值。
    , y4 W' |( w& ^8 D4 M
我们在多数情况下,仅需直接使用 Worklight 提供的 authenticator,如有复杂的情况,我们也可以使用 Java 来实现自己所需的 authenticator。在本例中,我们将使用基于适配器的 authenticator 的默认实现,下面接着介绍基于适配器的安全验证。
6 e* U6 [9 u6 t- M基于适配器的安全验证简介
& n* B1 H2 J. v8 N0 B! W" u0 B在 Worklight 中,适配器用于 获取信息和执行动作,它可以连接多种类型的后台系统,如数据库﹑ Cast Iron 等,Worklight 可以通过调用适配器过程来连接第三方系统,并可执行相应的操作,图一为适配器请求流程示意图:7 d, F1 G' K5 K- h

/ Z  J: f  X' {" v( B0 `图 1. 适配器请求流程示意图7 E+ ?& ^# Y; v5 w$ @: l6 z: }- h

, w7 P3 |- Z, K# ~# A: H! @1 ?开发适配器主要就是开发适配器中的过程(procedure),过程是适配器中的核心组成部分,它提供了连接第三方系统并执行所需业务逻辑的能力。在开发时,我们需要在适配器的 XML 声明文件中声明该适配器中的过程,然后在 JavaScript 文件中来实现定义的过程,当然我们也可以使用 Java 来实现过程。在本例中,我们使用了 JavaScript 来实现过程。
6 F' o$ M, K/ M: J9 a' q) R在实际项目中,由于身份信息可能来源于第三方系统,如数据库﹑ LDAP 服务器等,此时,我们便希望能够使用适配器来连接第三方系统,正如前面的章节所述,Worklight 提供了基于适配器的安全验证能力。
8 t$ Y0 S% n& v+ H' m8 o* v" t& S$ X- \需要注意的是,当使用基于适配器的安全验证方式时,我们可以在适配器中实现完整的身份验证逻辑,而此时,login module 并不是必须的,任何其他声明的 login module 将作为一个额外的身份验证逻辑在适配器执行后被调用。
+ X& }4 {9 T, B0 j* U同其他方式的安全验证流程类似,Worklight 在拦截到一个访问受保护资源的请求后,会首先检查该请求是否含有合法的用户身份,如果是,则给予访问权限,并返回请求的数据;如果没有合法的用户身份,则启动验证流程,只有在验证流程成功后,才能授予访问权限,返回请求的数据。如图 2 所示。5 }9 t2 t9 }, M: l$ r

5 o+ \/ b/ a6 I: Z图 2. 基于适配器的安全验证处理流程图* J6 F" h1 d3 Z: I8 ]" c2 \# J2 R( ~
1 g  {+ R/ n& V  d


1 l! j  h8 J  m2 Q: `: `# A/ I! i. G0 I* Y2 O
示例介绍
2 {* D' ^4 _+ `( O, G2 \; w基于适配器的安全验证方式是实际项目中常会使用的一种安全验证方式,本文给出了一个基于适配器的安全验证的示例,并使用该安全策略来保护适配器中的过程,读者也可以根据自己的需要使用安全策略来保护应用和静态资源。" R. y. n- O2 u4 a/ j
本文的示例分为两个部分:适配器和客户端应用,图 3 给出了示例的请求处理流程,该处理流程同图 2 所示的标准请求处理流程类似:
& E2 }- @& K' Z+ Y2 }/ K8 x
4 t( h0 g& s" O, t8 Q3 @图 3. 示例请求处理流程! f- F; f( A5 p0 T8 H9 R. N) t  {
2 d: W. `: f1 _' b; S
  • 请求访问客户端应用中的页面 AdapterAuthentication.html
  • 页面中的 JavaScript 调用受保护的过程 protectResource
  • 安全验证框架被触发,调用适配器中的 onAuthRequired
  • 客户端页面中的 JavaScript 检查 authRequired 属性,判断是否需要验证
  • 如已验证,显示请求页面,显示相应的 DIV 内容,并隐藏特定的 DIV 内容,如登录输入框。至此,请求处理流程结束。
  • 如需要验证,则显示请求页面,显示相应的 DIV 内容,如登录输入框,并隐藏特定的 DIV 内容。
  • 用户输入相应身份信息,并触发身份验证请求,相应的适配器中的过程 doLogin 将被触发,用于验证身份信息。
  • 如果验证成功,则转至第 5 步。
  • 如果验证失败,则转至第 6 步,并显示相应的错误消息。8 L- w) g9 f! E
从开发角度看,本例的开发主要分为适配器的开发和客户端应用的开发,由于只是用于演示用途,本例并未设计复杂的业务逻辑。在适配器中,只有几个简单的过程;在客户端应用中,页面中主要分成两块,一块是在验证后才会显示的,另一块则在没有验证的时候显示。下面我们将介绍如何开发本例及相应的 Worklight 安全验证框架中的要点。
" ~8 Q7 ?( J; N' A7 u2 A) Q
2 V! ?6 ~: Q4 b- _! _9 r9 R
! R/ l3 T9 D+ x! M' C. o
示例开发0 C4 g, o: F. f" B- o" k! d
配置服务器端的安全验证策略$ f6 b3 \: d$ x5 F% `! n: r# c, Z9 a
当安全验证策略确定后,我们就可以着手配置安全策略,但在配置之前,我们首先需要创建一个 Worklight 项目,本示例选择创建一个混合应用(Hybrid Application)类型的项目,在向导窗口,输入应用名为 AdapterAuthentication,在下面的小节“开发客户端应用”中,我们将基于这里生成的应用做修改。
7 J' m) p. N/ l- a# `+ A/ k7 B; O6 f1 i& l
图 4. 创建 Worklight 项目' i+ t  S. }/ h5 {7 n: Z- a$ l( [

; Z* i: |4 I- y4 N/ f. h项目创建成功后,我们可以在项目视图中看到已经生成好的文件,打开 server->conf,我们即可找到用于安全配置的文件 authenticationConfig.xml,如图 5 所示。
+ U. ~9 T1 K9 K, ~% j/ m$ d  Q7 L# Q0 c$ ?' E1 D
图 5. 安全配置文件 authenticationConfig.xml$ }8 Y7 E/ g1 g: r
9 {) b8 n. }) i, g7 r
正如本文在第一小节提到的,服务器端的安全配置需要配置 security test 和 realm,在服务器端,realm 由 login module 和 authenticator 组成,下面我们首先配置 login module,打开文件 authenticationConfig.xml,在节点 loginModules 加入清单 1 所示的代码片段。
2 b4 y# x+ b9 j$ G9 s
9 k* h& Q, C: v2 z/ E* P0 K清单 1. login module 代码片段
1 A, e+ ~+ q& M9 I) r4 j* h
<loginModule name="StrongDummy"> , h* P/ j" J! v5 B: V/ `. |
<className>com.worklight.core.auth.ext.NonValidatingLoginModule</className>
9 u# I: r% z6 B/ j( A2 X: @4 c </loginModule>

5 @1 A/ r6 v/ D* s, {6 i! A由于示例使用适配器来执行安全验证逻辑,我们并不需要有额外的安全验证逻辑,所以这里使用了 NonValidatingLoginModule,如果这里定义了其他的 login module,该 login module 将在适配器中的逻辑执行后被执行。3 Y1 v) D3 l/ e' Q8 o
配置好了 login module,便可在同一个文件中加入 realm 的配置,找到节点 realms,并加入清单 2 所示的代码片段。
4 W& O8 y8 o8 ~( P* U" B2 n% `( u7 o# R, w7 f
清单 2. realm 代码片段5 [. G4 ^/ t+ s9 j3 c
<realm loginModule="StrongDummy" name="AdapterAuthRealm">
6 u) z* m1 B" f0 t9 ]% b* x <className>com.worklight.integration.auth.AdapterAuthenticator</className> 1 i& G) y. I* J% ^
<parameter name="login-function" value="AuthAdapter.onAuthRequired"/>
5 _+ V4 [0 `. M) ^, u <parameter name="logout-function" value="AuthAdapter.onLogout"/>
1 v5 C& E0 `1 x* b </realm>
9 v! e; R8 E  q% t& U8 n' d* n1 P
com.worklight.integration.auth.AdapterAuthenticator 为 realmAdapterAuthRealm 的 authenticator 的实现,该 authenticator 表示我们将使用适配器过程来进行安全验证,login-function 和 logout-function 是我们 realm 中的两个重要属性,当 Worklight 框架侦测到一个针对受保护资源的访问请求,会调用 login-function 中所定义的方法,当获得一个退出登录请求时,则调用 logout-function 中所定义的方法,可以发现属性值的格式为“适配器名 . 过程名”。清单 2 中已经给出了示例中所用到的适配器的两个过程,它们将在下一个小节中介绍。
! {9 Y% V$ P" M现在,我们就可以在同一个配置文件中加入 security test 的配置,如清单 3 所示。4 ]7 y& I) d  p

; ^5 t$ K/ T: b7 M* ?清单 3. security test 配置代码片段/ L! K/ N: s5 s3 b
<securityTests>
4 u5 g7 t5 K! b8 [        <customSecurityTest name="AdapterAuth-securityTest">
! n* [* C3 i# x  J( S, h# _) G            <test isInternalUserID="true" realm="AdapterAuthRealm"/>
$ t- w* f# G2 ?2 y1 _- S/ \        </customSecurityTest>
( ?: }% i& L$ F! B5 M% ?3 t </securityTests>

' h: U1 G: Q, A1 K; c/ c* w在文件中,我们可以看到已经有些被注释掉的 security test 的配置,而其中的 security test 又可以分为下面三种:
4 S" ^! |2 ]+ G  U& @% @6 z7 H& q
  • webSecurityTest:用于包含 Web 安全相关的 realm。
  • mobileSecurityTest:用于包含移动安全相关的 realm。
  • customSecurityTest:用于包含自定义开发的 realm。
    4 T3 s, q) G2 E  `& j2 V' S" w* \' z& @- E
由于示例使用了适配器来保护对受保护资源的访问,我们这里使用 customSecurityTest,realm 则是上面所声明的 AdapterAuthRealm。
$ N6 l. ^3 x3 L, F" `到这里,我们就已经配置好了安全验证策略,在下面的适配器开发中,我们将会使用到本节声明的 security test 和 realm。
$ L1 c& y$ P4 m6 A8 d, H开发适配器- r9 r7 Q, `# ?! I: X! C" N' ?
Worklight 提供了三种类型的适配器:SQL 适配器﹑ HTTP 适配器以及 Cast Iron 适配器,本例使用了 HTTP 适配器,但由于我们的适配器比较简单,实际上并不需要连接第三方支持 HTTP 协议的系统。$ \- Z, l, w' P' W, Y5 j8 d
在项目浏览器视图中,可以找到 adapter 文件夹,我们即可选中文件夹 adapters,右键选中 NewWorklight Adapter,如图 4 所示。3 W# ?3 ?) Q2 U6 {1 f7 U
# O! l; _: n1 i* e
图 6. 创建适配器8 G; n, A# e" N1 M

1 r: o* I$ h- @+ n4 DFig6_CreateAdapter.jpg
9 {9 }' w6 M3 ?; b: T; A. d在弹出的窗口中,选中 Adapter type 为 HTTP Adapter,并输入 Adapter name 为 AuthAdapter。
, p  y" H/ Q) Y; m1 R6 u. @; @7 A( z( u3 B% ]8 N& ]& i
图 7. 输入适配器信息
: Y* y" v! ^8 w& }3 F1 J5 ]
  V* W. M- F- h选择“Finish”,即可看到项目视图中新生成的文件夹 AuthAdapter,该文件夹包含下面三个文件,如图 6 所示:& T  _+ \- I7 x2 \
  • AuthAdapter.xml,适配器的配置和声明文件,用于配置和第三方系统连接的相关属性及声明该适配器中对其他应用或适配器所公开的过程。
  • AuthAdapter-impl.js,用于实现上面 XML 文件中所声明的过程。
  • filtered.xsl,用于定义所处理的数据的 schema,由于本例较为简单,无需对该文件做任何修改。
    , V# [4 {2 A, |

+ D# ]* Z( J' P: z, p+ b图 8. 适配器文件列表
% e: m' G& G' a' e3 ~6 B' V% g
$ }& q  |* @: E; v+ z$ r如前文所述,本例实际上并未使用适配器连接第三方系统,所以在适配器的配置和声明文件中,我们并无需配置 connectivity 节点,使用生成的默认值即可,目前,Worklight 的 HTTP 适配器支持 REST 和 SOAP 调用。除了 connectivity 之外,我们还在配置和声明文件中,声明了两个公开方法 doLogin 和 protectResource,清单 4 给出了配置和声明文件片段。: |- f1 w. L7 i1 c5 f' R3 X4 z
9 c9 A1 Z9 o/ O$ {2 s7 D
清单 4 配置和声明文件片段
' x7 S8 C. f( ?! ?
<connectivity> , s, N7 }* `6 s' N1 b/ J
<connectionPolicy xsi:type="http:HTTPConnectionPolicyType"> # F  f, M9 ]0 z
<protocol>http</protocol>
% I9 d" U4 Z+ a <domain>rss.cnn.com</domain> % ^9 A' H6 M. U
<port>80</port> ) j7 D- f- M# h1 i, W  l% k
</connectionPolicy>
4 k+ b6 v; g( h( Z- \+ ` <loadConstraints maxConcurrentConnectionsPerNode="2" /> % v# P. x* @: x; K" V# D' h
</connectivity>
( `. g" T! K5 o8 U/ M# O& ~4 C+ }$ j- E
<procedure name="doLogin"/>
) V' m" K, j1 I6 ?/ I0 ^* b8 C
9 v/ H4 H5 c) C3 U+ Q  c <procedure name="protectResource" securityTest="AdapterAuth-securityTest"/>

5 j/ C. j3 I8 E/ i7 f, l: s: v( pprotectResource 为受保护的过程,当有请求调用该过程时,相应的 security test 将会被调用,doLogin 则是一个公开的未受保护的过程,该过程将被客户端页面调用,用于验证用户身份。
6 `; t/ \  L* a配置好了 XML 文件之后,我们便可在 JavaScript 文件中实现所配置的公开方法,同时可以添加其他内部方法,清单 2 给出了 doLogin 方法的实现。$ W% @( I6 h4 ?9 i4 p% k0 K/ D/ v

1 r' q2 O5 [( H" x9 l: ^( O; C清单 5. doLogin 方法的实现
) f! b  T# @6 T2 B' R& l
function doLogin(username, password){
: @# ?& K1 j& a) g' ?3 v( n8 f% t* J$ d& K5 [+ ~2 {( x: l
var userIdentity = {
9 Z/ F+ [8 G2 B/ x userId: username, / t1 _6 \1 W! p+ C" f* T0 u0 [
displayName: username,
; ~, [) N- Q2 V loginName: username, # }9 E# D& L6 o# @2 W
attributes: { * b( C* d4 H# Q
foo: "bar") ?0 R: f/ ^3 V4 e. v& V7 d
},
. O6 h& c9 U6 }( T7 F
. @( J) S9 ]4 f; R/ X isUserAuthenticated: '1'
9 c* F: y, }. W. f }; . l2 j; j1 A  E8 {6 [

0 A& b! b, \  @$ h+ k& e //Dummy verify logic, just for demo purpose
1 A/ B! }  ]" G( t( M( L6 @ if (username == password) { $ W7 D6 l8 I, D" z2 i
WL.Server.setActiveUser("AdapterAuthRealm", userIdentity);
- }, P+ h  Q. L$ D3 v0 l4 u5 [! }( v" f7 Y+ o; C1 z
return {
! G3 H, f4 @- o& ? authRequired: false ) E  q1 J1 I3 X9 I, ?0 D
};
7 U. C6 G. Y! }2 ]# r } else {
3 V0 p5 b' P6 @ return onAuthRequired(null, "User name or password is wrong."); - `2 _5 I/ `3 `' M5 W
} , L. E8 l' c0 c- b

* m, Z- W, Z- A0 r }
1 v6 @2 \" ]; m! _# e, p6 t
示例逻辑比较简单,当输入的用户名和密码一样时,我们便认为用户身份信息正确,同时将生成的身份对象设置为当前的活动用户,并返回 authRequired 为 false,注意,第一个参数为上一小节安全配置文件中所声明的 realm。如果用户身份验证失败,则将调用方法 onAuthRequired,该方法亦会在 Worklight 侦测到有访问受保护资源请求时由 Worklight 框架自动调用(见上一小节 realm 的配置),清单 6 给出了 onAuthRequired 和剩下的 2 个方法代码。$ f1 g) U% I: w* w/ X1 `

6 X% P4 I2 `+ q1 N清单 6. 适配器中的其他方法+ X/ Y2 x& o1 Z5 d; |
function onAuthRequired(headers, errorMessage){ 8 q2 ]) @5 I$ O& R: d
errorMessage = errorMessage ? errorMessage : "Auth Required"; ) v  u. q  y" r0 E6 |& e
: N! i, q& p9 J3 n2 n) K
return {
  e7 |0 i! g  E& g, S1 m# n4 l authRequired: true, + ^9 a( S3 n4 c% D; h" U. }/ ^) x
errorMessage: errorMessage 5 h9 s" `6 C& G- u+ @) z3 \$ R
};
: w; `1 e3 x# X( f9 C3 X } - U0 V4 U5 P8 L5 {/ v

3 @6 k2 d* h% v, E0 x/ E function protectResource() { 4 _( L* v7 V; C5 ~$ U9 X5 D
4 E6 G/ ]- z$ S' u( m
//No function code required, this method is just used to
# h4 m; I# V9 K6 ? //make sure the user is authenticated 5 M3 n9 y0 @8 p* I5 m
//Otherwise, login page will be showed.
, L+ U4 P+ N; K( h+ |0 L/ ?% |+ @( @/ ?/ d& D0 [
} , ~' M2 i2 Z  u* S! W

3 a  S* }9 C* V, }+ u& p4 G7 d0 P
8 E. F. k7 x% ~$ \1 q function onLogout() { 0 v5 ~3 C5 T, T0 Q6 B
//Put any additional log out/clean up logic here
2 C' d) C$ M% \; z, X+ e //For demo purpose, no code is needed / V6 t1 r4 \3 D! Y0 S

( [, n0 G' ~2 ]! [) z }
% X; Z/ c7 h: R7 ^7 Q# b$ l5 {
通过上面代码片段,可以发现方法 protectResource 和 onLogout 是两个空方法,在清单 4 中我们将方法 protectResource 定义为受保护的方法,所以尽管该方法中没有逻辑,但在调用方法前,依然会进行安全检查;onLogout 方法为 realm 的 logout-function 的属性值,所以该方法会在用户退出登录时被调用,用于清除用户数据等,出于演示用途,本例未添加任何代码。
, a: a: u* J- D+ E6 g) F5 U5 E至此,我们的适配器就开发完毕,下面我们就可以开发客户端应用来验证我们所做的安全配置和适配器。9 X! `+ _  V3 d- n. u
开发客户端应用
( Y* x: ]( F" j9 b+ l+ D0 N示例的客户端应用主要为一个 HTML 页面,应用为登录前或登录后的用户显示同一个页面,但页面的内容并不一样。由于只是演示用途,我们直接基于创建项目时自动生成的应用上做定制修改,虽然创建项目时自动创建了数个文件,而我们只需要修改其中的一个 JS(AdapterAuthentication.js)文件和 HTML(AdapterAuthentication.html)文件,如图 9 所示。
- Z6 y0 f% u( O# z1 ^, w4 c  N' U8 b( y& t9 T" T2 o' n
图 9. 客户端应用文件结构图
5 K4 E& n# G/ b- e' `. F7 F
2 F( {7 S0 J8 P在整个客户端应用的开发过程中,我们主要需要做下面的工作:
) j8 ~0 r' j/ W, }
  • 修改 HTML 页面,对已验证的用户和未验证的用户添加不同的显示内容。
  • 修改 JavaScript wlCommonInit 方法,用于调用受保护的过程。
  • 在 JavaScript 文件中,增加客户端的用户身份信息收集和验证请求提交代码,其核心为 challenge handler。
  • 在 JavaScript 文件中,增加一个新的方法 checkUser,用于检查用户是否已经过验证。
    " Y/ g" @) b8 l' l- T. M
HTML 页面比较简单,主要目的是能够对已验证的和未验证的用户提供不同的显示内容,并能够给未验证用户提供登录框供用户输入用户名和密码。清单 7 为页面 HTML 代码片段。. C1 K9 q. v4 [* [, x' K3 M2 \
1 ^+ F9 s3 `/ g" p
清单 7. 页面 HTML 代码片段
8 A6 j1 Q- o1 K  I. d- |9 i
<body id="content" style='display: none'> - s& Q$ H8 Q" b* d3 P1 v+ o
        <div id="AppDiv"> ! i- J5 z& a$ `  C
<div class="header" style="display:none">
+ L4 B! t$ w0 D5 s# S8 k) K2 E <h1>Adapter Authentication Demo</h1> * M& E+ }$ j9 e6 j- Z: u, O1 n
</div>
* \7 U' J% N5 B            <input type="button" value="Check User" 3 k' I! L: F3 a# i' E% U1 V
            />
' L" j/ h/ I: u) r8 ]            <input type="button" value="Logout"
4 u" S7 S& M; L" W" T            /> 5 q1 K: K% h7 R; C
6 z& C- r5 q4 ^: b, Q+ @* u0 F3 l
        </div>
+ H% \* ]6 F+ G          C& P# o, E4 e9 s( W0 D# I# @3 I
        <div id="AuthDiv" style="display:none">
9 \  o2 y' P# N1 E2 d        <p id="AuthInfo"></p>
* p9 ~# Z* r; D        <hr />
! J6 E# N: T5 `$ v       <input type="text" placeholder="Enter username"
. V. @0 C# Y: z* \; C+ E8 [( h8 u& f        id="AuthUsername"/><br /> & g/ B, }' |9 n2 ^# d. M3 C7 C! j+ A
       <input type="password" placeholder="Enter password"
  s$ b9 J+ B" ?5 U+ v       id="AuthPassword"/><br /> ; f% U, a+ t1 E9 ]
       <input type="button" value="Submit" id="AuthSubmitButton" /> 0 Q  A8 s  u  [) ^$ `# N
        </div>
9 {% B8 l  ]' `4 r! E- X3 O: a0 G- p( r0 c
<script src="js/initOptions.js"></script>
3 p: {4 l( Y2 |/ y( Y+ r <script src="js/AdapterAuthentication.js"></script>
0 N# P$ g2 }: H* s2 o6 y/ p9 F <script src="js/messages.js"></script>
! p% `" Q5 X8 t9 f </body>
2 K, l) t' r/ e/ M* ^& d
在加载页面的过程中,WL.Client.init({}) 会首先被调用,它将调用方法 wlCommonInit。AppDiv 区域用于提供已通过验证的用户所能看到的内容,AuthDiv 区域用户显示为登录用户所能看到的内容,challenge handler 的方法 handleChallenge 会设定这两个 DIV 的 display 属性。
) w: v& M) q1 N& E5 p3 jwlCommonInit 方法位于文件 AdapterAuthentication.js 中,我们需要在该方法中调用前面所创建的适配器 AuthAdapter 中的受保护过程 protectResource,这样,在初始化客户端应用页面时,Worklight 的安全机制将会随着受保护过程的调用而被触发,清单 8 列出了方法 wlCommonInit。
7 L9 z- V# [( L4 M9 ~* r$ P1 t+ Q* b# @$ m: D0 G1 ^- `
清单 8. 方法 wlCommonInit: u0 V5 y' G( \+ d1 X. S2 N' z
function wlCommonInit(){ ) h2 {7 ^8 G% e: y( Z/ T( G1 X
// Common initialization code goes here
" g1 ?; U( \4 c. B4 b: ^5 \4 o var invocationData = { $ n% y. n( i6 X3 I3 M* f; Y7 f/ q
adapter : "AuthAdapter", 0 h. m3 m( j  [0 @
procedure : "protectResource",
2 O7 |, N5 P" q% Z parameters : [] & h6 r. v  N+ R# F% ?1 ~
}; . l* R; W" \. x! `  @
WL.Client.invokeProcedure(invocationData,{}); $ b5 J# y: k' w, e9 w0 v
  
) b1 ?+ h: d$ V+ ~! G6 \; C" E" R }
1 B9 y6 {7 R3 n- H- ~; i% |
在 AdapterAuthentication.js 文件中,还需要添加核心的客户端安全验证代码,其核心主要是获取和使用 challenge handler3 b8 j) D9 S% f, Q0 y( E8 f
& n0 s5 ]3 }* d: H+ M6 a% }5 z/ x
清单 9. 获取和使用 challenge handler3 U& V9 r6 X# B% Q/ J4 f# q4 {2 G% i: T
var authChallengeHandler = WL.Client.createChallengeHandler("AdapterAuthRealm");
- f0 z& O' s! F6 {* E0 t1 f/ Z' E7 ]5 j' `% X- J3 D) t( g
authChallengeHandler.isCustomResponse = function(response) { ( |& F- t+ I- L2 k  D! D

/ x' |9 P( g) D7 ^ if (!response || !response.responseJSON|| response.responseText === null) {
* V6 I8 E! S  F) I* N  Q1 D return false; + c" r" ~# K* j# X; y# U3 L
}
; [% g: V  ]- f( v0 l' M* b8 \% z if (typeof(response.responseJSON.authRequired) !== 'undefined'){
/ C" m1 b5 F& W' z return true; 0 n# v5 I8 P0 {, ?: ?
} else { 1 J) j- Q$ ?+ }! a+ j
return false;
/ n% c& x* o. ~. x5 k$ U } 8 Y+ h& ]4 X) W

! ~( h) F" i' ~' e  l }; ; V; R  C% R4 u9 F$ e
/ C2 j9 c, N* _; D# e4 s
authChallengeHandler.handleChallenge = function(response){ " p3 y- R& a8 x. e: N+ d) ~
var authRequired = response.responseJSON.authRequired; . y. i! m; e+ M0 U; }: }9 f- R* }

( b# q( O; ^3 w0 L/ t if (authRequired == true){
* y0 K$ x" S, H $("#AppDiv").hide();
: S/ G1 Q* E8 |6 z$ E/ Z- { $("#AuthDiv").show(); " x. d0 q7 ~4 H. d5 Z* r4 j1 J0 P
$("#AuthPassword").empty(); * D' C" Y- I, g. M
$("#AuthInfo").empty(); 5 h- ~8 q, [: w/ h5 ?! S

1 X2 b6 ]) J( u if (response.responseJSON.errorMessage) 3 A7 H$ d5 ]0 T, _/ i) c
    $("#AuthInfo").html(new Date() + " :: " + response.responseJSON.errorMessage);
+ L- r% P. F% ^6 @* [' Q' i( {% r! T$ o' h$ i
} else if (authRequired == false){ 5 \9 Z8 P$ c0 m5 f$ @
$("#AppDiv").show(); : I% T- i2 o. s9 `* k
$("#AuthDiv").hide();
, f- D- E# g6 X7 Z; ]% }) S authChallengeHandler.submitSuccess();
0 M0 f9 g4 D7 Y! a% C% \# A9 Z+ U8 g } * z, C" o0 D' Z& V5 v) M
};
# K. i! @% @$ x1 F" r$ u- }# n
WL.Client.createChallengeHandler("AdapterAuthRealm") 用于获取 challenge handler,参数为我们所要使用的 realm。Challenge handler 的方法 isCustomResponse 会在每次收到服务器端响应的时候被调用,用于检查是不是需要对该响应做安全验证,当该方法返回为 true 时,意味着 Worklight 框架需要对该响应做安全验证,另一个方法 handleChallenge 随即被调用,用于在需要安全验证的情况下,显示用户身份信息输入选项,并显示相应的错误消息;当返回为 false 时,则意味着用户已通过安全验证,只需调用 submitSuccess 方法来通知 Worklight 框架,表示用户已通过安全验证。5 U& K/ T2 j: D& z
那么 authRequired 的初始值又是如何被设为 true 的呢?当受保护的资源被请求时,Worklight 安全机制随即被触发,realm 配置中所配置的 login-function 将被调用,如清单 6 所示,onAuthRequired 方法会将 authRequired 值设为 true,而在安全验证成功后,该方法则不会继续被调用。清单 10 给出了本例的安全配置片段,详细内容会在下面的小节介绍。
) G+ |# M. \* r5 F4 U8 H' \& b
0 m, K$ w5 C/ Y清单 10. 安全配置片段
. R& u7 U* a) h
<realm loginModule="StrongDummy" name="AdapterAuthRealm"> ( m) @; `# r5 s9 s; H7 p8 D
<className>com.worklight.integration.auth.AdapterAuthenticator</className> # b9 r! D; K. m0 Q
<parameter name="login-function" value="AuthAdapter.onAuthRequired"/> ' J1 q, e0 s) J1 k9 Y4 _) M; s) ~
<parameter name="logout-function" value="AuthAdapter.onLogout"/> ) T% s* w/ T$ Y  ~
</realm>
8 F0 w! a$ E* b; y$ Z$ _0 [
本文的主要目的是介绍基于适配器的安全验证,其核心点之一便是如何调用适配器做安全验证,而调用代码也需要添加在 AdapterAuthentication.js 中,清单 11 给出了相应的代码片段。4 P) Z' j0 Y7 h" D6 s
# r2 G; L, T9 S0 |
清单 11. 调用适配器触发验证逻辑
5 A* g6 D$ P9 d; ^9 ~
$("#AuthSubmitButton").bind('click', function () {
/ j$ O+ e* N$ L var username = $("#AuthUsername").val();
6 x% ]0 j5 W( I5 U7 w$ ] var password = $("#AuthPassword").val(); & Q  w% _! ?# p. n
% t3 s1 n9 `0 }. k! u' J5 v
var invocationData = { 6 ~; A. [, B: i. @- @1 v
adapter : "AuthAdapter", " W' p- y5 M- r7 U2 Z0 p
procedure : "doLogin", 8 I3 k- u, v7 G" K& c# u. v% V
parameters : [ username, password ]
5 \( W/ ?' R, m) C: q! V5 R };
! z9 }& ~% k7 E! U% D
6 R+ B2 W1 p( z2 F; P' `6 _ authChallengeHandler.submitAdapterAuthentication(invocationData, {});
7 [) d' s; e1 N/ k% g1 [ });
) l1 G, m% s6 z/ t" p
上面的代码主要做了三件事:
+ V' E. u1 o; j  ~" z
  • 为按键的 click 事件绑定 JavaScript 方法,当用户单击页面登录按键时,绑定的 JavaScript 方法将被触发。
  • 获取页面中的用户名和密码信息
  • 获取适配器 AuthAdapter 中的 doLogin 对象,并通过执行 submitAdapterAuthentication 来调用适配器中的过程来执行验证逻辑。
    & K9 S7 g0 r% b" |
至此,我们已经完成了本例的开发工作,下面我们将部署和运行本例。1 E, ]: Z% G! s/ J1 S" m# }


7 x  i- @* Q8 r/ L. t- w/ f) X1 W/ B3 H, F2 Y9 x* i1 G# }
部署和运行示例0 S/ f4 M8 R: _- c1 e
由于我们使用了适配器,所以我们首先需要部署适配置,在项目视图中,右键单击适配器的文件夹,选中“Run As->Deploy Worklight Adapter”,如图 10 所示。5 b+ ~$ o$ x  H) B1 |

' D  m& Y, A8 M4 }' y% L# N# b0 p图 10. 部署适配器& S' K" }$ c! b" [; q) i' E

( E3 [8 `/ F( }% D  q* ]在控制台视图中,如果看到“Adapter deployed successfully”,则表示适配器已经部署成功,如图 11 所示。! z3 d2 B& W+ _. ?: H9 y( c3 @

' X9 k) d; I. r. u7 X  `: y图 11. 控制台视图
0 V; ~4 Y* u: D' W! l  u0 a+ {9 y" M. F8 u9 o  G
同部署适配器类似,我们还需要部署客户端应用,找到应用的文件夹,右键单击,选中“Run As->Build All and Deploy”,如图 12 所示。) e/ D$ d$ k$ ?8 [7 V, ]) p1 i# ]

7 o# h; u2 W6 X8 L% m7 O1 i( y图 12. 部署客户端应用2 t5 V( E1 P, W; J( r' d

4 T2 s) [1 W" Q同样,如果控制台视图提示“Application 'AdapterAuthentication' deployed successfully with all environments”,则表明应用已经部署成功,如图 13 所示。
: O) [0 a; t6 S6 _% \3 n% U) ]* J/ `8 g( l5 b
图 13. 部署客户端应用控制台7 J" n4 ?# H2 P, ^: s$ G5 k5 X
9 V5 _$ }* ~7 V7 G# l  [& L
最后,我们便可以通过控制台(localhost:8080/console)来预览我们的示例,图 14 为初次访问的示例页面。
# E. V  @( Y$ Q2 Z5 p
1 O2 g% e5 K0 G8 ^" W/ A! n图 14. 示例初次访问页面: n. g: a% H1 H- E- m! F

7 |" s1 {$ M8 S5 t1 n5 U! Q因为用户还没登录,而我们在页面初始化过程中已经调用了受保护的适配器过程,所以 onAuthRequired 方法被触发,从而在页面上部显示提示消息,提示用户需要登录,当用户输入正确的用户和密码(这里只需要用户名等于密码),即可显示已验证用户所能看到的内容,如图 14 所示。1 c7 i% ?& a. O9 n" Q; @
% y5 X1 ]: R1 X
图 15. 授权用户显示页面0 z: P/ j+ i4 t" R" v
& E4 p" W8 k2 g, p' _
用户即使再次刷新页面,页面内容也不会变化,因为我们在适配器过程 doLogin 已经将 authRequired 设为 false,这样 Worklight 框架会认为后续请求已经经过安全验证,无需再次进行安全验证,当点击“退出”按键后,则会回到未验证状态,并显示未验证的页面。7 O, Y; o! K5 n" q

8 @) S- f1 g- G+ y- @" B' }
7 Y- C  p, P& U9 t# ^8 r- p0 K1 {
总结
) y$ A/ h' k" D, {( k- wWorklight 提供了多种安全验证方式,本文选择介绍了常用的一种方式:基于适配器的验证方式,本文通过一个简单的示例,介绍了基于适配器的安全验证的基本概念,同时介绍了基本的开发步骤以及如何在 Eclipse 中做相关开发,我们在掌握了开发本文示例的基础上,可以根据项目的需要,做进一步的定制开发。
) X0 v9 {3 L$ M% r% y* `7 l5 Y" Q, w' S9 a" |5 k, @2 G2 A$ m

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
 楼主| 发表于 2013-2-17 16:00:05 | 显示全部楼层
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

SCMLife推荐上一条 /4 下一条

QQ|小黑屋|手机版|无图版|SCMLife.com ( 京ICP备06056490号-1 )

GMT+8, 2018-9-20 07:15 , Processed in 0.078536 second(s), 8 queries , Gzip On, MemCache On.

Powered by SCMLife X3.4 Licensed

© 2001-2017 JoyShare.

快速回复 返回顶部 返回列表