首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 网站开发 > Web前端 >

利用maven创办webx3项目——实现简单的留言板(八)

2012-06-30 
利用maven创建webx3项目——实现简单的留言板(八)增加权限和白名单8、权限验证前面已经说过,创建的留言板系统

利用maven创建webx3项目——实现简单的留言板(八)
增加权限和白名单8、权限验证

前面已经说过,创建的留言板系统没有权限验证,导致每个页面不用登陆就能访问,那么现在就来加上权限验证,增加留言板系统的安全性。

?

首先了解一下权限验证的原理,前面也简单的提到过,其实就是利用cookie和session机制。

?

cookie实现

只用cookie是可以实现权限验证的,其过程为:

1、当登陆请求到来时,生成一个cookie,将其响应给用户;

? ? ?Cookie cookie = new Cookie("login","success");

? ? ?response.addCookie(cookie);

? ? ?然后客户端浏览器将创建一个cookie;

2、当请求再次到达时,检查请求报头中的cookie信息即可实现权限验证;

3、用户退出时,设置login=flase;

4、可以设置cookie,当浏览器关闭后,cookie马上销毁;也可以设置有效期。

但这种方式的安全性极低,如果人为的修改了cookie,那么系统是完全透明的。

?

session实现

session一般是保存在服务器中的一个记录请求会话的对象。

利用session实现权限验证,其过程为:

1、请求第一次到达时,新建一个session,将其唯一标识sessionId保存在cookie中,返回给客户端;

2、登录请求到达时,根据cookie中的sessionId,在此session种创建用户的信息<key,value>;

3、用户请求需要权限验证的页面时,根据cookie中的sessionId获取session,然后就可以拿到当前用户的信息。

4、用户退出时,将用户的信息从session中删除。

5、可以在用户退出时调用session方法清除session,也可以设置有效期控制session的生命周期。

这种方式通过服务器主动的进行权限验证,相对cookie来说比较安全。但也存在风险,比如csrf攻击。

webx3可以防御这种攻击,就是在页面中增加一个隐藏的字段。

?

webx3中的session可以保存在服务器端,也可以保存在客户端的cookie中,只需要在webx.xml中设置一下即可。

?

如下图:

?

设置将session保存在客户端的cookie中,还可以对session和cookie进行一些参数的设置,其实webx3实现一个session框架,具体内容参见《webx框架指南》。

?

设置好session后,就可以在系统中利用session进行权限验证了。

?

权限验证是通过在pipeline.xml中配置验证valve来实现的。

?

首先创建一个权限vavle类,如下:

?

com.alibaba.webx3.messageboard.util.AuthorizationValve.java

?

?

