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,解决这一问题。先给出详细的实现:
1public 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 阅读(8685)
评论(6) 编辑 收藏 所属分类:
Java