Filter
程序员文章站
2022-07-15 10:46:50
...
Filter介绍
- 过滤器,它是Servlet技术中最实用的技术,web开发人员通过filter技术,对web服务器管理的所有web资源例如:JSP,Servlet,静态图片文件或静态html文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制,过滤敏感词汇,压缩响应信息等一些高级功能。
javaee的API,filter接口
javax.servlet.filter
void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException
- difilter是真正用于过滤的方法。filter也需要在web.xml文件中配置
- 每个Filter都有对FilterConfig对象的访问权,可从该对象获得其初始化参数以及对它可以使用的ServletContext的引用,以便为过滤任务加载所需要的资源。
- 每次由于对链末尾的某个资源的客户端请求而通过链传递请求/响应对时,容器都会调用Filter的doFilter 方法,传入此方法的FilterChain允许Filter将请求和响应传递到链中的下一个实体。
void init(FilterConfig filterConfig) throws ServletException
void destroy()
Filter入门
Filter创建步骤
- 创建一个类,实现javax.servlet.Filter接口
- 重写接口中的方法
- 在web.xml文件中配置
public class DemoFilter implements Filter{
//重写接口内的方法
public void destriy(){
}
public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws IOException,ServletException{
System.out.println(“执行过滤操作”);
//放行,继续执行index.jsp
chain.dofilter(request,response);
}
public void init(FilterConfig filterConfig) throws ServletException{
}
}
//web.xml配置
<filter>
<filter-name>demoFilter</filter-name>
<filter-class>cn.itcast.web.filter.DemoFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>deFilter</filter-name>
<url-pattern>/index.jsp</url-pattern>
<!--访问index.jsp页面会被DemoFilter过滤-->
</filter-mapping>
访问index.jsp,结果展示:
设置参数chain:
执行结果:
实现过滤操作步骤:
<url-pattern>
它是用于设置过滤的路径- 在doFilter方法的第三个参数FilterChain,它是用于控制是否继续往下执行(访问服务器端资源)的。
Filter的生命周期
- 在javax.servlet.Filter接口中三个方法
- public void init(FilterConfig filterConfig) throws ServletException
- 初始化方法,只执行一次
- public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)
- 真正进行拦截操作的方法
- 通过chain.doFilter(request,response)向下执行
- public void destroy()
- 销毁操作
FilterConfig(参考ServletConfig)
- 在Filter的init方法中有一个参数FilterConfig
- FilterConfig作用也是获取Filter的相关配置信息
- 初始化参数的获取
- Filter的名称获取
- ServletContext对象获取
接口FilterConfig
- public String getFilterName()
- 如部署描述符中定义的那样,返回此过滤器的过滤器名称
- public ServletContext getServletContext()
- 返回对调用者在其中执行操作的ServletContext的作用
- public String getInitParameter(String name)
- 返回包含指定初始化参数的值的String,如果参数不存在,则返回null
- public
java.util.Enumeration<E> getInitParameterNames()
- 以String对象的Enumeration的形式返回过滤器初始化参数的名称,如果过滤器没有初始化参数,则返回一个空的Enumeration。
public void init(FilterConfig filterConfig) throws ServletException{
//获取Filter名称
String filterName = filterConfig.getFilterName();
System.out.println(filterName);
//获取初始化参数
String encoding = filterConfig.getInitParameter("encoding");//utf-8
System.out.println(encoding);//utf-8
//
Enumeration<String> names = filterConfig.getInitParameterNames();
while(names.hasMoreElements()){
System.out.println(names.nextElements());
}//encoding,username
//获取ServletContext对象
filterConfig.getServletContext();
}
//web.xml配置文件
<filter>
<filter-name>demo2Filter</filter-name>
<filter-class>cn.itcast.web.filter.Demo2Filter</filter-class>
<!--初始化参数-->
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>username</param-name>
<param-value>tom</param-value>
</init-param>
</filter>
Filter详细配置信息
关于配置Filter
<filter>
<filter-name></filter-name>
<filter-class></filter-class>
<init-param>
<param-name></param-name>
<param-value></param-value>
</init-param>
<filter-mapping>
<filter-name></filter-name>
<url-pattern></url-pattern>
</filter-mapping>
</filter>
Filter链
- 在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链
- web服务器根据Filter在web.xml文件中的注册顺序
<mapping>
,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法 。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第二个filter,如果没有则调用目标资源。- 注意:
- FilterChain.doFilter(request,response)它代表的是向下执行,如果下一个还是filter,那么访问这个filter,如果当前filter已经是整个链的末尾,那么访问web资源。
- Filter的顺序由
<Filter-mapping>
的配置顺序决定。
//firstFilter
public class FirstFilter implements Filter{
public void destroy{}
public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws IOException,ServletException{
System.out.pritnln("firstFilter...");
chain.doFilter(request,response);//向下放行
System.out.println("firstFilter end ...");
}
public void init(FilterConfig filterConfig) throws ServletException{
}
}
//secondfilter
public class SecondFilter implements Filter{
public void destroy{}
public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws IOException,ServletException{
System.out.pritnln("secondFilter...");
chain.doFilter(request,response);//向下放行
System.out.println("secondFilter end ...");
}
public void init(FilterConfig filterConfig) throws ServletException{
}
}
//DemoServlet
public class DemoServlet extends HttpServlet{
public void doGet(HttpServletRequest reequest,HttpServletResponse response) throws ServletException,IOException{
Ssytem.out.pritnln("demo servlet");
}
public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
doGet(request,response);
}
}
<!--firstFilter-->
<filter-mapping>
<filter-name>firstFilter</filter-name>
<url-pattern>/index.jsp</url-pattern>
</filter-mapping>
<!--secondFilter-->
<filter-mapping>
<filter-name>secondFilter</filter-name>
<url-pattern>/index.jsp</url-pattern>
</filter-mapping>
<url-pattern>
- 对于Fliter来说,它是用于确定拦截资源路径
<url-pattern>
有几种方法:
- 完全匹配 必须以“/”开始
- 可以使用* 通配符
- 目录匹配
- /a/* /* 要求必须“/”开始
- 扩展名匹配
- .do .action 要求,不能以“/”开始,以*.xxx结束
简单的编码过滤(只针对于post请求)
- 编写一个jsp页面
- 编写一个servlet,在servlet中获取请求参数
- 问题:乱码问题
- 对于post解决方法:request.setCharacterEncoding(“utf-8”);
- 创建一个Filter,在Filter中完成编码处理
禁用jsp页面缓存
- 禁用页面缓存的目的:为了得到实时信息
- 禁用jsp页面缓存的方式
- 在jsp页面上设置
<meta http-equiv="pargma" content = "no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
- 可以通过Filter来控制
- 在Filter中设置
- response.setHeader(“pragma”,”no-cache”);
- response.setHeader(“cache-control”,”no-cache”);
- response.setDateHeader(“expires”,-1);
- filter的url-pattern
- *.jsp(jsp格式的)
<filter-mapping>
<filter-name>cacheFilter</filter-name>
<url-pattern>*.jdp</url-pattern>
</filter-mapping>
public void doFilter(ServletRequest req,ServletResponse resp,FilterChain chain) throws IOException,ServletException{
//强制转换
HttpServletResponse request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)resp;
//操作
response.setDateHeader("expires",0);
//放行
chain.doFilter(request,response);
}
设置图片缓存时间
- 让图片缓存,缓存的目的是为了提高性能和效率
- filter的url-pattern
- *.bmp(图片的格式)
public void doFilter(ServletRequest req,ServletResponse resp,FilterChain chain) throws IOException,ServletException{
//强制转换
HttpServletResponse request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)resp;
//操作
response.setDateHeader("expires",System.currentTimeMillis()+60*60*24*10*1000);//缓存10天
//放行
chain.doFilter(request,response);
}
自动登录案例
- 在访问一个站点,登陆时勾选自动登陆(三个月内不用登录),操作系统后,关闭浏览器,过几天再次访问该站点时,直接进行登陆后状态。
- 自动登陆原理:
- 登录成功后,判断是否勾选了自动登录
- 如果勾选了自动登录,将用户名与密码存储到cookie中
- 做一个Filter,它拦截所有请求,当访问资源时,我们从cookie中获取用户名与密码,进行登录操作。
登录操作
- 创建User数据库,user表,添加数据。
- DataSourceUtils.java c3p0-config.xml User.java(javaBean)
- login.jsp success.jsp
//success.jsp
<body>
当前用户:${user.username}
</body>
//login.jsp
<body>
<!--展示异常信息-->
${requestScope["login.message"]}<br>
<form action="${pageContext.request.contextPath}/login" method="post">
username:<input type="text" name="username"><br>
password:<input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
//LoginServlet.java url:login
public class LoginServlet extends HttpServlet{
public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
//得到请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");
//登录
UserService service = new UserService();
try{
User user = service.login(username,password);
if(user != null){
//登录成功
request.getSession().setAttribute("user",user);
response.senRedirect(request.getContextPath()+"/demo4/success.jsp")
return;
}else{
request.setAttribute("login.message","用户名或密码错误");
request.getRequestDispatche("/demo4/login.jsp").forward(request,response);
return;
}
}catch(SQLException e){
e.printStackTrace();
request.setAttribute("login.message","登录失败");
request.getRequestDispatche("/demo4/login.jsp").forward(request,response);
return;
}
}
}
public class UserService throws SQLException{
public User login(String username,String password){
return new UserDao().findUserByUsernameAndPassword(username,password);
}
}
public class UserDao{
//根据用户名与密码查找用户
public User findUserByUsernameAndPassword(String username,String password) throws SQLException{
String sql = "select * from user where username=? and password=?";
QueryRunner runner = new QueryRunner(DataSourceUtils.getDataSource());
return runner.query(sql,new BeanHandler<User>(User.class),username,password);
}
}
完成自动登录
- 在页面上添加自动登录按钮
- 在LoginServlet中
*如果登录成功后,判断是否勾选了自动登录,如果勾选了,将用户名与密码存储到cookie中。- 创建一个AutoLoginFilter
//login.jsp
<body>
<!--展示异常信息-->
${requestScope["login.message"]}<br>
<form action="${pageContext.request.contextPath}/login" method="post">
username:<input type="text" name="username"><br>
password:<input type="password" name="password"><br>
<input type="checkbox" name="autologin" value="ok">自动登录<br>
<input type="submit" value="登录">
</form>
</body>
//LoginServlet.java中
public class LoginServlet extends HttpServlet{
public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
//得到请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");
//登录
UserService service = new UserService();
try{
User user = service.login(username,password);
if(user != null){
//登录成功
//判断是否勾选了自动登录按钮
String autologin = request.getParameter("autologin");
if("ok".equals(autologin)){
//勾选了
//创建一个cookie
Cookie cookie = new Cookie(“autologin”,usernaem+"::"+password);
//cookie持久化
cookie.setMaxAge(60*60*24*10);//存储10天
cookie.setPath("/");
response.addCookie(cookie);
}
request.getSession().setAttribute("user",user);
response.senRedirect(request.getContextPath()+"/demo4/success.jsp")
return;
}else{
request.setAttribute("login.message","用户名或密码错误");
request.getRequestDispatche("/demo4/login.jsp").forward(request,response);
return;
}
}catch(SQLException e){
e.printStackTrace();
request.setAttribute("login.message","登录失败");
request.getRequestDispatche("/demo4/login.jsp").forward(request,response);
return;
}
}
}
public calss AutoLoginFilter implements Filter{
public void destroy){
}
public void doFilter(ServletRequest req,ServletResponse resp,FilterChain chain) throws IOException,ServletException{
//强制转换
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse)resp;
//操作
//得到cookie中的username.password
Cookie cookie = CookieUtils.findCookidByName(request.getCookies(),"autologin");
if(cookie != null){
//找到了,进行自动登录
String username = cookie.getValue().split("::")[0];
String password = cookie.getValue().split("::")[1];
UserService service = new UserService();
User user = service.login(username,password);
if(user != null){
//查找到用户,进行自动登录
request.getSession().setAttribute("user",user);
}
}
//放行
chain.doFilter(request,response);
}
public void init(FilterConfig filterConfig) throws ServletException{
}
}
public calss CookieUtils{
public static Cookie findCookieByName(Cookie[] cs,String name){
if(cs==null || cs.length==0){
return null;
}
for(Cookie c : cs){
fi(c.getName().equals(name)){
return c;
}
}
return null;
}
}
问题分析:
- 如果用户已经登录了,不需要自动登录。
- 如果用户是进行例如,注册,登录操作,不需要自动登录。
- 得到请求资源路径,判断是否是登录,注册操作
- 例如:Http://localhost/day21-2/demo4/login.jsp
- String uri = request.getRequestURI();—>/day21-2/demo4/login.jsp
- String contextPath = requext.getContextPath();—>/day21-2
- String path = uri.subString(contextPath.length());—>/demo4/login.jsp
public void doFilter(ServletRequest req,ServletResponse resp,FilterChain chain) throws IOException,ServletException{
//强制转换
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse)resp;
//操作
//判断用户没有登录,才进行自动登录
User u= (User)request.getSession().getAttribute("user");
if(u==null){
//得到cookie中的username.password
Cookie cookie = CookieUtils.findCookidByName(request.getCookies(),"autologin");
if(cookie != null){
//找到了,进行自动登录
String username = cookie.getValue().split("::")[0];
String password = cookie.getValue().split("::")[1];
UserService service = new UserService();
User user = service.login(username,password);
if(user != null){
//查找到用户,进行自动登录
request.getSession().setAttribute("user",user);
}
}
}
//放行
chain.doFilter(request,response);
}
如果用户是中文,怎样处理?
- cookie中不能存储中文的。
- 存储时,存utf-8码,使用时解码
- 存:Cookie cookie = new Cookie(“autologin”,URLEncoder.encode(username,”utf-8”)+”::”+password); (LoginServlet.java)
- 取:String username = URLDecoder.decode(cookie.getValue().split(“::”)[0],”utf-8”); (AutoLoginFilter)
关于密码的安全性问题
- 可以对密码进行加密 md5
- mysql中加密方式:md5(字段)
- update user set password = md5(password);
- java中加密方式:
public class Md5Utils{
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,sevretBytes).toString(16);
for(int i=0;i<32-md5code.length();i++){
md5code = "0"+md5code;
}
return md5code;
}
}
//UserDao.java
return runner.query(sql,new BeanHandler<User>(User.class),username,Md5Utils.md5(password));
url级别权限控制
- 权限控制的原理
- 可以做一个权限的Filter,在Filter中判断用户是否登录了,如果登录了,可以访问资源,如果没有登录,不能访问资源。
- 问题1:怎样判断哪些资源需要权限,哪些资源不需要权限?
String uri = request.getrequestURI();
String contextPath = request.getContextPath();
String path = uri.substring(contextPath.length());
if(path.equals("/book_add") || path.equals("/book_update")||path.equals("/book_delete")||path.equals("/book_search")){
}- 问题2:我们的用户是有role的,如果admin,可以访问所有资源,而User只能访问book_search怎样处理。
if("admin".equals(user.getRole())){
//这是admin角色
if(!(path.equals("/book_add")||path.eauals("/book_update")||path.equals("/book_delete"))){
throw new PrivilegeException();
}else{
//这是User角色
if(!(path.equals("/book_search"))){
throws new PrivilegeException();
}
}
}
对权限控制代码优化
主要对url路径的判断进行优化
- 在src下创建两个配置文件,user.properties admin.properties
- 在这两个文件中分别保存不同的角色具有的权限路径
- 例如:admin.properties中url=/book_add,/book_delete,/book_update,/book_serch
- 在PrivilegeFilter中完成权限控制
- 在init方法中将配置文件中的信息读取出来分别保存到users,admins两个
List<String>
集合中。
- ResourceBundle.getBundle(String baseName);
- admin.properties文件它的基名就是admin
- user.properties文件它的基名就是user.
- 得到ResourceBundle对象,可以通过它的getString(String name);
- bundle.getString(“url”);这就得到了配置文件中名称叫url的值。
- 在判断时就比较方便
- 判断资源路径是否需要权限控制
- if(admins.contains(path) || users.contains(path))
- 判断每一个角色是否可以访问资源路径
- if(!(admins.contains(path))){
throw new PrivilegeException();
}