【web第十八天】Filter过滤器
Filter
1.过滤器概念
1.1.过滤器的基本概念
Filter也称之为过滤器,它是Servlet技术中最实用的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
是javaEE三大组件之一:Servlet、Filter、Listener
1.2.过滤器的功能
(1)过滤器可以拦截对资源的访问
(2)一个过滤器可以拦截多个资源,一个资源也可能被多个过滤器拦截
(3)所谓的拦截是根据访问的URL地址来确定访问的是哪个资源,确定是否拦截
(4)所谓的拦截其实就是拦截下来代表请求的request和响应的response
(5)拦截后:控制是否允许访问、访问之前和之外做一些额外操作
这种多个过滤器拦截一个资源的模式 称之为 责任链模式
2.过滤器开发入门
2.1.开发过滤器的步骤
想要开发一个过滤器,需要如下两个步骤:
(1)写一个类实现Filter接口
(2)在web.xml中配置过滤器
2.2.写一个类实现javax.servlet.Filter接口
init为初始化方法,在Filter对象被创建出来时,Servlet容器会调用该方法对filter进行初始化。
destory为销毁的方法,在过滤器对象被销毁之前,服务器会调用这个方法执行善后工作。
doFilter为过滤器中最核心的方法,当过滤器拦截到对资源的访问时,服务器会自动调用该方法执行过滤代码。我们只需要在这个方法中设计过滤器的逻辑代码即可。
2.3.在web.xml中配置一下过滤器
<filter> -- 配置一个过滤器
<filter-name>FirstFilter</filter-name> -- 过滤器的名字
<filter-class>cn.tedu.filter.FirstFilter</filter-class> --过滤器类的全路径名
</filter>
<filter-mapping> -- 过滤器的拦截路径配置,可以配置多个
<filter-name>FirstFilter</filter-name> -- 为哪个名字的过滤器配置
<url-pattern>/*</url-pattern> -- 拦截哪个路径资源可以配置多个
<servlet-name>XxxServlet</servlet-name> -- 拦截哪个名字的Servlet
<dispatcher></dispatcher> -- 指定过滤器拦截哪种方式对资源的访问,可
以取值为REQUEST FORWARD INCLUDE ERROR,如果不配置,默认只
拦截 REQUEST方式的访问。可以配置多个。
</filter-mapping>
2.4.Filter生命周期
在web应用启动时,会创建出web应用中配置的过滤器对象,创建出过滤器对象后立即调用init方法进行初始化的操作,之后一直存活,直到web应用被销毁时,Filter跟着被销毁,在销毁之前调用destory方法执行善后工作。在存活期间,每当拦截到资源,就执行dofilter方法 来执行过滤器的逻辑,如果不做操作 则默认拦截,可以通过调用filterchain的dofilter方法来放行对资源的访问。并且可以在dofilter方法之前或之后做一些额外的操作。
2.5.细节
如果一个资源被多个过滤器所拦截,多个过滤器的拦截顺序,取决与web.xml中filter-mapping 配置的顺序。
多个过滤器的执行,类似于方法一层一层调用的过程,一层一层往里钻,再一层一层往外出。
2.6.和filter开发相关的对象
FilterConfig:
init方法的参数。
代表filter在web.xml中的配置对象。
可以用来获取filter的初始化参数
可以用来获取ServletContext对象
FilterChain:
dofilter方法的参数
代表过滤器链
提供了doFilter方法,放行当前过滤器,执行后续过滤器,如果后续没有过滤器,则调用到最终访问的资源
3.Filter案例一:全站乱码解决过滤器
在web开发的过程中,存在请求参数乱码,和响应输出乱码。
之前的开发中,在所有的Servlet和jsp页面中,需要手动的解决这两种乱码。
可以通过开发过滤器,拦截所有的资源访问,在过滤器中解决全站乱码问题,这样只需要一个过滤器,就可以解决全站乱码,而不需要在每个资源中都单独去解决乱码了。
响应乱码:
response.setContentType("text/html;charset="+encode);
请求乱码:
post请求:
request.setCharacterEncoding(encode);
get请求:
方案1:获取请求中的参数,解决完乱码,再设置回去。
不可能为每个servlet都获取参数解决乱码 -- getParameterMap
就算都获取出来解决了乱码也没有办法将解决好乱码的请求参
数设置回去 -- 无解
方案2:request中的请求参数本身无法改变,那么,换一个思路想办法改造和获取请求参数相关的方法,在方法内加上解决乱码的代码,这样通过这些方法获取请求参数时,解决好乱码再返回用起来就感觉,乱码被解决了一样。
改造一个已有对象身上的方法????
继承
只能先继承再创建对象,如果用原来的类创建对象,对这些对象没有影响。
装饰
可以改造已有对象身上的方法,但是当原来对象身上的
方法比较多的时候,开发起来比较麻烦。
动态代理
可以改造已有对象身上的方法,并且只需要改造要改造的方法,其他方法不需要额外编写,非常方便。
Easymall使用装饰的方式实现乱码解决
在EncodingFilter中添加或修改如下代码: /** * 内部类 ServletRequest的装饰类 改造了获取请求参数相关的方法 增加了乱码解决的代码 */ //继承了HttpServletRequestWrapper ,这个父类本身就是 HttpServletRequest的装饰器 在其中提供方法的默认的实现 不想改造的方法 不用管 想改造的方法 覆盖父类方法即可 class MyServletRequest extends HttpServletRequestWrapper{ private ServletRequest request = null; private boolean hasNotEncode = true; //构造器 接受传入的request保存在类的内部 public MyServletRequest(HttpServletRequest request) { super(request); this.request = request; } //覆盖和获取请求参数相关的方法 @Override public Map<String,String[]> getParameterMap() { try { //获取请求参数组成的map Map<String,String[]> map = request.getParameterMap(); if(hasNotEncode){//由于request对此map会缓存 所以解决乱码的操作 只需要做一次 此处通过hasNotEncode来控制 //遍历map for (Map.Entry<String, String[]>entry : map.entrySet()) { //获取当前遍历到的值的数组 String[] values = entry.getValue(); //遍历值的数组 for (int i = 0; i < values.length; i++) { //解决乱码 存回数组 values[i] = new String(values[i].getBytes("iso8859-1"),encode); } } hasNotEncode = false; } //返回解决完乱码的map return map; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(); } } @Override public String[] getParameterValues(String name) { return getParameterMap().get(name); } @Override public String getParameter(String name) { String[] values = getParameterValues(name); return values == null ? null : values[0]; } }
/** * 过滤方法 */ public void doFilter(final ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException { //解决乱码 //响应乱码 response.setContentType("text/html;charset="+encode); //请求参数 ServletRequest myRequest = new MyServletRequest((HttpServletRequest) request); //资源放行 chain.doFilter(myRequest, response); }
|
分别删除LoginServlet、RegistServlet、AjaxCheckUsernameServlet中的处理乱码部分代码。 |
4.Filter案例二:30天内自动登录
在处理用户登录时,判断用户是否勾选过30天内自动登录选项,如果用户名密码正确,且勾选过该选项,则发送包含用户名密码的cookie,让浏览器保存该cookie30天。
之后用过户再来访问时,如果用户未登录,且带了自动登录的cookie,且其中的用户名密码都正确,则自动登录该用户。
开发步骤:
login.jsp 提供30天内自动登录的选择框 //浏览器不识别中文,需要进行url编码,需要URLEcode(
remCookie=newCookie("remname",URLEncoder.encode(username,"utf-8"));
remCookie.setMaxAge(60*60*24*30);
remCookie.setPath(request.getContextPath()+"/");
response.addCookie(remCookie);
LoginServlet 在用户登录成功后,增加处理30天内自动登录的逻辑
AutoLoginFilter 首先要配置AutoLoginFilter,拦截所有请求,判断是否未登录,是否有自动登录cookie,自动登录cookie中的用户名密码是否正确,如果都满足,则自动登录,无论是否自动登录,都放行资源。
LogoutServlet 当用户手动注销时,删除自动登录cookie。
代码示例:
在LoginServlet中,进行登录后添加如下代码: //判断是否需要30天内自动登陆 if("true".equals(request.getParameter("autologin"))){ Cookie c = new Cookie("autologin",URLEncoder.encode(username,"utf-8")+"#"+password); c.setMaxAge(3600*24*30); c.setPath(request.getContextPath()+"/"); response.addCookie(c); }
|
在filter包下创建类AutoLoginFilter,实现Filter接口,并在web.xml中进行配置,在EncodingFilter后面添加如下代码: <filter> <filter-name>AutoLoginFilter</filter-name> <filter-class>cn.tedu.filter.AutoLoginFilter</filter-class> </filter> <filter-mapping> <filter-name>AutoLoginFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
|
在AutoLoginFilter中的doFilter方法中添加如下代码: //1.只有未登录的用户才做自动登录 HttpServletRequest req = (HttpServletRequest) request; if(req.getSession(false) == null || req.getSession().getAttribute("user") == null){ //2.只有携带了自动登录cookie的用户才做自动登录 Cookie[] cookies = req.getCookies(); Cookie autoCookie = null; if(cookies != null){ for (Cookie cookie : cookies) { if("autologin".equals(cookie.getName())){ autoCookie = cookie; } } } if(autoCookie != null){ //3.只有自动登录cookie中保存的用户名密码都正确才做自动登录 UserService service = BasicFactory.getFactory().getInstance(UserService.class); String username = URLDecoder.decode(autoCookie.getValue(),"utf-8").split("#")[0]; String password = autoCookie.getValue().split("#")[1]; User user = service.loginUser(username, password); System.out.println(username); if(user != null){ //三个条件都满足,实现自动登录 req.getSession().setAttribute("user", user); } } } //4.无论自动登录,都放行访问 chain.doFilter(request, response);
|
在LogoutServlet中杀死session后添加如下代码: //删除自动登录cookie Cookie cookie = new Cookie("autologin",""); cookie.setMaxAge(0); cookie.setPath(request.getContextPath()+"/"); response.addCookie(cookie); |
5.MD5加密算法
5.1.MD5概述
用户名密码保存在客户端是一种十分危险的行为。所以需要进行加密后保存。
其中MD5就是一种比较常用的加密算法。
与其说MD5算法是一种加密算法,不如说是一种数据指纹(数据摘要)算法。
其特点如下:
任意大小的二进制数经过MD5计算后都能得到一个独一无二的128位二进制数。
不同的数据算出的MD5绝对不相同。
相同的数据算出的MD5一定相同。
只能有明文算出密文,密文是永远也无法算成明文的。
MD5大量应用于计算机中。如数据库中保存的密码通常都是经过MD5加密后的数据。如用户下载文件时可以进行MD5校验防止数据被篡改。
在记住用户名案例中,我们可以使用MD5进行加密后再保存在客户端,从而保证数据安全。
在数据库中保存的密码也不宜直接存储为明文。也要经过MD5加密后存储。
5.2.JAVA实现MD5加密
使用md5的算法进行加密:
public static String md5(String
plainText) {
byte[] secretBytes =
null;
try {
secretBytes
= MessageDigest.getInstance("md5").digest(
plainText.getBytes());
} catch (NoSuchAlgorithmException
e) {
throw new
RuntimeException("没有md5这个算法!");
}
String md5code = new
BigInteger(1, secretBytes).toString(16);
for (int i = 0; i
< 32 - md5code.length(); i++) {
md5code =
"0" + md5code;
}
return md5code;
}
5.3.使用md5对easymall项目中的密码加密
代码如下:
在WebUtils类中加入md5加密代码: /** * 使用md5的算法进行加密 */ public static String md5(String plainText) { byte[] secretBytes = null; try { secretBytes = MessageDigest.getInstance("md5").digest( plainText.getBytes()); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("没有md5这个算法!"); } String md5code = new BigInteger(1, secretBytes).toString(16); for (int i = 0; i < 32 - md5code.length(); i++) { md5code = "0" + md5code; } return md5code; }
|
在RegistServlet中,实现注册之前,添加如下代码: //对password进行md5加密,然后再存入数据库 user.setPassword(WebUtils.md5(user.getPassword())); //4.实现注册(将用户信息保存进数据库) UserService service = BasicFactory.getFactory().getInstance(UserService.class); service.registUser(user);
|
在LoginServlet中,获取请求参数password修改如下: String password = WebUtils.md5(request.getParameter("password")); |