随笔-193  评论-715  文章-1  trackbacks-0
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 阅读(8327) 评论(6)  编辑  收藏 所属分类: Java

评论:
# re: JSF深入--控制跳转 2007-10-16 14:39 | cooky
你好! 为什么不用饭否呢?
just an advice :)  回复  更多评论
  
# re: JSF深入--控制跳转 2007-10-16 15:11 | Robin's Java World
@cooky
饭否?
什么意思?没明白。  回复  更多评论
  
# re: JSF深入--控制跳转 2007-10-16 17:46 | Robin's Java World
@cooky
明白了。饭否也还不错。呵呵!  回复  更多评论
  
# re: JSF深入--控制跳转 2007-10-17 12:56 | Alexander.Yu
一个跳转就要写500多行...晕倒.  回复  更多评论
  
# re: JSF深入--控制跳转 2007-10-18 12:05 | Robin's Java World
@Alexander.Yu
其实这是参照JSF官方实现来做的,大部分代码都是原来的官方的代码,没有改动,只是改了记LOG的方式和一些关键的部分。
  回复  更多评论
  
# re: JSF深入--控制跳转 2008-04-01 18:05 | gembin
关注JSF  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航: