|
目标:
u 掌握使用登录功能对系统的用户进行认证;
u 掌握如何控制只有具有相应权限的用户才可以访问相应的资源;
u 掌握如何控制页面中的部分功能只能在特定的情况下才能使用;
u 掌握如何对多个文件的安全同时进行控制;
u 了解验证码的工作原理。
网站的很多功能是普通用户不能访问的,例如添加图书这样的功能只能由管理员完成,普通用户是无法访问的,如果普通用户要访问,应该提示普通用户没有相应的权限。本章介绍如何控制用户的权限。
第4、5、6章中介绍的登录功能只是简单的模拟,现在对该功能进行完善。
用户在登录界面输入用户ID和口令,系统根据用户输入的用户ID和口令进行处理:
l 用户ID和口令都正确,并且当前用户是管理员,则跳转到管理员的默认界面。
l 如果用户ID和口令正确,并且是普通用户,则跳转到普通用户的默认界面。
l 如果用户ID和口令不正确,则重新转向登录界面。
不管是管理员还是普通用户,登录之后的默认界面应该是用户使用频率最高的界面。如果网站比较智能,可以对用户的访问进行统计,为用户显示经常使用的界面,或者上次访问的界面。对于管理员来说,使用频率最高的功能应该是订单管理,所以应该把订单管理作为管理员的默认界面。对于普通用户来说,使用频率最高的功能应该是图书查看,所以应该把图书查看界面作为普通用户的默认界面。
仍然采用MVC模式来设计该功能。
首先是M部分,可以使用UesrBean中原有的方法findUserById。
其次是V部分,登录界面在前面已经完成,图书查看功能是读者在前面实训中完成的功能,假设为findBooks,订单管理是下一章将要完成的实训内容,假设为findOrders。
最后是C部分,获取用户输入的用户ID和口令,根据ID调用模型的findUserById方法,根据返回值进行处理:
u 如果返回值为null,则说明用户不存在;
u 否则,判断口令是否匹配:
n 如果不匹配,说明口令不正确;
n 如果匹配,判断用户的类型:
u 如果用户类型为1,说明该用户为管理员;
u 否则为普通用户。
根据上面的分析,修改后的控制器的代码如下:
package bookstore.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import bookstore.bean.*;
public class LoginServlet extends HttpServlet
{
public void doGet(HttpServletRequest request,HttpServletResponse response)
throws IOException,ServletException {
// 获取用户提交的信息
String userid = request.getParameter("userid");
String userpass = request.getParameter("userpass");
// 创建JavaBean对象
UserBean ub = new UserBean();
// 转向的文件
String forward=null;
// 提示信息
String info=null;
UserBean user=ub.findUserById(userid);
// 用户不存在
if(user == null){
forward="login.jsp";
}
// 口令不正确
else if(!user.getUserpass().equals(userpass)){
forward="login.jsp";
}
// 管理员
else if(user.getType().equals("1")){
forward="findOrders";
}
// 普通用户
else{
forward="findBooks";
}
// 定义跳转文件
RequestDispatcher rd=request.getRequestDispatcher(forward);
// 完成重定向
rd.forward(request,response);
}
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws IOException,ServletException {
doGet(request,response);
}
}
只有管理员可以使用图书添加功能,如果用户没有登录,提示用户“您还没有登录,请先登录!”,如果当前用户是普通用户,提示用户“您不是管理员,不能添加用户”。如果当前用户是管理员,则显示添加界面。
首先,直接访问图书添加的页面,得到如图13.1所示界面。
图13.1 没有登录的提示界面 图13.2 没有权限的提示界面
然后,点击“登录”超链接跳转到登录界面,输入普通用户的ID和口令,然后再访问图书添加界面,得到如图13.2所示界面。
然后,点击“登录”超链接跳转到登录界面,输入管理员用户ID和口令,然后再访问图书添加界面,得到如图9.1所示的用户添加界面。
要防止这个文件被没有权限的人访问,必须对每个访问这个文件的人的身份进行验证,要想知道当前用户的权限,在登录的时候必须记录当前用户的权限。
在访问这个文件的时候,首先应该从session中获取用户信息,如果用户不存在,跳转到“没有登录”的提示界面,如果用户是普通用户,跳转到“没有权限”的提示界面,不让用户访问当前页面,这样就保证了当前页面的安全。
为了方便用户的操作,需要在信息提示页面增加到登录页面的超链接。
使用session对象能够在多个页面之间共享信息,在Servlet中也可以使用session对象,但是与在JSP中不同,在JSP中session对象是内部对象,在Servlet中需要先获取session对象。要完成安全控制需要使用session对象保存用户信息,下面介绍关于session的常用操作。
session对象存储在request对象中,request对象也就是服务类方法的第一个参数,可以使用HttpServletRequest的下面的方法来获取session对象:
HttpSession getSession(boolean);
通常是在doGet方法或者doPost方法中,通过第一个参数request来获取。代码如下:
HttpSession session;
session =request.getSession(true);
获取session对象之后,如果希望在session中存储信息,可以使用下面的方法:
session.setAttribute("name",value);
用于在session中保存信息,有两个参数,第一个参数是字符串,是要存储信息的名字,第二个参数是保存的值本身,可以是各种类型的对象。如果第一个参数所指定的名字存在,会替换该名字原来对应的值;如果第一个参数所指定的名字不存在,会把第二个参数指定的值存储到session中。
如果要从session中获取已经保存的信息,可以使用下面的方法:
session.getAttribute("name");
参数就是要获取的信息的名字。在使用的时候对得到的信息必须进行强制类型转换,因为方法的返回值类型是Object。例如在session中存储的是String对象,要得到这个对象,可以使用下面的代码:
String str = (String)session.getAttribute("name");
如果要删除session中的某个信息,可以使用下面的方法进行删除:
session.remove("name");
参数就是要删除的session中的对象的名字。
如果不想使用session了,可以使用下面的方法:
session.invalidate()
使用该方法之后,就不能继续使用session了,包括session中的所有信息。
在登录成功之后,把用户信息保存到session中。因为这个信息是与特定用户相关的,在用户的整个访问过程中都有效。
在Servlet中首先要获取session对象,在用户登录成功之后,把用户对象保存到session中。修改后的Servlet的代码如下:
package bookstore.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import bookstore.bean.*;
public class LoginServlet extends HttpServlet
{
public void doGet(HttpServletRequest request,HttpServletResponse response)
throws IOException,ServletException {
// 获取用户提交的信息
String userid = request.getParameter("userid");
String userpass = request.getParameter("userpass");
// 创建JavaBean对象
UserBean ub = new UserBean();
// 转向的文件
String forward=null;
// 提示信息
String info=null;
// 得到session对象
HttpSession session;
session =request.getSession(true);
UserBean user=ub.findUserById(userid);
// 用户不存在
if(user == null){
forward="login.jsp";
}
// 口令不正确
else if(!user.getUserpass().equals(userpass)){
forward="login.jsp";
}
// 管理员
else if(user.getType().equals("1")){
session.setAttribute("login",user);
forward="findOrders";
}
// 普通用户
else{
session.setAttribute("login",user);
forward="findBooks";
}
// 定义跳转文件
RequestDispatcher rd=request.getRequestDispatcher(forward);
// 完成重定向
rd.forward(request,response);
}
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws IOException,ServletException {
doGet(request,response);
}
}
在原来的添加界面的代码中,增加对用户是否登录已经是否有相应的权限的判断。判断过程如下:
u 从session中获取“login”对象,如果不存在表示没有登录,转向需要登录的提示界面;
u 如果用户存在,并且是普通用户,转向没有权限的提示界面;
u 如果是管理员,继续向下执行即可。
用于判断的代码如下:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!--判断session中是否存在login-->
<c:if test="${empty login}">
<!--跳转到需要登录的提示界面-- >
<jsp:forward page="needlogin.jsp"/>
</c:if>
<!--判断session中用户是否是管理员-->
<c:if test="${login.type==0}">
<!--跳转到没有权限的提示界面-->
<jsp:forward page="noright.jsp"/>
</c:if>
把这段代码添加到addUser.jsp页面的前面即可。
上面完成了对添加用户的界面的安全控制,这样如果不是管理员就不能访问这个界面了。好像已经完成了安全控制,事实是不是这样呢?
添加用户的过程是:在添加界面输入用户信息,提交给控制器处理,控制器调用业务方法完成添加过程。如果用户直接访问控制器怎么办?因为控制器是Servlet,是允许访问的。
有人会说虽然可以访问,但是没有用户信息,仍然不能添加。实际上,可以通过问号传递参数,效果与表单提交完全相同。在前面讲分页显示的时候,页码就是通过问号传递的。所以要想保证用户添加功能的安全,还需要对控制器进行安全控制。
在控制器中进行安全控制,主要的过程:
u 从session中获取登录信息
u 然后对登录信息进行判断:
n 如果为null,说明用户没有登录,跳转到“需要登录”的提示界面;
n 如果不为null,并且type为0说明是普通用户,跳转到“没有权限”的提示界面。
修改添加用户的Servlet,在doGet方法的最前面添加如下代码:
// 得到session对象
HttpSession session;
session =request.getSession(true);
// 从session中获取登录信息
UserBean login = (UserBean)session.getAttribute("login");
// 判断登录信息
if(login==null || login.getType().equals("0")){
response.sendRedirect("login.jsp");
}
这样,添加用户的页面和添加用户的控制器全都控制了。
除了上面介绍的方法之外,JSP还提供了一种比较方便的方式能够很好的解决安全问题。这种方式是Servlet过滤器,它的特点是可以对用户的请求进行过滤,也可以过滤对用户的响应。相对于前面的处理方式,使用过滤器可以不用修改要进行安全控制的功能文件。下面介绍如何使用过滤器完成添加功能的安全控制。
Servlet过滤器是特殊的Servlet,Servlet过滤器可以对用户的请求信息和响应信息进行过滤,当我们访问Servlet过滤器所对应的Servlet的时候,会先执行Servlet过滤器,对请求和响应的信息进行过滤。
可以指定Servlet过滤器和特定的URL关联,只有当客户请求访问此URL的时候,才会触发过滤器工作。
独立的Servlet过滤器可以被串联在一起,形成管道效应,协同修改请求和响应对象。每个Servlet完成不同的过滤功能。
所有Servlet过滤器类必须实现javax.servlet.Filter接口,同时需要继承HttpServlet。
接口中的主要方法有:
init(FilterConfig):初始化方法,Servlet容器创建Servlet过滤器实例后调用这个方法,可以读取web.xml 文件中Servlet过滤器的初始化参数;
doFilter(ServletRequest,ServletResponse,FilterChain):完成实际的过滤操作,参数FilterChain用于访问后续的过滤器;
destory():在销毁过滤器实例前调用该方法。
下面的实例LoginFilter.java完成对Servlet:Login.java的过滤。
package bookstore.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class AddUserFilter extends HttpServlet implements Filter {
private FilterConfig filterConfig;
public void init(FilterConfig filterConfig) {
this.filterConfig = filterConfig;
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) {
// 得到session对象
HttpSession session;
session =request.getSession(true);
// 从session中获取登录信息
UserBean login = (UserBean)session.getAttribute("login");
// 判断登录信息
if(login==null || login.getType().equals("0")){
response.sendRedirect("login.jsp");
}
// 继续调用其它的过滤器
try {
filterChain.doFilter(request, response);
}
catch(Exception e) {
}
}
public void destroy() {
}
}
在doFilter方法中,前两个参数就是包含请求信息和响应信息的request和response对象。处理过程与前面介绍的验证过程相同。
和Servlet一样,过滤器需要在web.xml中配置。过滤器的配置也包括两个方面:第一是Servlet过滤器的声明,第二是配置Servlet过滤器对应的URL。
首先是Servlet过滤器的声明,通过<filter>元素声明
<filter>
<filter-name>过滤器名字</filter-name>
<filter-class>过滤器类名</filter-class>
<init-param>
<param-name>参数名</param-name>
<param-value>参数值<param-value>
</init-param>
</filter>
其中,filter-name确定过滤器的名字,filter-class确定过滤器对应的类名,如果使用参数,通过init-param元素来声明。
上面的LoginFilter可以使用下面的代码声明:
<filter>
<filter-name>addUserFilter</filter-name>
<filter-class> bookstore.servlet.AddUserFilter</filter-class>
</filter>
然后是Servlet过滤器的映射,与url进行关联,通过<filter-mapping>元素声明
<filter-mapping>
<filter-name>过滤器名字</filter-name>
<url-pattern>访问路径</url-pattern>
</filter-mapping>
其中,filter-name是过滤器的名字,url-pattern是关联的url,当我们访问这个url时候,会触发该过滤器,需要保证这里的filter-name和filter元素中的filter-name一致。上面的addUserFilter是对AddUserServlet的过滤,应该配置如下:
<filter-mapping>
<filter-name>addUserFilter</filter-name>
<url-pattern>/addUser</url-pattern>
</filter-mapping>
通过这样的配置之后,在AddUserServlet中就不用进行配置了。
上一节采用的方式能够解决用户添加页面的安全问题,但是网站上存在大量的需要进行安全控制的页面,如果在每个界面添加这样一段代码,不仅需要很大的工作量,而且后期的维护会很麻烦,也不符合我们软件工程中的代码共享思想。既然每个页面都要使用这样的功能,并且这样的功能在每个文件中是相同的,能不能把它们放在一个专门的文件中,需要的时候再去调用这个文件?JSP中提供了相应的解决方法,本节的内容就是要解决这个问题的。
首先把验证的代码专门放在一个文件中,把这个文件命名为checkuser.jsp,文件的代码如下:
<%@ page contentType="text/html;charset=gb2312"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!--判断session中是否存在login-->
<c:if test="${empty login}">
<!--跳转到需要登录的提示界面-->
<jsp:forward page="needlogin.jsp"/>
</c:if>
<!--判断session中用户是否是管理员-->
<c:if test="${login.type==0}">
<!--跳转到没有权限的提示界面-->
<jsp:forward page="noright.jsp"/>
</c:if>
然后删除图书添加界面中的安全控制代码,在原来的安全控制代码所在位置使用指令引入这个专门的验证文件,这个包含指令的格式如下:
<%@ include file="目标文件"%>
语法格式与page指令非常类似,开始标志和结束标志相同,使用file属性指定目标文件。它的作用是把目标文件的内容拷贝到标签所在的位置,然后形成一个文件,在运行的时候只有一个文件存在。
在本例中,需要在图书添加界面中调用这个共享的文件,代码如下:
<%@ include file="checkuser.jsp"%>
思考:如果要在控制器中共享安全控制的代码,如何实现?
从字面意思看,两者都有包含的意思,并且从运行的结果上来看,两者的功能也非常类似,但是两者的运行过程相差很大。
<%@ include%>指令是编译时语法,也就是在编译的时候,把指令所指向的目标文件的内容拷贝到指令所在的位置,替换指令,最终形成一个文件,在运行的时候只有一个文件。
<jsp:include>动作是运行时语法,包含<jsp:include>动作的文件在执行到这个标签的时候,会转向执行标签所指向的目标文件,执行完目标文件之后,再接着执行标签后的内容,在运行的时候,涉及到两个文件,就像方法调用一样。
那么什么时候应该使用<%@ include%>指令,什么时候使用<jsp:include>标签呢?因为<%@ include%>指令是静态的,而<jsp:include>是动态的,所以如果某一段代码肯定会执行,则可以使用<%@ include%>指令,如果某一段代码有可能执行,有可能不执行,需要根据运行时候的状态,这时候可以使用<jsp:include>。
在实际应用中,很多网站的导航部分和版权信息部分都是相同的,就像上面的验证过程一样,在每个文件中都会出现,所以可以考虑把这些内容放在单独的文件中,然后使用<%@ include%>指令引用使用。
前面介绍的安全控制方式,不管是针对JSP文件,还是针对Servlet,都是针对每个文件的。实际上,所有的管理功能都需要进行相同的验证。如果能进行集中的安全控制,就不用为每个功能文件编写安全控制的代码,这样就简单多了。
要进行集中的安全控制,可以使用前面介绍的过滤器。对添加功能中的Servlet进行控制的过滤器部分配置代码如下:
<filter-mapping>
<filter-name>addUserFilter</filter-name>
<url-pattern>/addUser</url-pattern>
</filter-mapping>
<url-pattern>指出对那个功能进行控制。在<url-pattern>中可以使用通配符*,例如下面的代码:
<filter-mapping>
<filter-name>addUserFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
可以对所有的请求进行过滤。
在bookstore应用中,并不是所有的功能都需要进行安全控制,例如,登录界面。那么如何能够方便的控制需要进行安全控制的功能呢?可以让具有相同访问权限的功能具有相似的访问方式,例如与管理相同的功能的访问路径都是以manager(除了应用路径http://127.0.0.1:8080/bookstore之外)开始的。
可以把所有管理员才能访问的页面放在“manager”文件夹中,所有管理员才能访问的Servlet的<url-pattern>都写成“manager/*”的方式。然后配置过滤器的<url-pattern>的时候可以写成:
<url-pattern>/manager/*</url-pattern>
具体的过滤器的编写与前面介绍的完全相同。通过这样的配置,就可以控制所有管理员的功能只能被管理员使用。
在bookstore的图书信息察看功能中,如果是普通用户只能查看图书信息,不能删除和修改图书信息,如果是管理员不仅可以查看图书信息,还可以删除和修改图书信息。这样就会产生下面的问题:普通用户能够访问图书信息察看界面,但是不能使用界面上的删除和修改功能。使用前面介绍的安全控制方法就不能解决了,需要对页面的局部进行安全控制。选择业面中的删除功能进行控制。
删除功能的代码如下:
<form action="deleteBoo" method="post"
onSubmit="return confirm('真的要删除该图书吗?');">
<input type="hidden" name="bookid" value="${book.bookid}">
<input type="submit" value="删除">
</form>
使用<c:if>标签判断用户是否是管理员,如果是显示上面的代码,如果不是则不显示,修改后的代码如下:
<c:if test="${(!empty login) && login.type==1}">
<form action="deleteUser" method="post"
onSubmit="return confirm('真的要删除该用户吗?');">
<input type="hidden" name="userid" value="${user.userid}">
<input type="submit" value="删除">
</form>
</c:if>
在很多网站中为了安全,都在登录的时候使用图形验证码,接下来简单介绍图形验证码的作用和实现原理。
在原来Web应用的登录功能中,主要是通过对输入的用户名和口令进行验证。如果不法分子知道了某个用户的用户名,但是不知道口令,他可以试验所有的组合,最后肯定能找出口令。随着计算机计算能力的提高,通过试验来猜测口令就变的非常简单了。
为了增加口令被破解的难度,提出了图形验证码。增加图形验证码之后,对于普通用户来说,在使用的时候需要多输入一项信息,而对于口令的破解者来说,需要先识别这些验证码(一般比较困难),更重要的是这个验证码每次都变化,这样口令被破解几乎不可能。
基本工作原理就是在用户登录界面上以图形的方式显示一些符号,通常是字母和数字,并且这些符号是随机生成的。用户把这些图形码的信息再输入到输入框进行验证。
如何进行验证呢?系统需要保存图形码对应的信息,然后与用户输入的信息进行匹配。信息可以保存在客户端也可以保存在服务器端,相对来说放在服务器端比较安全。
如果要保存在客户端,可以使用隐藏域或者类似的机制保存,但是如果直接保存,容易让口令的破解者获取到,所以通常会加密。
如果保存在服务器端,可以把它保存在session中,在服务器端进行匹配。
Web应用的安全主要是通过用户在登录网站的时候提供用户名和口令来解决。为了防止用户恶意攻击,还可以采用与图形码相结合的方式。
因为在用户后续的访问过程中,仍然需要对安全进行处理,但是不能要求用户在每个页面都输入用户名和口令,所以需要保存用户信息。所以在登录的时候需要把用户信息保存在session中。
不仅需要控制界面的安全,更重要的是要控制Servlet的安全。可以采用共享文件来完成所有的安全控制。
也可以使用过滤器完成安全控制,并且可以通过通配符进行集中的安全控制。
如果页面中的某个功能需要进行访问控制,可以在页面中添加安全控制的代码。
1、 为了在以后的访问过程中,使用用户信息,可以把用户信息保存在以下什么对象中?
A)application B) session C)request D)pageContext
2、 用户通过输入框输入用户名,假设输入框的名字是username,希望把用户输入的用户名保存到session中,应该如何编写代码?
3、 要判断用户输入的用户名ueername和口令userpass是否相同,如何判断?
4、 在session中保存了用户的权限,使用的名字是degree,如果degree的值是1表示用户是管理员,如果degree的值是0表示用户是普通用户。请据此编写代码输出用户的身份。
5、 需要把a.jsp中的内容显示在b.jsp文件中,请使用<%@ include%>标签完成。
6、 用户名保存在session中,要输出用户名,写出相应的代码。
7、 简述<%@ include%>标签与<jsp:include>动作的相同点和不同点。
如对本文有疑问,请提交到交流论坛,广大热心网友会为你解答!! 点击进入论坛