0011-0016 教程学习笔记
0011 Session与Cookie实现原理
第一节 会话管理
Cookie 保存在客户端
Session 保存在服务器内存中,客户端与服务器通讯用SessionId
应用场景在哪里? 登录,购物车,移动App接口会话管理
第二节 Cookie底层原理
1)服务器创建cookie对象,把会话数据存储到cookie对象中。
new Cookie("name","value");
2) 服务器发送cookie信息到浏览器
response.addCookie(cookie);
举例: set-cookie: name=eric (隐藏发送了一个set-cookie名称的响应头)
3)浏览器得到服务器发送的cookie,然后保存在浏览器端。
4)浏览器在下次访问服务器时,会带着cookie信息
举例: cookie: name=eric (隐藏带着一个叫cookie名称的请求头
5)服务器接收到浏览器带来的cookie信息
request.getCookies();
Cookie细节
1)void setPath(java.lang.String uri) :设置cookie的有效访问路径。有效路径指的是cookie的有效路径保存在哪里,那么浏览器在有效路径下访问服务器时就会带着cookie信息,否则不带cookie信息。
2)void setMaxAge(int expiry) : 设置cookie的有效时间。
正整数:表示cookie数据保存浏览器的缓存目录(硬盘中),数值表示保存的时间。
负整数:表示cookie数据保存浏览器的内存中。浏览器关闭cookie就丢失了!!
零:表示删除同名的cookie数据
3)Cookie数据类型只能保存非中文字符串类型的。可以保存多个cookie,但是浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
第三节 Session技术
Cookie的局限:
1)Cookie只能存字符串类型。不能保存对象
2)只能存非中文。
3)1个Cookie的容量不超过4KB。
如果要保存非字符串,超过4kb内容,只能使用session技术!!!
Session特点:
会话数据保存在服务器端。(内存中)
服务器创建一个Session后,session信息保存在服务器中,响应中把sessionId返回给客户端
客户端在下次请求中会将sessionId通过请求头的方式传给服务端
1)第一次访问创建session对象,给session对象分配一个唯一的ID,叫JSESSIONID
new HttpSession();
2)把JSESSIONID作为Cookie的值发送给浏览器保存
Cookie cookie = new Cookie("JSESSIONID", sessionID);
response.addCookie(cookie);
3)第二次访问的时候,浏览器带着JSESSIONID的cookie访问服务器
4)服务器得到JSESSIONID,在服务器的内存中搜索是否存放对应编号的session对象。
if(找到){
return map.get(sessionID);
}
Map<String,HttpSession>
<"s001", s1>
<"s001,"s2>
5)如果找到对应编号的session对象,直接返回该对象
6)如果找不到对应编号的session对象,创建新的session对象,继续走1的流程
结论:通过JSESSION的cookie值在服务器找session对象!!!!!
3.4 Sesson细节
1)java.lang.String getId() : 得到session编号
2)两个getSession方法:
getSession(true) / getSession() : 创建或得到session对象。没有匹配的session编号,自动创 建新的session对象。
getSession(false): 得到session对象。没有匹配的session编号,返回null
3)void setMaxInactiveInterval(int interval) : 设置session的有效时间
session对象销毁时间:默认情况30分服务器自动回收
第四节 自定义缓存
用Map集合去装数据
唯一不重复字符串作为key,简称sessionId
自定义缓存(简单的缓存框架)
public class CacheManager { private Map<String, Cache> cacheMap = new HashMap<>(); public void put(String key, Object oj) { put(key, oj, null); } public synchronized void put(String key, Object oj, Long timeOut) { if (oj == null) { return; } Cache cache = new Cache(); cache.setKey(key); if (timeOut != null) cache.setTimeOut(timeOut + System.currentTimeMillis()); cache.setValue(oj); cacheMap.put(key, cache); } public synchronized void deleteCache(String key) { cacheMap.remove(key); } public synchronized Object get(String key) { //此处不加synchronize会出现线程不安全问题 Cache cache = cacheMap.get(key); Object oj = null; if (cache != null) { oj = cache.getValue(); } return oj; } public synchronized void checkValidityData() { for (String key : cacheMap.keySet()) { Cache cache = cacheMap.get(key); Long timeOut = cache.getTimeOut(); if (timeOut == null) { return; } long currentTime = System.currentTimeMillis(); long endTime = timeOut; long result = (currentTime - endTime); if (result > 0) { System.out.println("清除:"+key); cacheMap.remove(key); } } } public static void main(String[] args) throws InterruptedException { CacheManager cacheManager = new CacheManager(); // cacheManager.put("lisi", 0); cacheManager.put("zhangsan", "jj", 5000l); ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); scheduledThreadPool.schedule(new Runnable() { public void run() { cacheManager.checkValidityData(); } }, 5000, TimeUnit.MILLISECONDS); Thread.sleep(5000); System.out.println(cacheManager.get("zhangsan")); } }
第六节 表单重复提交解决方案
出现原因:
1)网络延时 2)重复刷新 3)点击后退按钮到表单页
1. 前端解决办法
1)onSumit中设置提交标志 2)点击提交按钮后设置按钮为不可用
2. 后端解决办法
在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。然后将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
在下列情况下,服务器程序将拒绝处理用户提交的表单请求:
- 存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。
- 当前用户的Session中不存在Token(令牌)。
- 用户提交的表单数据中没有Token(令牌)
LocaFromServlet
@WebServlet("/LocaFromServlet") public class LocaFromServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 生成token String tokenValue=TokenUtils.getToken(); HttpSession session = req.getSession(); session.setAttribute("sessionToken", tokenValue); req.getRequestDispatcher("from.jsp").forward(req, resp); } }
from.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Form表单</title> <script type="text/javascript"> var flag = false;//false表示为提交 true 就是已经提交 function dosubmit(){ //获取表单提交按钮 var btnSubmit = document.getElementById("submit"); //将表单提交按钮设置为不可用,这样就可以避免用户再次点击提交按钮 btnSubmit.disabled= "disabled"; //返回true让表单可以正常提交 return true; } </script> </head> <body> <form action="DoFormServlet" method="post" onsubmit="return dosubmit()"> <input type="hidden" name="parameterToken" value="${sessionToken}"> 用户名:<input type="text" name="userName"> <input type="submit" value="提交" id="submit" > </form> </body> </html>
DoFromServlet
@WebServlet("/DoFormServlet") public class DoFormServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8");// 防止浏览器显示乱码 if(!isBumit(req)){ System.out.println("您提交提交了数据..或者token错误!"); resp.getWriter().write("您提交提交了数据..或者token错误!"); return ; } String userName = req.getParameter("userName"); try { Thread.sleep(300); } catch (Exception e) { // TODO: handle exception } System.out.println("数据库插入数据...userName:" + userName); // 插入数据库... resp.getWriter().write("保存成功.."); req.getSession().removeAttribute("sessionToken"); } public Boolean isBumit(HttpServletRequest request) { String parameterToken = request.getParameter("parameterToken"); String sessionToken = (String) request.getSession().getAttribute("sessionToken"); //判断是否提交 if (sessionToken == null) { return false; } // 判断是否是伪造token if(!(parameterToken.equals(sessionToken))){ return false; } return true; } }
1.访问/LocaFromServlet,服务器端生成token,一份存到session,一份传给from.jsp的隐藏域,然后转发到from.jsp
2。在from.jsp中输入内容,点击提交到DoFormServlet
3.在DoFormServlet中比对请求参数中隐藏域的token和session中的token是否一致,如果一致则说明是第一次提交,然后清除session,当再次提交时,session中的token为空,所以不是第一次提交,返回失败,这样就能解决重复提交的问题
0012 深入理解Http协议
1.1 什么是http协议?
http协议: 对浏览器客户端 和 服务器端 之间数据传输的格式规范
第四节 防盗链
原因:B网站直接引用A网站的图片链接
方案: 判断referreferer的网址名是否是本网站名
public class ImgFilter implements Filter { public void destroy() { // TODO Auto-generated method stub } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; String referer = request.getHeader("referer"); System.out.println("refer is" + "" + referer); if (referer == null || !referer.contains(request.getServerName())) { //如果是null则为直接从浏览器访问 request.getRequestDispatcher("/static/error.png").forward(request, response); } else { filterChain.doFilter(request, response); } } public void init(FilterConfig arg0) throws ServletException { // TODO Auto-generated method stub } }
web安全与防攻击
1.什么是XSS?
就是脚本注入(<script>这里写了一些恶意代码</script>),使用过滤器把提交参数的value值,转换成html代码执行
2.CSRF (模拟http请求) ,企业中会有白名单和黑名单,其实就是判断referer
第六节 http与https
http与https区别
1、https 协议需要到 ca 申请证书,一般免费证书较少,因而需要一定费用。
2、http 是超文本传输协议,信息是明文传输,https 则是具有安全性的 ssl 加密传输协议。
3、http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
4、http 的连接很简单,是无状态的;HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 http 协议安全。
客户端在使用 HTTPS 方式与 Web 服务器通信时有以下几个步骤,如图所示。
(1)客户使用 https 的 URL 访问 Web 服务器,要求与 Web 服务器建立 SSL 连接。
(2)Web 服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
(3)客户端的浏览器与 Web 服务器开始协商 SSL 连接的安全等级,也就是信息加密的等级。
(4)客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
(5)Web 服务器利用自己的私钥解密出会话密钥。
(6)Web 服务器利用会话密钥加密与客户端之间的通信
0014 Spring基础知识
框架离不开 反射+解析xml
Spring是一个容器,就是把每个bean与bean的关系全部交给第三方容器进行管理,整个bean对象的生命周期都交给spring管理
1.springioc执行流程:
解析xml配置,获取bean class 地址
使用java反射机制,进行实例化对象
返回对象
问原理不要答用了什么什么类,直接把思想打出来
2.单例在哪里使用? 我们用的spring默认是单例的,为了节约JVM资源。单例有什么缺点?线程不安全,所以尽量不要共享全局变量
3.Spring四种生命周期
Prototype 多例 :每次调用 getBean()都会新建一个bean
Singleton 单例 : 容器中只有一个实例
request http 请求request作用里面使用,每次新http请求都会产生一个新的bean
session http请求session作用域中使用,每次http请求都会产生新的bean
第四节 SpringIOC
1.IOC容器创建对象方式:
1)调用无参构造器
2)调用带参构造器
3)工厂创建对象
<!-- 无参构造函数 --> <bean id="user1" class="com.itmayiedu.entity.UserEntity" scope="prototype" /> <!-- 有参构造函数 --> <bean id="user2" class="com.itmayiedu.entity.UserEntity"> <constructor-arg name="name" type="java.lang.String" value="张三"></constructor-arg> <constructor-arg name="age" type="java.lang.Integer" value="18"></constructor-arg> </bean> <bean id="factory" class="com.itmayiedu.entity.ObjectFactory"></bean> <!-- 通过实例工厂方法创建 --> <bean id="user3" factory-bean="factory" factory-method="getInstance"></bean> <!-- 通过静态工厂方法创建 --> <bean id="user4" class="com.itmayiedu.entity.ObjectFactory" factory-method="getStaticInstance"></bean>
2. 依赖注入
1) 通过构造函数
2) 通过set方法给属性注入值
3) p名称空间
(常用)Set方法注入值
<!-- dao instance --> <bean id="userDao" class="cn.itmayiedu.UserDao"></bean> <!-- service instance --> <bean id="userService" class="cn.itmayiedu.UserService"> <property name="userDao" ref="userDao"></property> </bean> <!-- action instance --> <bean id="userAction" class="cn.itmayiedu.UserAction"> <property name="userService" ref="userService"></property> </bean>
p 名称空间注入属性值 (优化)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- ###############对象属性赋值############### --> <!-- 给对象属性注入值: # p 名称空间给对象的属性注入值 (spring3.0以上版本才支持) --> <bean id="userDao" class="cn.itmayiedu.UserDao"></bean> <bean id="userService" class="cn.itmayiedu.UserService" p:userDao-ref="userDao"></bean> <bean id="userAction" class="cn.itmayiedu.UserAction" p:userService-ref="userService"></bean> <!-- 传统的注入: <bean id="user" class="cn.itmayiedu.User" > <property name="name" value="xxx"></property> </bean> --> <!-- p名称空间优化后 --> <bean id="user" class="cn.itmayiedu.User" p:name="Jack0001"></bean> </beans>
注解版本使用
注解方式可以简化spring的IOC容器的配置!
使用注解步骤:
1)先引入context名称空间
xmlns:context="http://www.springframework.org/schema/context"
2)开启注解扫描
<context:component-scan base-package="cn.itcast.e_anno2"></context:component-scan>
3)使用注解
通过注解的方式,把对象加入ioc容器。
创建对象以及处理对象依赖关系,相关的注解:
@Component 指定把一个对象加入IOC容器
@Repository 作用同@Component; 在持久层使用
@Service 作用同@Component; 在业务逻辑层使用
@Controller 作用同@Component; 在控制层使用
@Resource 属性注入
总结:
1) 使用注解,可以简化配置,且可以把对象加入IOC容器,及处理依赖关系(DI)
2) 注解可以和XML配置一起使用。
@Resource与@autiwer
0015 深入理解Spring事务
1.Spring事务与传播行为
事务特性: 原子性、一致性、隔离性、持久性
事务用法: 1)编程事务(自己手动控制事务) 2)声明式事务(xml 注解)
第七节 事务传播行为
Spring中事务的定义:
事务传播行为:
Propagation.REQUIRED
指定当前的方法必须在事务的环境下执行;
如果当前运行的方法,已经存在事务, 就会加入当前的事务;
Propagation.REQUIRED_NEW
指定当前的方法必须在事务的环境下执行;
如果当前运行的方法,已经存在事务: 事务会挂起; 会始终开启一个新的事务,执行完后; 刚才挂起的事务才继续运行。
Class Log{ Propagation.REQUIRED insertLog(); } Propagation.REQUIRED Void saveDept(){ insertLog(); // 加入当前事务 .. 异常, 会回滚 saveDept(); } Class Log{ Propagation.REQUIRED_NEW insertLog(); } Propagation.REQUIRED Void saveDept(){ insertLog(); // 始终开启事务 .. 异常, 日志不会回滚 saveDept(); }
事务传播行为:
Propagation.REQUIRED
指定当前的方法必须在事务的环境下执行;
如果当前运行的方法,已经存在事务, 就会加入当前的事务;
Propagation.REQUIRED_NEW
指定当前的方法必须在事务的环境下执行;
如果当前运行的方法,已经存在事务: 事务会挂起; 会始终开启一个新的事务,执行完后; 刚才挂起的事务才继续运行。