Filter过滤器
一、Filter 过滤器概述
- Filter是JavaEE三大核心技术(Servlet 、 Filter 、 Listener)之一
- FIlter作用是拦截对资源的访问 , 拦截下来后可以控制是否允许通过 , 或者在允许通过前后做一些额外的操作 。
- 所谓的拦截其实就是对代表请求的request、对象和代表 响应的response对象拦截下来 , 进行控制
- 一个过滤器可能拦截多个资源 , 一个资源也可能被多个过滤器拦截
- 这种多个拦截器拦截一个资源的模式成为责任链模式 。
- 常用场景:
- 就与URL的访问权限控制
- 全站乱码解决过滤器
- 过滤敏感词汇
- 压缩响应
二、 过滤器的开发
- 想要开发一个过滤器 , 需要两个步骤
- 写一个类实现Filter接口
- 在web.xml中配置过滤器
- Filter接口:
- init(FilterConfig)
- 初始化的方法 , 当Filter被初始化时 , 调用此方法 , 执行初始化操作
- destory()
- 销毁方法, 在Filter被销毁之前调用 , 执行善后操作
- doFilter(ServletRequest request , ServletResponse response , FilterChain chain)
- 核心方法 ,在存活期间 , 过滤器拦截到对资源的访问 会造成此方法的执行 , 需要在这个方法中设计过滤器的核心逻辑代码
- init(FilterConfig)
配置过滤器
<filter> -- 配置一个过滤器 <filter-name>FirstFilter</filter-name> -- 过滤器的名字 <filter-class>com.tarena.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>
三、生命周期
- 在web应用启动时 , 会创建处web应用中配置的过滤器对象 , 创建出过滤器对象会立即调用init方法进行初始化操作 , 之后一直存活 , 直到web应用被销毁时 , Filter跟着被销毁.在销毁之前会自动调用destory方法执行善后操作 。 在存活期间 , 每当拦截到资源访问 , 就执行doFilter方法 , 来执行过滤器的 逻辑 , 如果不做操作 , 则 默认拦截 , 可以通过FilterChain类的对象的 doFilter方法实现对资源访问的放行 。 并且可以在doFilter前后做一些操作 。
四、 细节
- 如果一个资源被多个过滤器拦截 , 多个 拦截器的拦截顺序取决于在web.xml文件中配置过滤器时的先后顺序 。
- 多个 过滤器的执行 , 类似于方法 一层一层调用的过程 ,, 一层一层往里钻, 然后在一层层一层往外出 。
五、 和Filter开发相关的对象
FilterConfig:
- init方法的参数
- 代表FIlter在web.xml文件中的配置对象
- 可以用来获取Filter在Web.xml文件中的初始化配置参数
可以用来获取ServletContext对象
public void init(FilterConfig filterConfig) throws ServletException { System.out.println("init...."); //1.filterConfig功能1:获取filter的初始化参数 Enumeration<String> names = filterConfig.getInitParameterNames(); while(names.hasMoreElements()){ String name = names.nextElement(); String value = filterConfig.getInitParameter(name); System.out.println(name+"~"+value); } //2.获取ServletContext对象 ServletContext sc = filterConfig.getServletContext(); }
- FilterChain:
- doFilter方法参数
- 代表过滤器链
- 提供了doFilter方法 ,放行当前过滤器 , 执行后续过滤器 , 如果后续没有过滤器则调用到相应的资源 。
六、Filter案例
全站乱码解决过滤器
- 在web开发过程中 , 存在请求参数乱码和响应输出乱码 。
- 之前的开发中 , 在所有的Servlet和jsp页面中 , 需要手动解决这两种乱码
- 可以通过开发过滤器拦截所有的资源访问 , 在过滤器中解决全站乱码问题 。
具体解决请求响应乱码问题:
1. 在web.xml文件 中配置全局的编码类型 <!-- 全局配置 --> <context-param> <param-name>encode</param-name> <param-value>utf-8</param-value> </context-param> 2. 在过滤器初始化时获取全局配置的编码 , 并保存到过滤器中 private String encode = null; public void init(FilterConfig config) throws ServletException { encode = (String) config.getServletContext().getAttribute("encode"); } 3. 解决响应乱码: 在doFilter方法中 response.setCharacterEncoding(encode); response.setContentType("text/html;charset="+encode); 4. 解决请求参数乱码 //方案一: // request.setCharacterEncoding(encode); //只能解决Post请求参数的乱码 //可以解决Post和Get请求类型的参数乱码 //但是在转码的时候需要指定具体的参数名称 ,转码之后要重新放入request中供servlet拿取更是不能实现 , 所以 不可行 // String param = new String(request.getParameter("xxxx").getBytes("iso-8859-1") , encode); //方案二: //request中的请求参数本身无法改变 //那么 换一个思路 想办法改造和获取请求参数相关的方法 在方法内加上解决乱码的代码 //这样通过这些方法获取请求参数时 解决好乱码再返回 用起来就感觉 乱码被解决了一样 //改造原有request方法方案一: //继承 //继承只能先改造在创建实例 , 但是现在已经有了request对象, 就算通过继承改造了ServletRequest类也不会影响到已有的对象 , 排除 //改造原有request方法方案二: //装饰设计模式 //1. 新建一个类 , 实现与被改造对象相同的接口 //2. 通过狗仔方法传入被改造的对象并保存在本类中 //3. 然后实现接口中所有的方法 , 如果需要改造则在对应的方法里写出逻辑 , 如果不需要改造的方法 ,则直接通过传入的没被改造的参数对象调用原有的方法即可 //这种方案 的缺点 ,如果被改造的方法 中方法过多时 ,这个操作会十分繁琐 。 //改造原有的request对象方案三: //在装饰设计模式的基础上实现 //通过源码 发现Servlet包下已经提供了一个ServletRequestWrapper类 , 它实现了与ServletRequest相同的接口 //也就是说他就是java中已经提供的供开发者修改request对象中方法的入口 //新建一个类继承ServletRequestWrapper类之后 ,通过构造方法 把原始对象传进去 , 然后只重写需要改造的方法即可 代码// public class EncodeFilter implements Filter{ /** * 当前web应用编码集 */ private String encode = null; /** * 初始化方法 */ public void init(FilterConfig filterConfig) throws ServletException { //获取ServletContext对象 ServletContext sc = filterConfig.getServletContext(); //读取初始化参数 中的 编码集 配置 this.encode = sc.getInitParameter("encode"); } /** * 过滤方法 */ public void doFilter(final ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException { //--全站响应乱码 response.setCharacterEncoding(encode); response.setContentType("text/html;charset="+encode); //--全站请求乱码 - 通过装饰器 装饰request 修改其中的和获取请求参数相关的方法 增加了乱码解决的代码 ServletRequest myReq = new MyServletRequest((HttpServletRequest) request); //放行资源 chain.doFilter(myReq, response); } /** * 销毁方法 */ public void destroy() { } /** * 内部类 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 { //1.获取真正request的请求参数组成的map Map<String,String[]> map = request.getParameterMap(); if(hasNotEncode){//由于真正的request对此map会缓存 所以解决乱码的操作 只需要做一次 此处通过hasNotEncode来控制 //2.遍历map for(Map.Entry<String, String[]>entry : map.entrySet()){ //3.获取当前遍历到的值的数组 String [] values = entry.getValue(); //4.遍历值的数组 for(int i = 0;i<values.length;i++){ //5.解决乱码 存回数组 values[i] = new String(values[i].getBytes("iso8859-1"),encode); } } hasNotEncode = false; } //6.返回解决完乱码的map return map; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } }
用户30天内自动登录
- 在处理用户登录时 , 判断用户是否勾选30天内自动登录 , 如果用户名密码正确且勾选过该选项 , 则发送cookie , 将用户名密码保存30天。 为了安全起见 , 保存之前先对密码进行MD5加密
- 之后用户在来访问时经过自动登录过滤器被拦截 , 如果用户未登录 , 且带了自动登录的cookie , 并且其中的用户名密码都正确 , 则给给用户给自动登录 。但是无论自动登录与否都要对url放行 。
- 在LoginServlet中添加用户自动登录逻辑: 如果用户勾选 了自动登录 , 则将用户信息添加进cookie中保存在本地
- 在AutoLoginFilter中 拦截所有请求 , 先判断是否登录 , 在判断是否有自动登录cookie ,最后判断用户密码是否正确
自动登录过滤器源码
public class AutoLoginFilter implements Filter{ public void destroy() { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; // 1. 判断该用户是否 已经登录 if(req.getSession(false) == null ||req.getSession(false).getAttribute("user") == null){ System.out.println("用户未登录"); // 2. 判断访问时是否带有自动登录cookie Cookie[] cookies = req.getCookies(); for(Cookie c :cookies){ if("autologin".equals(c.getName())){ System.out.println("带有自动登录cookie"); String v = c.getValue(); String[] vs = URLDecoder.decode(v , "utf-8").split("#"); UserService us = BaseFactory.getBase().getInstance(UserService.class); // 3. 验证用户名密码是否正确 User user = us.login(vs[0], vs[1]); if(user != null){ System.out.println("开始登录"); //三个条件都满足 , 添加登录标记 req.getSession().setAttribute("user", user); System.out.println("自动登录成功"); } break; } } } // 4. 无论是否自动 登录成功 , 都放行访问。 放行访问 chain.doFilter(request , response); } public void init(FilterConfig arg0) throws ServletException { // TODO Auto-generated method stub } }
七、MD5加密算法
- 又称数据摘要算法 , 数据指纹算法
- 任意长度的二进制文件计算出128位二进制的再要信息 , 通常转换为32位16进制显示 。
- 明文相同算出的密文一定相同
- 明文不同算出的密文一定不同 (概率极低 , 所以一般认为是唯一的)
- 只能由明文算成密文 ,, 不能有密文算成明文
- 应用:
- 加密存储数据
- 文件完整性校验
- 数字签名