B/S通常我们的权限控制包括以下几个方面:1,Web层访问权限控制(包括URL级或Button/Link级);2,业务逻辑访问控制,主要指业务逻辑中方法级的权限控制;3,数据访问权限控制,主要指对Table, View的访问控制,严格的控制会到Row级,甚至是Field级。
问题引入:
我们通常的系统中仅仅做了Web层访问权限控制,因为大多系统只有Web方式的UI做为人机交互的接口,不提供其它的客户端,所以业务逻辑访问控制一般可以不用控制,数据访问方面由于一般系统的要求目前尚未达到这一权限控制的必要,也没有完整的进行控制。Web层的URL级控制通常采用J2EE规范中的Filter来解决,但由于JSF中的跳转并不会重写URL,默认采用Forward的方式,一般不会采用Redirect的方式,而且采用Redirect的时候,开发时会很不方便,很多Request参数无法传递,所以我们实际上在Filter中获得的URI往往都不是真实的地址。
实际案例:
我们现在有一个画面,假如从菜单中进入画面的URI为“list.jsf”,画面上有“新增”、“查询”两个Button,当点击“查询”这个Button时,查询结果并显示在“list.jsf”这个页面上,当点击“新增”这个Button时,跳转到“add.jsf”这个页面,新增画面中还有一个“返回”Button。当我们点击“新增”时,在Filter中得到的URI为“list.jsf”,实际上我们要跳转到“add.jsf”这个页面,当我们点击“返回”Button时,Filter中得到URI的是“add.jsf”,这与我们实际要跳转到的页面“list.jsf”又不符。如果我们仅仅在Filter中根据URI来判断权限,那么当一个用户不具有新增权限时,就会出现可以操作新增功能的事情,这样便未达到权限控制的目的。
问题解决:
JSF提供了自定义Application的可能,通过研究JSF的实现可以发现JSF控制页面的跳转都是在NavigationHandler中完成的。那么我们来试着实现一个自己的NavigationHandler,解决这一问题。先给出详细的实现:
1
public class NavigationHandleWithAuthImpl extends NavigationHandler
{
2
3
/** *//**
4
* Field DEBUGLOG.
5
*/
6
private static final Logger DEBUGLOG = Logger.getLogger(Constant.LOG_DEBUG);
7
8
/** *//**
9
* Field ERRORLOG.
10
*/
11
private static final Logger ERRORLOG = Logger.getLogger(Constant.LOG_ERROR);
12
13
/** *//**
14
* Field IGNORED_URI.
15
*/
16
private static final String IGNORED_URI = "/login.faces;/send.faces;"
17
+ "/mainlayout.faces;/backForward.faces";
18
19
/** *//**
20
* Application associate that contains navigation mappings loaded from
21
* configuration file(s).
22
*/
23
private ApplicationAssociate associate = null;
24
25
/** *//**
26
* This constructor uses the current <code>Application</code> instance to
27
* obtain the navigation mappings used to make navigational decisions.
28
*/
29
public NavigationHandleWithAuthImpl()
{
30
super();
31
if (DEBUGLOG.isDebugEnabled())
{
32
DEBUGLOG.debug("Created NavigationHandler instance ");
33
}
34
// if the user is using the decorator pattern, this would cause
35
// our ApplicationAssociate to be created, if it isn't already
36
// created.
37
ApplicationFactory aFactory = (ApplicationFactory) FactoryFinder
38
.getFactory(FactoryFinder.APPLICATION_FACTORY);
39
aFactory.getApplication();
40
associate = ApplicationAssociate.getInstance(ConfigureListener
41
.getExternalContextDuringInitialize());
42
}
43
44
/** *//**
45
* 检查URL权限
46
*
47
* @param authList
48
* List<FunctionVo>
49
* @param uri
50
* String
51
* @return boolean
52
*/
53
private boolean checkURL(List<FunctionVo> authList, String uri)
{
54
if (authList == null)
{
55
return false;
56
}
57
for (FunctionVo vo : authList)
{
58
String authUri = vo.getUrl();
59
if (authUri != null && !authUri.equals(""))
{
60
int index = authUri.indexOf("?");
61
if (index >= 0)
{
62
authUri = authUri.substring(0, index);
63
}
64
}
65
if (uri.equals("/" + authUri))
{
66
return true;
67
}
68
}
69
return false;
70
}
71
72
/** *//**
73
* Determine the next view based on the current view
74
* (<code>from-view-id</code>
75
* stored in <code>FacesContext</code>), <code>fromAction</code> and
76
* <code>outcome</code>.
77
*
78
* @param context
79
* The <code>FacesContext</code>
80
* @param fromAction
81
* the action reference string
82
* @param outcome
83
* the outcome string
84
*/
85
public void handleNavigation(FacesContext context, String fromAction,
86
String outcome)
{
87
if (context == null)
{
88
String message = Util
89
.getExceptionMessageString(Util.
90
NULL_PARAMETERS_ERROR_MESSAGE_ID);
91
message = message + " context " + context;
92
throw new NullPointerException(message);
93
}
94
if (outcome == null)
{
95
if (DEBUGLOG.isDebugEnabled())
{
96
DEBUGLOG.debug("No navigation rule found for outcome "
97
+ outcome + "and viewId "
98
+ context.getViewRoot().getViewId()
99
+ " Explicitly remain on the current view ");
100
}
101
return; // Explicitly remain on the current view
102
}
103
CaseStruct caseStruct = getViewId(context, fromAction, outcome);
104
ExternalContext extContext = context.getExternalContext();
105
if (caseStruct != null)
{
106
Object obj = context.getExternalContext().getSessionMap().get(
107
Constant.LOGIN_INFO_KEY);
108
List authList = null;
109
if (obj != null)
{
110
authList = ((LoginInfo) obj).getAuthorityFunctionVoList();
111
}
112
String uri = caseStruct.navCase.getToViewId().replace(".jsp",
113
".faces");
114
boolean flag=true;
115
if (this.IGNORED_URI.indexOf(uri) < 0)
{
116
if (authList != null && !this.checkURL(authList, uri))
{
117
// URI is invalid
118
flag=false;
119
}
120
}
121
122
ViewHandler viewHandler = Util.getViewHandler(context);
123
Util.doAssert(null != viewHandler);
124
125
if (caseStruct.navCase.hasRedirect())
{
126
// perform a 302 redirect.
127
String newPath = viewHandler.getActionURL(context,
128
caseStruct.viewId);
129
130
try
{
131
if (DEBUGLOG.isDebugEnabled())
{
132
DEBUGLOG.debug("Redirecting to path " + newPath
133
+ " for outcome " + outcome + "and viewId "
134
+ caseStruct.viewId);
135
}
136
extContext.redirect(newPath);
137
} catch (java.io.IOException ioe)
{
138
String message = "Redirect to " + newPath + " failed.";
139
ERRORLOG.error(message);
140
throw new FacesException(message, ioe);
141
}
142
context.responseComplete();
143
if (DEBUGLOG.isDebugEnabled())
{
144
DEBUGLOG
145
.debug("Response complete for "
146
+ caseStruct.viewId);
147
}
148
} else
{
149
UIViewRoot newRoot = null;
150
if (flag)
{
151
newRoot = viewHandler
152
.createView(context, caseStruct.viewId);
153
} else
{
154
newRoot = viewHandler.createView(context,
155
"/backForward.jsp");
156
}
157
context.setViewRoot(newRoot);
158
if (DEBUGLOG.isDebugEnabled())
{
159
DEBUGLOG.debug("Set new view in FacesContext for "
160
+ caseStruct.viewId);
161
}
162
}
163
}
164
}
165
166
/** *//**
167
* This method uses helper methods to determine the new <code>view</code>
168
* identifier. Refer to section 7.4.2 of the specification for more details.
169
*
170
* @param context
171
* The Faces Context
172
* @param fromAction
173
* The action reference string
174
* @param outcome
175
* The outcome string
176
* @return The <code>view</code> identifier.
177
*/
178
private CaseStruct getViewId(FacesContext context, String fromAction,
179
String outcome)
{
180
// String nextViewId = null;
181
String viewId = context.getViewRoot().getViewId();
182
CaseStruct caseStruct = null;
183
184
synchronized (this)
{
185
caseStruct = findExactMatch(viewId, fromAction, outcome);
186
187
if (caseStruct == null)
{
188
caseStruct = findWildCardMatch(viewId, fromAction, outcome);
189
}
190
191
if (caseStruct == null)
{
192
caseStruct = findDefaultMatch(fromAction, outcome);
193
}
194
}
195
return caseStruct;
196
}
197
198
/** *//**
199
* This method finds the List of cases for the current <code>view</code>
200
* identifier. After the cases are found, the <code>from-action</code> and
201
* <code>from-outcome</code> values are evaluated to determine the new
202
* <code>view</code> identifier. Refer to section 7.4.2 of the
203
* specification for more details.
204
*
205
* @param viewId
206
* The current <code>view</code> identifier.
207
* @param fromAction
208
* The action reference string.
209
* @param outcome
210
* The outcome string.
211
* @return The <code>view</code> identifier.
212
*/
213
214
private synchronized CaseStruct findExactMatch(String viewId,
215
String fromAction, String outcome)
{
216
// String returnViewId = null;
217
// if the user has elected to replace the Application instance
218
// entirely
219
if (null == associate)
{
220
return null;
221
}
222
Map caseListMap = associate.getNavigationCaseListMappings();
223
Util.doAssert(null != caseListMap);
224
List caseList = (List) caseListMap.get(viewId);
225
if (caseList == null)
{
226
return null;
227
}
228
// We've found an exact match for the viewId. Now we need to evaluate
229
// from-action/outcome in the following order:
230
// 1) elements specifying both from-action and from-outcome
231
// 2) elements specifying only from-outcome
232
// 3) elements specifying only from-action
233
// 4) elements where both from-action and from-outcome are null
234
return determineViewFromActionOutcome(caseList, fromAction, outcome);
235
}
236
237
/** *//**
238
* This method traverses the wild card match List (containing
239
* <code>from-view-id</code> strings and finds the List of cases for each
240
* <code>from-view-id</code> string. Refer to section 7.4.2 of the
241
* specification for more details.
242
*
243
* @param viewId
244
* The current <code>view</code> identifier.
245
* @param fromAction
246
* The action reference string.
247
* @param outcome
248
* The outcome string.
249
* @return The <code>view</code> identifier.
250
*/
251
private synchronized CaseStruct findWildCardMatch(String viewId,
252
String fromAction, String outcome)
{
253
CaseStruct result = null;
254
255
// if the user has elected to replace the Application instance
256
// entirely
257
if (null == associate)
{
258
return null;
259
}
260
261
Map caseListMap = associate.getNavigationCaseListMappings();
262
Util.doAssert(null != caseListMap);
263
TreeSet wildcardMatchList = associate.getNavigationWildCardList();
264
Util.doAssert(null != wildcardMatchList);
265
266
Iterator iter = wildcardMatchList.iterator();
267
String fromViewId;
268
List caseList;
269
String wcFromViewId = null;
270
while (iter.hasNext())
{
271
fromViewId = (String) iter.next();
272
// See if the entire wildcard string (without the trailing "*" is
273
// contained in the incoming viewId. Ex: /foobar is contained with
274
// /foobarbaz
275
// If so, then we have found our largest pattern match..
276
// If not, then continue on to the next case;
277
278
if (viewId.indexOf(fromViewId, 0) == -1)
{
279
continue;
280
}
281
// Append the trailing "*" so we can do our map lookup;
282
wcFromViewId = fromViewId + "*";
283
caseList = (List) caseListMap.get(wcFromViewId);
284
285
if (caseList == null)
{
286
return null;
287
}
288
289
// If we've found a match, then we need to evaluate
290
// from-action/outcome in the following order:
291
// 1) elements specifying both from-action and from-outcome
292
// 2) elements specifying only from-outcome
293
// 3) elements specifying only from-action
294
// 4) elements where both from-action and from-outcome are null
295
296
result = determineViewFromActionOutcome(caseList, fromAction,
297
outcome);
298
if (result != null)
{
299
break;
300
}
301
}
302
return result;
303
}
304
305
/** *//**
306
* This method will extract the cases for which a <code>from-view-id</code>
307
* is an asterisk "*". Refer to section 7.4.2 of the specification for more
308
* details.
309
*
310
* @param fromAction
311
* The action reference string.
312
* @param outcome
313
* The outcome string.
314
* @return The <code>view</code> identifier.
315
*/
316
317
private synchronized CaseStruct findDefaultMatch(String fromAction,
318
String outcome)
{
319
// String returnViewId = null;
320
// if the user has elected to replace the Application instance
321
// entirely
322
if (null == associate)
{
323
return null;
324
}
325
326
Map caseListMap = associate.getNavigationCaseListMappings();
327
Util.doAssert(null != caseListMap);
328
329
List caseList = (List) caseListMap.get("*");
330
331
if (caseList == null)
{
332
return null;
333
}
334
335
// We need to evaluate from-action/outcome in the follow
336
// order: 1)elements specifying both from-action and from-outcome
337
// 2) elements specifying only from-outcome
338
// 3) elements specifying only from-action
339
// 4) elements where both from-action and from-outcome are null
340
341
return determineViewFromActionOutcome(caseList, fromAction, outcome);
342
}
343
344
/** *//**
345
* This method will attempt to find the <code>view</code> identifier based
346
* on action reference and outcome. Refer to section 7.4.2 of the
347
* specification for more details.
348
*
349
* @param caseList
350
* The list of navigation cases.
351
* @param fromAction
352
* The action reference string.
353
* @param outcome
354
* The outcome string.
355
* @return The <code>view</code> identifier.
356
*/
357
private synchronized CaseStruct determineViewFromActionOutcome(
358
List caseList, String fromAction, String outcome)
{
359
360
String cncFromAction = null;
361
String fromOutcome = null;
362
String toViewId = null;
363
CaseStruct result = new CaseStruct();
364
int size=caseList.size();
365
ConfigNavigationCase cnc = null;
366
for (int i = 0; i < size; i++)
{
367
cnc = (ConfigNavigationCase) caseList.get(i);
368
cncFromAction = cnc.getFromAction();
369
fromOutcome = cnc.getFromOutcome();
370
toViewId = cnc.getToViewId();
371
if ((cncFromAction != null) && (fromOutcome != null))
{
372
if ((cncFromAction.equals(fromAction))
373
&& (fromOutcome.equals(outcome)))
{
374
result.viewId = toViewId;
375
result.navCase = cnc;
376
return result;
377
}
378
}
379
}
380
for (int i = 0; i < size; i++)
{
381
cnc = (ConfigNavigationCase) caseList.get(i);
382
cncFromAction = cnc.getFromAction();
383
fromOutcome = cnc.getFromOutcome();
384
toViewId = cnc.getToViewId();
385
if ((cncFromAction == null) && (fromOutcome != null))
{
386
if (fromOutcome.equals(outcome))
{
387
result.viewId = toViewId;
388
result.navCase = cnc;
389
return result;
390
}
391
}
392
}
393
394
for (int i = 0; i < size; i++)
{
395
cnc = (ConfigNavigationCase) caseList.get(i);
396
cncFromAction = cnc.getFromAction();
397
fromOutcome = cnc.getFromOutcome();
398
toViewId = cnc.getToViewId();
399
if ((cncFromAction != null) && (fromOutcome == null))
{
400
if (cncFromAction.equals(fromAction))
{
401
result.viewId = toViewId;
402
result.navCase = cnc;
403
return result;
404
}
405
}
406
}
407
408
for (int i = 0; i < size; i++)
{
409
cnc = (ConfigNavigationCase) caseList.get(i);
410
cncFromAction = cnc.getFromAction();
411
fromOutcome = cnc.getFromOutcome();
412
toViewId = cnc.getToViewId();
413
if ((cncFromAction == null) && (fromOutcome == null))
{
414
result.viewId = toViewId;
415
result.navCase = cnc;
416
return result;
417
}
418
}
419
420
return null;
421
}
422
423
/** *//**
424
* @author robin
425
*/
426
class CaseStruct
{
427
428
/** *//**
429
* Field viewId.
430
*/
431
protected String viewId;
432
433
/** *//**
434
* Field navCase.
435
*/
436
protected ConfigNavigationCase navCase;
437
}
438
439
}
440
来看看其中的关键部分,149行起:
UIViewRoot newRoot = null;

if (flag)
{
//当检查URL通过时,使用CaseStruct,即faces-config.xml中的配置跳转
newRoot = viewHandler
.createView(context, caseStruct.viewId);

} else
{
//未通过URL检查时,直接到权限不足页面或你指定的页面
newRoot = viewHandler.createView(context,
"/backForward.jsp");
}
context.setViewRoot(newRoot);当然别忘了在faces-config.xml中加入自定义Application Navigation的配置,如下:
1
<faces-config>
2
<application>
3
<navigation-handler id="navigationWithAuth">
4
com.***.framework.NavigationHandleWithAuthImpl
5
</navigation-handler>
6
</application>
7
8
9


10
11
</faces-config> 注意:
在NavigationHandler中,当发现检查URL权限未能通过时,千万不要直接去修改当前的那个CaseStruts,因为JSF自己会缓存整个跳转的配置,以提高执行效率,请使用viewHandler.createView()来创建一个新CaseStruts,否则会发生跳转不正常的情况。
posted on 2007-10-16 14:07
Robin's Programming World 阅读(8692)
评论(6) 编辑 收藏 所属分类:
Java