文章目录
- 登录校验
- Cookie
- Session
- JWT
- 生成JWT
- 校验JWT
- 基于JWT进行身份验证
- CSRF
- Cookie、Session、Token的区别?
- 过滤器(Filter)
- 配置过滤器
- 过滤器链
登录校验
由于HTTP协议是无状态的,我们在进行登录后等一系列接口请求是无法直接区分是哪一个用户的发给服务器的请求,就需要用到会话技术来区分不同用户的请求,也可用来进行登录的校验(统一拦截),传统的会话技术有Cookie、Session,还有目前主流的Token。
会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开,会话结束。一次会话可以包含多次请求和响应。
Cookie
Cookie是HTTP支持的一种本地存储的机制,Cookie存放在客户端(浏览器),Cookie的值是一个字符串一般由程序员自己定义。当用户使用浏览器首次访问服务器的时候,响应头中就会带有一个Set-Cookie,这个Cookie的值就会被保存到浏览器中,后面用户的每一次请求都会带上这个Cookie。
由于Cookie存储空间有限(取决于浏览器具体的实现的,一般都不很大),因此Cookie一般能保存一些简单的信息,最典型的就是用户身份信息。
Cookie的缺点就是,上诉说的存储空间小,不够安全,因为Cookie是存放在本地浏览器中的,Cookie是可以被用户手动禁用的,移动端APP是无法使用Cookie的。
Session
Session是一种存在服务器上的会话技术,对比Cookie来说Session是相对安全一点的。一般Session是借助Cookie来传递身份标识(SessionID)的。在浏览器第一次访问服务器的时候,同样会通过Set-Cookie来将Session带上,并保存在浏览器的Cookie中,再后面的请求中都会带上这个SessionID,来区分不同浏览器。
而Session的缺点和Cookie类似,而且在分布式场景下会存在Session失效的问题。
禁用Cookie后Session还可以使用吗?
一般SessionId是借助Cookie来传递的,但如果Cookie被禁用了,就不知道直接使用Cookie传递SessionId了,有两种解决方式:
- 将SessionId放在每个请求的URL中传递,服务端再解析SessionId
- 将SessionId 放在表单中也就是请求体中传递给服务端
JWT
JWT全称JSON Web Token,JWT是一个字符串可以在请求头和响应头中直接传递,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
JWT由三个部分组成:
-
第一部分:Header(头),记录了令牌的类型、签名算法等,如:
{ "alg": "HS256", "typ": "JWT" }
-
第二部分:Payload(有效载荷),携带一些自定义信息,默认信息等,如:
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
-
第三部分:Signature(签名),防止Token被篡改,确保安全性,将header、payload,并加入指定密钥,通过指定签名算法计算而来。
在传递Token的时候,前两部分都会转换成base64格式的字符来进行传递,而第三部分则是通过指定的签名算法计算而来的,并不是base64格式的字符串。三个部分用点号分割,如图。
而由于数字签名的存在,整个JWT当中任何一个地方被篡改了,在校验的时候就会失败。
JWT的使用场景也是登录,在用户登录成功后生成一个token返回给前端,后续每个请求都会带上这个token。
优点:
- 支持PC端和移动端
- 支持跨域,JWT 包含了完整的认证和授权信息
- 解决集群下的认证问题
- 减轻服务端存储压力,传统的基于会话的认证机制需要服务器在会话中存储用户的状态信息,包括用户的登录状态、权限等。而使用 JWT,服务器无需存储任何会话状态信息,所有的认证和授权信息都包含在 JWT 中
缺点:
需要自己实现
生成JWT
引入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
@Test
public void testGenerateJwt() {
Map<String,Object> claims = new HashMap<>();
claims.put("id",1);
claims.put("username","hhy");
String jwt = Jwts.builder()
.signWith(SignatureAlgorithm.HS256,"testToken")// 签名算法和签名密钥
.setClaims(claims) // 自定义内容
.setExpiration(new Date(System.currentTimeMillis() + 3600*1000))// 设置token有效时间为1h
.compact();
System.out.println(jwt);
}
校验JWT
@Test
public void testParseJwt() {
Claims claims = Jwts.parser()
.setSigningKey("testToken") // 指定签名密钥
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNzA1ODA5NzQyLCJ1c2VybmFtZSI6ImhoeSJ9." +
"-XMvQ0q0j7Ew49RZkae3Z0DpZ-a6d4O6Uw9sOi5ft-U")// 解析token
.getBody();
System.out.println(claims);
}
基于JWT进行身份验证
服务器通过 Payload、Header 和 Secret(密钥)创建 JWT 并将 JWT 发送给客户端。客户端接收到 JWT 之后,会将其保存在 Cookie 或者 localStorage 里面,以后客户端发出的所有请求都会携带这个令牌。而服务端收到这个JWT后就会先进行校验
CSRF
什么 Cookie 无法防止 CSRF 攻击,而 Token 可以?CSRF(Cross Site Request Forgery) 一般被翻译为 跨站请求伪造 。那么什么是 跨站请求伪造 呢?攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
- 用户登录了网站a.com,并在Cookie中保存了SessionId
- 接着黑客在test.com网站放置了诱导用户点击的链接,让用户访问了test.com
- 而访问test.com的时候就会默认带上用户的登录后的SessionId
- 这时用户访问黑客的页面,就让黑客拿到了用户的SessionId
- 黑客就可以通过SessionId去伪造用户的请求给a.com发送请求
需要注意的是:黑客无法直接窃取到用户的信息(Cookie,Header,网站内容等),仅仅是冒用Cookie中的信息
使用Token就可以防止CSRF,在我们登录成功后获得Token之后,一般会将这个Token存放在localStorage中,接着前端通过某些方式给每个发到后端的请求都加上这个Token,即使你用户点击了非法的链接发送请求到服务器,这个请求是不会携带Token的,所有这个请求是非法的。所以要想避免CSRF就可以使用Token,并将Token存储到本地存储中,而不是存储到Cookie中
Cookie、Session、Token的区别?
- 首先就是存储位置,Cookie是存储在浏览器的,而Session是存储在服务器上的,Token一般是以加密的方式存储在localStorage 中的
- Session一般是配合Cookie来进行使用的,通过响应头中的Set-Cookie来设置一个SessionId,这个Session一般保存在浏览器的Cookie中
- Session和Cookie默认是不能跨域的,而Token是可以跨域的,
- 因为Cookie是直接存储在浏览器中,所以不适合存储敏感数据,而Session存储在服务器中,相对来说更可靠一些,而Token是以指定的签名算法来进行加密后存储到浏览器中的
过滤器(Filter)
Filter过滤器,是JavaWeb三大组件之一(Servlet、Fiter、Listener)
过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。
配置过滤器
使用步骤:
-
定义Filter:定义一个类,实现Filter接口,并重写其方法
-
配置Filter:Filter类上加上
@WebFilter
注解,配置拦截资源的路径。@WebFilter(urlPatterns = "/*") // 拦截所有请求 public class MyFilter implements Filter { /** * 初始化执行一次 * @param filterConfig * @throws ServletException */ @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { // 拦截到请求时,调用该方法,可以调用多次 //放行前逻辑 filterChain.doFilter(servletRequest,servletResponse);// 放行 // 放行后逻辑 } @Override public void destroy() { // 销毁方法,服务器关闭时调用,值调用一次 } }
urlPatterns的值:
- 拦截具体路径
/login
- 目录拦截
/user/*
- 拦截所有
/*
- 拦截具体路径
-
在启动类上加
@ServletComponentScan
开启Servlet组件支持,因为Filter是JavaWeb的组件,而不是Spring的
@ServletComponentScan
@SpringBootApplication
public class JwtApplication {
public static void main(String[] args) {
SpringApplication.run(JwtApplication.class, args);
}
}
过滤器链
如果在项目中配置多个过滤器就形成了一个过滤器链,这些过滤器的执行顺序就是按照以类名按照字符串排序执行