package com.alibaba.webx3.messageboard.util;import java.util.List;import javax.annotation.Resource;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpSession;import org.springframework.beans.factory.annotation.Autowired;import com.alibaba.citrus.service.pipeline.PipelineContext;import com.alibaba.citrus.service.pipeline.support.AbstractValve;import com.alibaba.citrus.service.uribroker.URIBrokerService;import com.alibaba.citrus.service.uribroker.uri.URIBroker;import com.alibaba.citrus.turbine.TurbineRunData;import com.alibaba.citrus.turbine.util.TurbineUtil;import com.alibaba.citrus.util.ServletUtil;import com.alibaba.citrus.util.StringUtil;public class AuthorizationValve extends AbstractValve {        @Autowired    private HttpServletRequest request;        @Autowired    private URIBrokerService   uriBrokerService;        @Resource(name="whiteListForLogin")    private List<String>       whiteListForLogin;        public void invoke(PipelineContext pipelineContext) throws Exception {        // 获取session        HttpSession session = request.getSession();        TurbineRunData rundata = TurbineUtil.getTurbineRunData(request);        String  sessionUser=null;                //获取session中的用户名,到相应的sessionStore中获取,若设置为cookie,则到cookie中查找        sessionUser = (String) session.getAttribute("login_user");        //取得request所请求的资源路径。        String path = ServletUtil.getResourcePath(request);                if (sessionUser == null) {            //不是白名单的页面,跳回登陆页面            if (!checkUri(path)) {                URIBroker loginUrl = uriBrokerService.getURIBroker("loginLink");                rundata.setRedirectLocation(loginUrl.render());                return;            }        }        pipelineContext.invokeNext();    }        //检查白名单    private boolean checkUri(String path) {        int lastSlashIndex = path.lastIndexOf("/");        //最后的页面下划线大写处理        if (lastSlashIndex >= 0) {                        path = path.substring(0, lastSlashIndex) + "/" + StringUtil.toCamelCase(path.substring(lastSlashIndex + 1));        } else {            path = StringUtil.toCamelCase(path);        }        return whiteListForLogin != null && whiteListForLogin.contains(path) ? true : false;    }}

?

其中checkUri()方法是检查设置的白名单,在白名单中的页面可以面登录访问,其中白名单对象就是List<String> whiteListForLogin,是通过注入得到的,其真实的身份是在webx.xml中配置的bean,如下:

?

?

......  <!-- 装载模块。 -->    <services:module-loader>        <ml-factories:class-modules>            <ml-factories:search-packages type="$1" packages="com.alibaba.webx3.common.module.*" />        </ml-factories:class-modules>    </services:module-loader>         <!-- 免登陆访问的白名单 -->    <beans:bean id="whiteListForLogin"  ><beans:constructor-arg><beans:list>    <beans:value>/index.htm</beans:value>    <beans:value>/register.htm</beans:value></beans:list></beans:constructor-arg></beans:bean>       </beans:beans>
?

?

?

然后在pipeline.xml中配置该valve,如下:

?

?

?

......    <!-- 检查csrf token,防止csrf攻击和重复提交。假如request和session中的token不匹配,则出错,或显示expired页面。 -->        <checkCsrfToken />                <!-- 登陆权限验证 -->        <valve />        <loop>......

?

?

此时只是完成了验证的功能,但是还能没有给系统增加授权的语句。

?

在userAction.java中增加授权的语句。

?

当用户登录时,在session中增加用户信息,退出时清除session。

?

?

package com.alibaba.webx3.messageboard.module.action;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import org.springframework.beans.factory.annotation.Autowired;import com.alibaba.citrus.turbine.Context;import com.alibaba.citrus.turbine.Navigator;import com.alibaba.citrus.turbine.dataresolver.FormGroup;import com.alibaba.webx3.messageboard.biz.service.UserService;import com.alibaba.webx3.messageboard.dao.object.UserDO;import com.alibaba.webx3.messageboard.module.vo.UserVO;public class UserAction {    @Autowired    private UserService         userService;    @Autowired    private HttpServletResponse response;    @Autowired    private HttpServletRequest  request;    // 登陆    public void doLogin(@FormGroup("login")    UserVO user, Context context, Navigator nav, HttpSession session) {        String username = user.getUsername();        String password = user.getPassword();        UserDO userdo = null;        boolean success;        // 根据用户名获得用户记录        userdo = userService.getByUsername(username);        if (userdo == null) {            context.put("message", "用户名不存在!");            nav.forwardTo("index");            return;        }        // 校验密码是否正确        if (password.equals(userdo.getPassword())) {            success = true;        } else {            success = false;        }        // 判断执行转向和重定向        if (success) {            session.setAttribute("login_user", user.getUsername());            context.put("username", user.getUsername());            nav.redirectTo("messageLink").withTarget("messageList");        } else {            context.put("message", "密码错误!");            nav.forwardTo("index");        }    }    // 退出    public void doLogout(Navigator nav, HttpSession session) {        session.invalidate();        // 转到首页        nav.redirectTo("messageBoardLink").withTarget("index");    }}
?

?

?

在messageList.vm中增加权限的判断,admin用户可以删除和编辑,其他用户只能操作自己添加的留言,如下

?

?

<div style="font-size:10pt"><p>留言列表&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a  href="$messageBoardLink.setTarget("message/addMessage")">添加留言</a></p><p style="color:red">$!message</p><p>--------------------</p><form action="" method="post" target="_self">$csrfToken.hiddenField<input type="hidden" name="action" value="messageAction"/>#foreach($messageItem in $messageList)<p>标题:$!messageItem.title</p><p>作者:$!messageItem.author</p><div style="color:blue">        <p>$!messageItem.content</p></div>#if($admin==true||$username==$messageItem.author)        <a href='#' onclick="deleteMessage($messageItem.id)">删除</a><a href='$messageBoardLink.setTarget("message/modifyMessage").addQueryData("messageId", $messageItem.id)'>编辑</a>#end<p>--------------------</p>#end<input type="hidden" id="messageId" name="messageId" value="$messageItem.id"><input type="submit" id="delete" style="display:none;"  name="event_submit_do_delete" />    </form></div><script>function deleteMessage(id){if(confirm("确定要删除?")){document.getElementById("messageId").value=id;document.getElementById("delete").click();}}</script>
?

?

至此,权限验证就完成了。

?

现在可以把webx-app1.xml删掉,代码可以不用管,这样子应用app1就不会加载了,其实不删也没什么关系。

?

留言板系统也就此结束~

?

9、总结

1、在实现权限系统时出现了一些问题,在配置白名单的bean时,将其配置到webx-messageboard.xml中,启动容器的时候加载app1出错:no bean named?whiteListForLogin,折腾了好久才知道,app1和messageboard先前设置的公用同一个pipeline,但是whiteListForLogin 这个vale只配在了messageboard的子容器中,所以会出现上述错误;

解决办法:将whiteListForLogin 配置到webx.xml中

2、解决了上述问题后,又报错:No matching bean of type [java.lang.String] found for dependency [collection of java.lang.String],经过网上搜刮,发现原因是自动注入的问题;.AuthorizationValve.java中注入whiteListForLogin不能用@Autowired,改用@Resource(name="whiteListForLogin")就解决了。具体原因可以参考 http://stackoverflow.com/questions/1363310/auto-wiring-a-list-using-util-schema-gives-nosuchbeandefinitionexception

?

3、通过这个小实践,加深了对webx3开发的理解,同时学到了不少web开发知识~在此感谢身边的同事,谢谢他们对我的帮助。

?

4、对webx3的原理还不是很熟悉,下一步准备研究一下~

?

?

?

关于webx3的session

?

webx3中实现了一个session框架,可以通过webx.xml中的设置将session保存在不同的地方,常用的有服务器内存和cookie;还可以将session的不同部分分别保存到不同的地方。

session框架

session ID是唯一标示,一般将其保存在cookie中,这样相同cookie值的请求都看作是同一个session的请求。

session的生命周期:第一个请求时创建;在访问期间可以不断地更新;超过配置的最大不活动时间就会结束,还可以通过调用session.invalidate()方法,直接清除session的所有内容;

Session Store是session框架中最核心的部分,定义了session保存的位置,可以设置多个store,这样就可以将session不同的部分保存在不同的地方。

在session框架中,有一个重要的特殊对象,用来保存session生命期的状态。这个对象叫作session model。

Session Model是用来记录当前session的生命期数据的,例如:session的创建时间、最近更新时间等。

? ? SessionModelEncoder

? ? 默认情况下,SessionModel对象将被转换成一个JSON字符串,然后这个字符串将被保存在某个session store中;读取时需要解码成SessionModel对象。

? ? 默认实现为:

? ? ? ? ? ? ? ? ? ? <session-model-encoders>

? ? ? ? ? ? ? ? ? ? ? ? ? ?<model-encoders:default-session-model-encoder />

? ? ? ? ? ? ? ? ? ? </session-model-encoders>

Session Interceptor拦截器的作用是拦截特定的事件,甚至干预该事件的执行结果。

Cookie Store

Cookie Store的作用,是将session对象保存在客户端cookie中。Cookie Store减轻了服务器维护session数据的压力,从而提高了应用的扩展性和可用性。

但是读写cookie比较麻烦,还要在代码中设置很多参数:domain、path、httpOnly...等,所以通过操作HttpSession,session框架就帮我们读写cookie了(那些参数在配置文件里配置就ok)。webx主张把一切对cookie的读写,都转换成对session的读写。

? ? Session Encoders

? ? Session里保存的是Java对象,而cookie中只能保存字符串。如何把Java对象转换成合法的cookie字符串(或者将字符串恢复成对象)呢?这就是Session Encoder所要完成的任务。详细见下面。

Cookie Store需要依赖其它两个Request Contexts: <buffered>(将所有的输出到response.getWriter()或getOutputStream()的内容缓存在内存里,直到最后一刻才真正

输出到浏览器) 和 <lazy-commit>(拦截了response对象中引起提交的方法,将它们延迟到最后才执行。)

Cookie Store分为多值和单值;

多值Cookie Store是在一组cookie(如tmp0, tmp1, ...)中保存一组attributes的名称和对象。它所创建的cookie值,只有session框架自己才能解读,如<key,value>的形式。

单值cookie store就是在一个cookie中仅保存一个值或对象,如<object>。

? ? Session Encoders和Session Value Encoder

? ? 这两个cookie store的结构是不一样的。因此解码的方法也不一样。单值的cookieStore使用Session Value Encoder解码;多值的cookieStore使用Session Encoder。

? ? Session Encoders

? ? Session Encoder需要转换一组session attributes的key-values。Session框架提供了一种encoder的实现,编码的基本过程为:序列化、加密(可选)、压缩、Base64编码、URL encoding编码。

? ? 保存session数据时,session框架将使用第一个encoder来将对象转换成cookie可接受的字符串;

? ? 读取session数据时,session框架将依次尝试所有的encoders,直到解码成功为止。

? ? 默认实现为:

? ? ? ? ? ? ? ? ? ? ? <session-stores:encoders>

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?<session-encoders:serialization-encoder />

? ? ? ? ? ? ? ? ? ? ? </session-stores:encoders>

?

? ? Session Value Encoder

? ? session Value Encoder只转换sessionattribute的值。

? ? 和SessionModelEncoder以及SessionEncoder类似,session框架也支持多个session valueencoders同时存在。

? ? ?? 保存session数据时,session框架将使用第一个encoder来将对象转换成cookie可接受的字符串;

? ? ?? 读取session数据时,session框架将依次尝试所有的encoders,直到解码成功为止。

? ? 这种编码、解码方案可让使用不同session value encoders的系统之间共享cookie数据,也有利于平滑迁移系统。

? ? 目前有两种基本的session value encoders实现。<simple-value-encoder>和<mappedvalues-encoder>

?

Simple Memory Store

SimpleMemoryStore是最简单的session store。它将所有的session对象都保存在内存里面。这种store不支持多台机器的session同步,而且也不关心内存是否被用尽。因此这种简单的store一般只应使用于测试环境。

<stores>

<session-stores:simple-memory-store id="simple" />

</stores>

热点排行