Session与过滤器
一、Session场景
可用户存数据,存到服务器中;特点是同一个用户的多次访问属于同一个会话;不同用户访问则是不同会话
应用场景:访问权限,验证码
访问权限
昨天的登录案例,直接访问Servlet的showAll路径,即可展示数据;但真实场景中,考虑安全性,应该是登录后才能访问后台数据
模拟编写简单权限限制的例子:
思路:直接访问showAll时,判断没有登录凭证,则跳转到登录页面,登录成功;存了凭证后,才能显示到showAll路径。
存凭证方式:session(多次访问有效-推荐),request(一次访问有效),cookie(不安全)
登录凭证的设置:
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//处理乱码:
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
String username = req.getParameter("username");
String password = req.getParameter("password");
if("zs".equals(username)&&"123".equals(password)){
//存凭证
req.getSession().setAttribute("username",username);
//跳转:重定向-可看到目标url
resp.sendRedirect("showAll");
}else{
resp.getWriter().write("登录失败");
}
}
}
展示数据中判断凭证:
@WebServlet("/showAll")
public class ShowServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
String name = (String) req.getSession().getAttribute("username");
if(name!=null) { //拿到登录凭证了,可以展示数据
resp.getWriter().write("展示后台数据成功!!!");
}else{//跳转到登录页面:重定向-改变url,可读性好
resp.sendRedirect("login.html");
}
}
}
验证码
在登录中往往除了登录校验外,还需要验证码的校验;
验证码的操作方式:
- 生成验证码 2. 判断验证码 3.刷新验证码
生成验证码:需要到验证码的依赖包,且使用session存储验证码内容
@WebServlet("/valid")
public class ValidateServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//展示验证码 参数1,2:宽高 参数3:验证码个数 参数4:干扰线数量
ValidateCode code = new ValidateCode(110, 50, 4, 8);
System.out.println("验证码:"+code.getCode()); //获取验证码
//存储验证码
req.getSession().setAttribute("imgCode",code.getCode());
code.write(resp.getOutputStream()); //写出验证码,传入字节流
}
}
判断验证码:在登录的Servlet中判断验证码增强登录权限
//验证码校验:
String code = req.getParameter("code");
String imgCode = (String) req.getSession().getAttribute("imgCode");
if(!imgCode.equalsIgnoreCase(code)){
resp.getWriter().write("验证码失败!");
return;
}
刷新验证码:点击触发显示验证码的img标签,改变访问路径
<form action="login" method="get">
用户名:<input type="text" name="username"><br>
密码:<input type="text" name="password"><br>
验证码:<input type="text" name="code" />
<!-- 用于展示验证码的图片标签 -->
<img src="valid" onclick="change(this)" /><br>
<input type="submit" value="提交">
</form>
<script>
function change(obj) {
alert()
obj.src="valid?date="+new Date(); //清缓存
}
</script>
Session与Cookie
Session存值到服务器;Cookie存储到客户端
场景:
- 当需要临时存储数据(内存存储),且安全性较高时,使用Session; 例如:权限验证,验证码
- 永久性存储,需要存储有效期,安全性不是特别高的应用中,则使用cookie; 例如:浏览记录,购物车
二、context全局对象
概述
context是一个全局对象,只要启动了Tomcat,那么任何Servlet都可使用到该对象;且任意用户的访问,都属于同一个context对象。
生命周期:
启动:在tomcat启动时,产生context对象
结束:只有tomcat关闭后,context才关闭
context用法
@WebServlet("/context")
public class ContextServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//创建全局对象的方式:1.req对象获取 2.通过session对象获取 3.当前Servlet对象获取
ServletContext context1 = req.getServletContext();
ServletContext context2 = req.getSession().getServletContext();
ServletContext context3 = this.getServletContext();
System.out.println(context1==context2); //true
System.out.println(context1==context3); //true
//调用常用方法:
String realPath = context1.getRealPath("/"); //获取真实路径,在上传图片时用到
System.out.println("从盘符出发的路径:"+realPath);
System.out.println("context获取上下文:"+context1.getContextPath()); //获取上下文路径
System.out.println("req获取上下文:"+req.getContextPath());
//和req,session类似,用于存值(没有req和session常用)
//req:一次请求有效
//session:一次会话有效(浏览器不改变,就一直有效)
//context:除了关闭或重启tomcat,其余都有效
context1.setAttribute("key","context存值");
}
}
@WebServlet("/get")
public class GetContext extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = req.getServletContext();
System.out.println("获取值:"+context.getAttribute("key"));
System.out.println("清除key:");
context.removeAttribute("key"); //移除只是移除存储的值,context还在,session和req也类似
}
}
存储方式说明:
能用作用域小的存值,尽量用小的; 顺序:request,session, context
应用案例
案例:统计某个网页的访问量,每访问一次,则浏览量增加一次;并显示统计量到页面上
@WebServlet("/count")
public class CountServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
ServletContext context = req.getServletContext();
//获取全局对象中存的值
Integer count = (Integer) context.getAttribute("cou");
if(count==null){ //如果为null,说明第一次访问
count = 1;
}else{
count++; //值+1
}
context.setAttribute("cou",count);
resp.getWriter().write("访问了"+count+"次");
}
}
三、过滤器
概述:浏览器到目标Servlet之间的一道拦截技术
过滤器引入
上面的访问权限,每次访问目标Servlet时,都需要进行凭证的判断,会非常繁琐;而且后续再次新增Servlet可能会忘记写凭证判断,则有安全性问题,这些都是需要进行处理的。
如果有一种技术,在访问所有目标资源时,都能拦截处理,即可解决冗余和安全性的问题。
应用场景:权限处理,编码处理
过滤器流程:浏览器访问目标资源之前,先进入过滤器; 过滤器中需要放行(doFilter()),否则无法到达目标资源 ; 到达目标资源后,响应会客户端时,依然会先进入过滤器
应用
//@WebFilter("/dest") //精确匹配-不常用 进入过滤器的注解
//@WebFilter("/*") //通配符匹配-常用 /xx/yy/* - 常用(多级路径)
public class DestFilter implements Filter {
//Filter的生命周期与Servlet类似的:1.构造 2.初始化 3.doFilter 4.destroy
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("进入过滤器...开始");
chain.doFilter(request,response); //放行
System.out.println("进入过滤器...结束");
}
//...
}
配置方式
配置方式有两种:注解配置,xml配置
- 注解配置: @WebFilter(映射路径)
- xml配置: 与servlet的配置类似,如下所示
<filter>
<filter-name>f</filter-name> <!-- 根据映射路径找到真实Filter路径 -->
<filter-class>com.qf.d_filter.DestFilter</filter-class>
</filter>
<filter-mapping><!-- 配置映射路径 -->
<filter-name>f</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
过滤器路径:
过滤器的过滤路径通常有三种形式:
精确过滤匹配 ,比如/index.jsp /myservlet1
后缀过滤匹配,比如 .jsp、.html、*.jpg
通配符过滤匹配/,表示拦截所有。注意过滤器不能使用/匹配。 /aaa/bbb/ 允许
过滤器链
访问目标资源时,经过多道拦截处理的过程,就是过滤器链;
在一个Web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。
优先级:
- 如果为注解的话,是按照类全名称的字符串顺序决定作用顺序(编码处理时必须先执行)
- 如果web.xml,按照 filter-mapping注册顺序,从上往下
- web.xml配置高于注解方式
- 如果注解和web.xml同时配置,会创建多个过滤器对象,造成过滤多次。
注意:多次过滤的执行顺序和响应回来的顺序的相反
四、Filter应用场景
编码处理
@WebFilter("/*")
public class EncodingFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
String uri = req.getRequestURI();
if(!uri.endsWith(".html")){
System.out.println("进入编码处理...");
servletRequest.setCharacterEncoding("utf-8");
servletResponse.setContentType("text/html;charset=utf-8");
}
filterChain.doFilter(servletRequest,servletResponse); //放行
}
}
权限访问
@WebFilter("/*")
public class SafeFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//在过滤器中,判断是否得到登录凭证 --session
HttpServletRequest req = (HttpServletRequest) servletRequest; //父转子
HttpServletResponse resp = (HttpServletResponse) servletResponse;
String username = (String) req.getSession().getAttribute("username");
//注意:此处要避免多次访问到login.html;因为重定向每次都从客户端发的请求,每发一次请求都需要经过过滤器
String uri = req.getRequestURI(); //获取请求路径
System.out.println("uri--->"+uri);
//判断如果有凭证,或者是登录的前端或后端判断页面,以及验证码都放行
if(username!=null||uri.contains("/login")||uri.contains("/valid")){
filterChain.doFilter(req,resp); //放行
}else{
resp.sendRedirect("login.html"); //重定向到登录
}
}
}
注意:后续最好将Servlet路径设置为多级的,这样login.html就无需放行,因为本身不在过滤器的拦截中
五、总结与作业
总结
1.Session的应用场景(重点)
访问权限、验证码、Session与cookie的应用区别
2.Context全局对象
概述-全局唯一对象; 创建对象、常用方法、应用案例-统计访问流量
3.过滤器
概述、引入-繁琐和安全隐患、基本应用、
配置方式-注解、web.xml、路径;过滤器链-进行多次过滤处理,优先级
4.过滤器应用场景(重点)
编码处理-在过滤器中统一完成
权限访问-在过滤器中判断session,需要考虑放行路径(考虑后台多级路径过滤)
作业
1. 项目判断验证码时,我们使用了session存取值,如果改为reqeust, context存值,分别会发生什么情况,为什么?
2. 案例使用context统计浏览次数, 如果使用request, session来存储, 会出现什么情况,为什么?
3. session是什么? 请简述, 并将session的实现原理说明
4. 使用过滤器完成过滤敏感词汇:前端文本框写入内容,点击提交;在后台接收数据后,屏蔽敏感词,并将屏蔽后的内容响应回客户端。
例如:文本框输入-“暴力的黄色小说”,将黄色替换成xx,并响应回客户端
晨考
1. HttpRequest,HttpSession,ServletContext 的作用域是什么
2. Session与Cookie的区别
3. session设置和获取属性的方法调用
4. 设置和获取Cookie的方法调用