目录
1 Socket
2 软件结构
3 Servlet
4 HTTP
5 单点登录SSO
6 常见问题总结参考资料
· 《深入分析 Java Web》
1 Socket
1.1 概念
套接字(socket),是描述计算机之间完成相互网络通信的抽象功能,没有对应的实体。通讯的两端都有Sokcet,数据在两个Sokcet间通过IO传输。
格式为:IP:端口号
1.2 分类
· TCP和UDP
· 两种方式传输数据都是通过序列化java对象后,通过二进制协议传输,故Socket通信和编程语言没有关系。
2 软件结构
2.1 C/S体系
Client-Server 客户端-服务器端。属于桌面应用程序。
· 弊端:
·需要安装软件
·维护难,占空间
·服务端升级,客户端也需要升级
· 优点:
·体验效果好
·占宽带小
·减轻服务器端压力
2.2 B/S体系
Browser-Server浏览器端-服务器端,属于网站应用程序。
· 弊端:
·需要使用浏览器访问
·兼容性差、安全性差
·服务器端压力大
· 优点:
·不需要特定客户端
·服务端升级,浏览器不需要升级。
3 Servlet
3.1 资源的分类
· 静态资源:当用户多次访问这个资源,资源的源代码永远不会改变的资源。
· 动态资源:当用户多次访问这个资源,资源的源代码可能会发送改变。
3.2 实质
Servlet本质是Server Applet 服务连接器。是服务器端的程序,具有独立平台和协议的特性,用于交互式地浏览和生成数据,生成动态Web内容。
3.3 使用
编写类继承HttpServlet类,并覆盖doGet和doPost方法,并在web.xml文件或者使用@WebServlet注解配置访问路径。
public class HelloServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//解决中文乱码问题
resp.setCharacterEncoding("utf-8");//内容编码,防止出现中文乱码
resp.setContentType("text/html;charset=utf-8"); //向浏览器输出内容
resp.getWriter().write("这是第一个servlet程序。当前时间为:"+new Date());
}
}
3.4 工作原理
3.4.1 Servlet容器
servlet容器的产生是为了技术上的解耦,通过标准化接口来相互协作。
图中看出,真正管理servlet的容器是Context容器,且一个Context对应一个Web工程。
3.4.2 Servlet容器的启动
Tomcat启动代码
Tomcat tomcat=getTomcatInstance();
File appDir= new File(getBuildDirectory(), "webapps/examples") ;
tomcat. addWebapp(null, "/examplesappDir. getAbsolutePath());
tomcat. start();
ByteChunk res = geturl( "http://localhost:"+getport()+" /examples/servlets/servlet/HelloWorldExample");
assertTrue(res tostring().indexOf(<h1>Hello World! </h1>")>0);
public Context addwebapp (Hosthost, String url, String path){
silence(url);
Context ctx = new StandardContext();
ctx.setPath( url);
ctx. setDocBase(path);
if(defaultRealm== null){
initsimpleAuth();
}
ctx. setRealm(defaultRealm);
ctx. addLifecycleListener(new DefaultWebXmlListener());
Contextconfig ctxCfg = new ContextConfig();
ctx. addLifecycleListener(ctxCfg);
ctxcfg. setDefaultwebXml ("org/apache/catalin/startup/DEFAULT_XMI ") ;
if(host == null){
getHost(). addchild (ctx);
} else{
host.addchild(ctx);
}
return ctx;
(1)Tomcat启动过程
·getTomcatInstance()获取Tomcat实例
·新建StandardContext()容器,并设置访问URl和项目文件访问地址
·添加LifecycleListener
·新建ContextConfig。解析Web应用的配置文件web.xml等
·tomcat实例执行addWebapp()
·tomcat实例执行start(),启动整个Tomcat容器。
(2)tomcat所有的容器都集成Lifecycle接口,Lifecycle接口管理着容器的整个生命周期,所有容器的修改和状态的改变都会由它去通知已经注册的观察者(Listener)。这是基于观察者模式设计的。
(3)Context容器的启动
在tomcat容器启动后,当Context容器处于init初始化状态时,其中的Listener将会被调用。
首先执行ContextConfig的Init方法:
· 创建用于解析XML配置文件的contextDigester对象
· 读取默认的 context. xm配置文件,如果存在则解析它。
· 读取默认的Hos配置文件,如果存在则解析它
· 读取默认的 Context自身的配置文件,如果存在则解析它
· 设置 Context的 DocBase
ContextConfig的init方法完成后, Context容器就会执行startInternal()方法,这个方法的启动逻辑比较复杂,主要包括如下几部分:
· 创建读取资源文件的对象
· 创建 ClassLoader对象
· 设置应用的工作目录
· 启动相关的辅助类,如 logger、 realm、 resources等。
· 修改启动状态,通知感兴趣的观察者(Web应用的配置)
· 子容器的初始化
· 获取 ServletContext并设置必要的参数。
· 初始化“ load on startup”的 Servlet. 其他Servlet在第一次被调用的时候初始化。
在初始化时,会将Servlet包装成StandardWrapper。因为StandardWrapper是Tomcat容器中的一部分,它具有容器的特性,而Servlet作为一个独立的Web开发标准,不应该强耦合在Tomcat中。
3.4.3 Servlet实例的建立和初始化
(1)创建实例
web.xml中的配置项,“load-on-startup”如果大于0,则在Tomcat启动时Servlet就会被启动。调用Wrapper.loadServlet方法获取servletClass,并交给InstanceManager去创建对象。
(2)初始化实例
调用StandardWrapper的InitServlet方法初始化对象。
3.4.4 Servlet体系结构
· ServletConfig,在Servlet初始化时就传到Servlet,以StandardWrapperFacade对象的形式调用,可以防止暴露不必要的数据。
· ServletContext,获取Context容器的信息。
· ServletRquest
· ServletResponse
体系设计过程中用到门面设计模式。
3.4.5 Servlet调用
(1)访问URL:http://hostname:port/contextpath/servletPath
(2)Tomcat中的org.apache.tomcat.util.http.mapper完成URL到一个Servlet子容器的映射工作。Mapper类保存了Tomcat的Container容器中的所有子容器的信息,根据传入Servlet容器的请求的hostname和contextpath设置到request的mappingData属性中。
(3)Mapper子类MapperListener作为监听这监听容器的变化,这样其中的mapper属性也相应修改。
(4)执行Servlet接口的service(ServletRequest req, ServletResponse resp)方法。
3.4.6 Filter
3.4.6.1 简介
Filter也称之为过滤器,它是 Servlet 技术中最实用的技术,Web 开发人员通过 Filter 技术,对 web 服务器管理的所有 web 资源:例如 Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现 URL 级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
它主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理。使用 Filter 的完整流程:Filter 对用户请求进行预处理,接着将请求交给 Servlet 进行处理并生成响应,最后 Filter 再对服务器响应进行后处理。
3.4.6.2 Demo
/**
* 使用Filter 打印参数
* @author Administrator
*
*/
public class FilterDemo implements Filter {
public FilterDemo(){
System.out.println("FilterDemo 构造函数被执行...");
}
/**
* 销毁
*/
public void destroy() {
System.out.println("destroy");
}
/*
用户在每个请求进来时,访问doFilter方法,在Servlet的service方法之前调用。*/
public void doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse,
FilterChain paramFilterChain) throws IOException, ServletException {
System.out.println("doFilter");
HttpServletRequest request = (HttpServletRequest) paramServletRequest;
HttpServletResponse response = (HttpServletResponse) paramServletResponse;
// 请求地址
String requestURI = request.getRequestURI();
System.out.println("requestURI:"+requestURI);
// 参数
Map<String, String[]> parameterMap = request.getParameterMap();
for (String key : parameterMap.keySet()) {
String[] arr=parameterMap.get(key);
}
}
/**
* 初始化
*/
public void init(FilterConfig paramFilterConfig) throws ServletException {
System.out.println("init");
}
}
<filter>
<filter-name>FilterDemo</filter-name>
<filter-class>com.qian.servlet.FilterDemo</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterDemo</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3.4.6.3 相关类
(1)FilterConfig
可以通过其获取ServletContext对象。
(2)FilterChain
责任链设计模式。FilterChain是doFilter方法的传入参数,保存了当前整个请求链。通过调用FilterChain.doFilter方法,可以将请求继续传递下去,但是如果要拦截请求,则不调用。
当FilterChain上所有的Filter对象执行完成后,才会执行最终的Servlet。
3.4.6.4 注意事项
(1)Filter常用于登录、XSS攻击、权限方面。
(2)Filter是单例的,与Servlet类似。
3.5 Servlet生命周期
3.5.1 Servlet接口方法
public class TestServletService implements Servlet{
@Override
public void destroy() {
// TODO 自动生成的方法存根
}
@Override
public ServletConfig getServletConfig() {
// TODO 自动生成的方法存根
return null;
}
@Override
public String getServletInfo() {
// TODO 自动生成的方法存根
return null;
}
@Override
public void init(ServletConfig arg0) throws ServletException {
// TODO 自动生成的方法存根
}
@Override
public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException {
// TODO 自动生成的方法存根
}
}
其中,与生命周期相关的有四个方法:
(1)构造方法:创建servlet对象的时候调用。默认情况下,第一次访问(拥有load-on-startup设置的除外)servlet的时候创建servlet对象只调用1次。证明servlet对象在tomcat是单实例的。
(2)init方法: 创建完servlet对象的时候调用。只调用1次。
(3)service方法: 每次发出请求时调用。调用n次。
(4)destroy方法: 销毁servlet对象的时候调用。停止服务器或者重新部署web应用时销毁servlet对象。只调用1次。
3.5.2 Tomcat内部代码运行
(1)通过配置文件的映射关系,找到ServletClass内容。
(2)通过反射构造Servlet对象
(3)创建ServletConfig对象,反射调用init方法。
(4)创建request、response对象,反射调用service方法。
(5)销毁servlet时,反射调用destroy方法。
3.5.3 时序图
3.6 Servlet多线程问题
(1)servlet对象在tomcat服务器是单实例多线程的,为每个用户请求分配一个线程,可以通过线程池来管理。
(2)因为servlet是多线程的,所以当多个servlet的线程同时访问了servlet的共享数据,如成员变量,可能会引发线程安全问题。
解决办法:
· 把使用到共享数据的代码块进行同步(使用synchronized关键字进行同步)
· 建议在servlet类中尽量不要使用成员变量。如果确实要使用成员,必须同步。而且尽量缩小同步代码块的范围。(哪里使用到了成员变量,就同步哪里!!),以避免因为同步而导致并发效率降低。
3.7 域对象
(1)作用: 用于保存数据、获取数据,可以在不同的动态资源之间共享数据。
(2)使用:
· 保存数据 setAttribute(String,Object)
· 获取数据 Object getAttribute(String)
· 删除数据 removeAttribute(String)
(3)分类:
· HttpServletRequest
· HttpSession 会话对象
· PageContext
· ServletContext 作用范围为整个Web应用
(4)可以通过getContextPath()方法获取上下文路径
3.8 转发与重定向
(1)转发:
· 地址栏不会改变
· 只能转发到当前web应用
· 可以把数据保存到request域
· request.getRequestDispatcher(String).forward(request,response)
(2)重定向:
· 地址栏改变
· 可以跳转到其他web应用
· 不能使用request数据(涉及到2次浏览器请求)
· response.sendRedirect(String)
3.9 Cookie
会话数据保存在客户端。浏览器在每次访问服务端时,都会带着cookie信息。
关于Cookie的跨域问题:cookie 跨域问题_chou_out_man的博客-CSDN博客_cookie跨域
3.9.1 使用
(1)构造Cookie对象
Cookie(java.lang.String name, java.lang.String value)
(2)设置cookie
void setPath(java.lang.String uri) :设置cookie的有效访问路径
void setMaxAge(int expiry):设置cookie的有效时间
· 不设置,则随浏览器关闭而消失
· 整数(正负均可),cookie保存到浏览器,缓存在硬盘中
· 零,不保存cookie
void setValue(java.lang.String newValue) :设置cookie的值
(3)发送cookie到浏览器端保存
void response.addCookie(Cookie cookie) :发送cookie
(4)服务器接收cookie
Cookie[] request.getCookies() :接收cookie
3.9.2 Demo
@WebServlet("/LastAccessTime")
public class LastAccessTime extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");// 防止浏览器显示乱码
String lastAccessTime = null;
Cookie[] cookies = req.getCookies();
for (Cookie cookie : cookies) {
String name = cookie.getName();
if (name.equals("lastAccessTime")) {
astAccessTime = cookie.getValue();
break;
}
}
if (StringUtils.isEmpty(lastAccessTime)) {
resp.getWriter().print("您是首次访问!");
} else {
resp.getWriter().print("你上次访问时间:" + lastAccessTime);
}
// 保存访问时间
// 创建cookie 将当前时间作为cookie保存到浏览器
String currenttime = new SimpleDateFormat("yyyy-mm-dd hh:mm:ss").format(new Date());
Cookie cookie = new Cookie("lastAccessTime", currenttime);
cookie.setMaxAge(60 * 60 * 24);
// 发送cookie
resp.addCookie(cookie);
}
}
3.9.3 工作原理
真正构建Cookie是在org.apache.catalina.connector.Response类中,调用generateCookieString方法将Cookie对象构造成一个String字符串,并将字符串命名为Set-Cookie添加到Header中。
3.9.4 利弊分析
弊端
· 只能存字符串类型,不能保存对象
· 只能存非中文
· 1个Cookie的容量不超过4KB,最多300个
· 安全性差
3.9.5 压缩cookie
(1)采用文本压缩方式。可采用gzip或者deflate算法
(2)压缩后进行转码,采用Base32或者Base64。因为Cookie中不能包含控制字符,只能包含ASCII码中34~126的可见字符。
3.10 Session
会话数据保存在服务器端(内存中)。
3.10.1 使用
HttpSession类:用于保存会话数据
(1)创建或得到session对象
HttpSession getSession()
HttpSession getSession(boolean create)
(2)设置session对象
void setMaxInactiveInterval(int interval) : 设置session的有效时间
void invalidate() : 销毁session对象
java.lang.String getId() : 得到session编号
(3)保存会话数据到session对象
void setAttribute(java.lang.String name, java.lang.Object value) : 保存数据
java.lang.Object getAttribute(java.lang.String name) : 获取数据
void removeAttribute(java.lang.String name) : 清除数据
3.10.2 demo
@WebServlet("/TestSession")
public class TestSession extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public TestSession() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.setContentType("text/html;charset=utf-8");// 防止浏览器显示乱码
HttpSession session = request.getSession();
String lastTime = (String) session.getAttribute("lastTime");
if(lastTime == null) {
response.getWriter().write("这是第一次访问");
}else {
response.getWriter().write("上次访问时间:" + lastTime + "现在: " + new Date());
}
session.setMaxInactiveInterval(60 * 60 * 24);
session.setAttribute("lastTime", new Date()+ "");
}
}
3.10.3 与Cookie关系
(1)服务器生成唯一对应session对象的JSESSIONID,并作为cookie发送到浏览器端保存。
(2)如果浏览器禁用cookie可以将JSESSIONID写到用户请求的URL中。
(3)如何避免浏览器的JSESSIONID的cookie随着浏览器关闭而丢失的问题
/**
* 手动发送一个硬盘保存的cookie给浏览器
*/
Cookie c = new Cookie("JSESSIONID",session.getId());
c.setMaxAge(60*60);
response.addCookie(c);
3.10.4 工作原理
(1)request.getSession()方法,触发创建session对象,并加入到org.apache.catalina.Manager的session容器中保存。
(2)Manager负责servlet容器中所有session的生命周期管理。当servlet容器重启或者关闭时,Manager负责持久化(调用upload方法)没有过期的session对象到“SESSIONS.ser”文件中,也会定期检测过期。
3.10.5 分布式session
(1)服务订阅服务器,集中管理资源和配置,统一通过它来推送配置。可以使用Zookeeper实现。
(2)分布式缓存,存储共享集群中每台集群的session。
(3)存取方式。
· 自定义InnerHttpSession类重新实现HttpSession接口。
· 通过Filter拦截用户请求,将自己设置的InnerHttpSession对象设置到request和response对象中。
· 应用创建的所有Session对象都保存在InnerHttpSession对象中,访问完成后将InnerHttpSession内容更新到分布式缓存中。
3.10.6 跨域名共享Cookie
(1)利用跳转应用支持多个域名的访问,并将同一个sessionID作为cookie写到多个域名下。
(2)多个域名,根据sessionID在分布缓存中拿取session信息。
3.10.7 表单重复提交问题
(1)原因
· 网络延时
· 浏览器重新刷新按钮
· 浏览器“回退”按钮,再提交。
(2)解决方案
· 利用javaScript代码进行标识
<%@ 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 isFlag = false; //表单是否已经提交标识,默认为false
function submitFlag() {
if (isFlag == false) {
isFlag = true;
return true;
} else {
return false;
}
}
</script>
</head>
<body>
<form action="${pageContext.request.contextPath}/DoFormServlet" method="post" onsubmit="return submitFlag()">
用户名:<input type="text" name="userName"> <input type="submit" value="提交" id="submit">
</form>
</body>
</html>
·利用js让按钮在提交一次后不可用
function dosubmit(){
//获取表单提交按钮
var btnSubmit = document.getElementById("submit");
//将表单提交按钮设置为不可用,这样就可以避免用户再次点击提交按钮
btnSubmit.disabled= "disabled";
//返回true让表单可以正常提交
return true;
}
· 在session域中生成并保存唯一token,在页面中加入隐藏域存储token,提交时进行检验
//用户访问服务器
@WebServlet("/ForwardServlet")
public class ForwardServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getSession().setAttribute("sesionToken", TokenUtils.getToken());
req.getRequestDispatcher("form.jsp").forward(req, resp);
}
}
//跳转页面
<%@ 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>
</head>
<body>
<form action="${pageContext.request.contextPath}/DoFormServlet" method="post" onsubmit="return dosubmit()">
<input type="hidden" name="token" value="${sesionToken}"> 用户名:<input type="text"
name="userName"> <input type="submit" value="提交" id="submit">
</form>
</body>
</html>
//服务器后端处理
HttpSession session = request.getSession();
String sesionToken = (String) session.getAttribute("sesionToken");
String token = request.getParameter("token");
if (!(token.equals(sesionToken))) {
return false;
}
session.removeAttribute("sesionToken");
3.11 Token
随机性令牌,唯一不重复字符串。
生成方式:
· 自定义唯一识别码
· UUID.randomUUID().toString()
3.12 Web安全与攻防
(1)XSS 跨站脚本注入
利用Filter进行拦截,例如将<script>转化为html元素 <script>。
(2)CSRF 跨站请求伪造,避免Cookie,利用token。
(3)上传漏洞、可执行exe文件。
(4)Sql注入 利用#{}和?占位符解决,避免sql语句的拼接。
4 HTTP
4.1 简介
超文本传输协议,是浏览器个服务端之间数据传输的格式规范。HTTP使用TCP作为传输层协议,但HTTP本身是无连接、无状态的,可以短连接(Http/1.0),也可以长连接(Http/1.1 保持一段时间)。
4.2 Http请求
4.2.1 请求格式
GET /day09/hello HTTP/1.1 -请求行
Host: localhost:8080 --请求头(多个key-value对象)
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,en-us;q=0.8,zh;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
--一个空行
name=eric&password=123456 --(可选)实体内容
4.2.2 请求行
GET /day09/hello HTTP/1.1
(1)请求方式
常见的请求方式: GET 、 POST、 HEAD、 TRACE、 PUT、 CONNECT 、DELETE
常用的请求方式: GET 和 POST
· GET
· URL后面可以跟参数
· 参数限制为1KB内
· 默认方式,但不适合提交敏感密码等内容
· POST
· 参数在请求体中
· 参数数据无限制
· 适合提交敏感内容
(2)http协议版本
http1.0:当前浏览器客户端与服务器端建立连接之后,只能发送一次请求,一次请求之后连接关闭。
http1.1:当前浏览器客户端与服务器端建立连接之后,可以在一次连接中发送多次请求。(基本都使用1.1)
(3)请求资源
URL: 统一资源定位符。http://localhost:8080/day09/testImg.html。只能定位互联网资源。是URI 的子集。
URI: 统一资源标记符。/day09/hello。用于标记任何资源。可以是本地文件系统,局域网的资源(//192.168.14.10/myweb/index.html), 可以是互联网。
4.2.3 请求头
· 多个key-value对象
· 含有配置信息和cookie
4.2.4 实体内容
仅POST方式含有(与请求头有一个空行)
4.2.5 HttpServletRequest对象
HttpServletRequest对象作用是用于获取请求数据。
核心的API:
(1)请求行:
request.getMethod(); 请求方式
request.getRequetURI() / request.getRequetURL()请求资源
request.getProtocol()请求http协议版本
(2)请求头:
request.getHeader("名称") 根据请求头获取请求值
request.getHeaderNames()获取所有的请求头名称
(3)实体内容:
request.getInputStream()获取实体内容数据
request.getParameter("参数名"); 根据参数名获取参数值(注意,只能获取一个值的参数)
request.getParameterValue("参数名“);根据参数名获取参数值(可以获取多个值的参数)
request.getParameterNames(); 获取所有参数名称列表
4.3 Http响应
4.3.1 响应格式
HTTP/1.1 200 OK --响应行
Server: Apache-Coyote/1.1 --响应头(key-vaule)
Content-Length: 24
Date: Fri, 30 Jan 2015 01:54:57 GMT
--一个空行
this is hello servlet!!! --实体内容
4.3.2 响应行
(1)http协议版本
(2)状态码: 服务器处理请求的结果(状态)
常见的状态码:
200: 表示请求处理完成并完美返回
302: 表示请求需要进一步细化。 404: 表示客户访问的资源找不到。
500: 表示服务器的资源发送错误。(服务器内部错误)
(3)状态描述
4.3.3 HttpServletResponse对象
HttpServletResponse对象修改响应信息:
(1)响应行:
response.setStatus() 设置状态码
(2)响应头:
response.setHeader("name","value") 设置响应头
(3)实体内容:
response.getWriter().writer(); 发送字符实体内容
response.getOutputStream().writer() 发送字节实体内容
4.4 Https
4.4.1 与Http区别
(1)https 协议需要到 ca 申请证书,一般免费证书较少,因而需要一定费用。
(2)http 是超文本传输协议,信息是明文传输,https 则是具有安全性的 ssl 加密传输协议(对称加密)。
(3)http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
(4)http 的连接很简单,是无状态的;HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 http 协议安全。
4.4.2 工作原理
客户端在使用HTTPS方式与 Web 服务器通信时有以下几个步骤。
(1)客户使用 https 的 URL 访问 Web 服务器,要求与 Web 服务器建立 SSL 连接。
(2)Web 服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
(3)客户端的浏览器与 Web 服务器开始协商 SSL 连接的安全等级,也就是信息加密的等级。
(4)客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
(5)Web 服务器利用自己的私钥解密出会话密钥。
(6)Web 服务器利用会话密钥加密与客户端之间的通信。
4.4.3 其他加密算法
4.4.3.1 AES
高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法(微信小程序加密传输就是用这个加密算法的)。对称加密算法也就是加密和解密用相同的密钥,具体的加密流程如下图:
4.4.3.2 RSA
RSA 加密算法是一种典型的非对称加密算法,它基于大数的因式分解数学难题,它也是应用最广泛的非对称加密算法。
非对称加密是通过两个密钥(公钥-私钥)来实现对数据的加密和解密的。公钥用于加密,私钥用于解密。
4.4.3.3 CRC
循环冗余校验(Cyclic Redundancy Check, CRC)是一种根据网络数据包或电脑文件等数据产生简短固定位数校验码的一种散列函数,主要用来检测或校验数据传输或者保存后可能出现的错误。它是利用除法及余数的原理来作错误侦测的。
4.4.3.4. MD5
MD5 常常作为文件的签名出现,我们在下载文件的时候,常常会看到文件页面上附带一个扩展名为.MD5 的文本或者一行字符,这行字符就是就是把整个文件当作原数据通过 MD5 计算后的值,我们下载文件后,可以用检查文件 MD5 信息的软件对下载到的文件在进行一次计算。两次结果对比就可以确保下载到文件的准确性。 另一种常见用途就是网站敏感信息加密,比如用户名密码,支付签名等等。随着 https 技术的普及,现在的网站广泛采用前台明文传输到后台,MD5 加密(使用偏移量)的方式保护敏感数据保护站点和数据安全。
参考:MD5算法分析及逆向详解_天下布武之信长的专栏-CSDN博客_md5逆向
4.5 HttpClient工具
· 需要导入的依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.29</version>
</dependency>
</dependencies>
· HttpClient
/**
* 发送 post请求访问本地应用并根据传递参数不同返回不同结果
*/
public void post() {
// 创建默认的httpClient实例.
CloseableHttpClient httpclient = HttpClients.createDefault();
// 创建httppost
HttpPost httppost = new HttpPost("http://localhost:8080/myDemo/Ajax/serivceJ.action");
// 创建参数队列
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair("type", "house"));
UrlEncodedFormEntity uefEntity;
try {
uefEntity = new UrlEncodedFormEntity(formparams, "UTF-8");
httppost.setEntity(uefEntity);
System.out.println("executing request " + httppost.getURI());
CloseableHttpResponse response = httpclient.execute(httppost);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
System.out.println("--------------------------------------");
System.out.println("Response content: " + EntityUtils.toString(entity, "UTF-8"));
System.out.println("--------------------------------------");
}
} finally {
response.close();
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭连接,释放资源
try {
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 发送 get请求
*/
public void get() {
CloseableHttpClient httpclient = HttpClients.createDefault();
try {
// 创建httpget.
HttpGet httpget = new HttpGet("http://www.baidu.com/");
System.out.println("executing request " + httpget.getURI());
// 执行get请求.
CloseableHttpResponse response = httpclient.execute(httpget);
try {
// 获取响应实体
HttpEntity entity = response.getEntity();
System.out.println("--------------------------------------");
// 打印响应状态
System.out.println(response.getStatusLine());
if (entity != null) {
// 打印响应内容长度
System.out.println("Response content length: " + entity.getContentLength());
// 打印响应内容
System.out.println("Response content: " + EntityUtils.toString(entity));
}
System.out.println("------------------------------------");
} finally {
response.close();
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭连接,释放资源
try {
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.6 跨域解决方案
(1)ajax + 响应头 Access-control-Allow-origin
在当前域名请求网站中,默认不允许通过ajax请求发送其他域名。报错XMLHttpRequest cannot load 。
$.ajax({
type : 'post',
dataType : "text",
url : "http://a.a.com/a/FromUserServlet",
data : "userName=余胜军&userAge=19",
success : function(msg) {
alert(msg);
}
});
后台response添加header,response.setHeader("Access-Control-Allow-Origin", "*"); 支持所有网站
(2)JSONP
在ajax中加入JSONP格式传输。
$.ajax({
type : "POST",
async : false,
url : "http://a.a.com/a/FromUserServlet?userName=张三",
dataType : "jsonp",//数据类型为jsonp
jsonp : "jsonpCallback",//服务端用于接收callback调用的function名的参数
success : function(data) {
alert(data.result);
},
error : function() {
alert('fail');
}
});
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("UTF-8");
// resp.setHeader("Access-Control-Allow-Origin", "*");
String userName = req.getParameter("userName");
String userAge = req.getParameter("userAge");
System.out.println(userName + "----" + userAge+"---"+req.getMethod());
try {
resp.setContentType("text/plain");
resp.setHeader("Pragma", "No-cache");
resp.setHeader("Cache-Control", "no-cache");
resp.setDateHeader("Expires", 0);
PrintWriter out = resp.getWriter();
JSONObject resultJSON = new JSONObject(); // 根据需要拼装json
resultJSON.put("result", "content");
String jsonpCallback = req.getParameter("jsonpCallback");// 客户端请求参数
out.println(jsonpCallback + "(" + resultJSON.toJSONString() + ")");// 返回jsonp格式数据
out.flush();
out.close();
} catch (Exception e) {
// TODO: handle exception
}
}
· 注意事项
· 原理是,底层生成一个get请求发送。
· 缺点:只支持get请求,不支持post请求
(3)网关系统
使用nginx进行域名转发
(4)使用后台服务器转发,如HttpClient
不建议使用,非常占宽带。
5 单点登录SSO
5.1 概念
SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
例如访问在网易账号中心(http://reg.163.com/)登录之后,访问以下站点都是登录状态:
· 网易直播http://v.163.com
· 网易博客http://blog.163.com
· 网易花田http://love.163.com
· 网易考拉https://www.kaola.com
· 网易Lofterhttp://www.lofter.com
5.2 架构
(1)架构说明
· 前台站点:需要登录的站点
· SSO站点-登录:提供登录的页面
· SSO站点-登出:提供注销登录的入口
· SSO服务-登出:提供登录服务
· SSO服务-登录状态:提供登录状态校验/登录信息查询的服务
· SSO服务-登出:提供用户注销登录的服务
· 数据库:存储用户账户信息
· 缓存:存储用户的登录信息,通常使用Redis
·用户登录状态的存储和校验逻辑
(2)用户登录状态的存储和校验
常见的Web框架对于Seesion的实现都是生成一个SessionId存储在浏览器Cookie中。然后将Session内容存储在服务器端内存中。
用户登录成功之后,生成AuthToken交给客户端保存。如果是浏览器,就保存在Cookie中。如果是手机App就保存在App本地缓存中。 用户在浏览需要登录的页面时,客户端将AuthToken提交给SSO服务校验登录状态/获取用户登录信息
对于登录信息的存储,建议采用Redis,使用Redis集群来存储登录信息,既可以保证高可用,又可以线性扩充。同时也可以让SSO服务满足负载均衡/可伸缩的需求。
AuthToken,直接使用UUID/GUID即可,如果有验证AuthToken合法性需求,可以将UserName+时间戳加密生成,服务端解密之后验证合法性
5.3 用户登录
按照上图,用户登录后Authtoken保存在Cookie中。 domian= test. com浏览器会将domain设置成 .test.com,这样访问所有*.test.com的web站点,都会将Authtoken携带到服务器端。然后通过SSO服务,完成对用户状态的校验/用户登录信息的获取。
5.4 登录信息获取/登录状态校验:
5.5 用户登出时序图
用户登出时要做的事情很简单:
· 服务端清除缓存(Redis)中的登录状态
· 客户端清除存储的AuthToken
5.6 跨域登录/登出
跨域要解决的问题,就是如何解决Cookie的跨域读写问题。
解决跨域的核心思路就是:
· 登录完成之后通过回调的方式,将AuthToken传递给主域名之外的站点,该站点自行将AuthToken保存在当前域下的Cookie中。
· 登出完成之后通过回调的方式,调用非主域名站点的登出页面,完成设置Cookie中的AuthToken过期的操作。
·跨域登录(主域名已登录)
·跨域登录(主域名未登录)
·跨域登出
5.7 实现
6 常见问题总结
6.1 eclipse 开发web程序
启动tomcat服务器的时候。临时目录在你的工作区间workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\
用于临时存放服务器编译过后的jsp、servlet、字节码文件、图片之类的,相当于缓存,不用每次都编译,提高速度。
6.2 Servlet里resp.getWriter().write("\n")不能换行
servlet编程的时候使用 resp.getWriter().write("\n") 来换行,一开始还觉得代码没有问题呀,也没报错,太天真了,原来是要用html标签来换行。
使用resp.getWriter().write("< br/>") 完全ok,不然resp.setContentType(“text/html”)是摆在那里吃素的吗!!!