session 原理
客户端(浏览器)第一次访问服务器的时候(一般为访问 servlet 或者 jsp,html 不会产生session),请求中不携带任何标识给服务器,服务器无法找到与之对应的 session,所以会新建 session 对象,并将 session 标识放到响应头的 Set-Cookie中,以 key-value 形式返回给客户端,例:JSESSIONID=7F149950097E7B5B41B390436497CD21; JSESSIONID 是固定的key,value 值是给该客户端新创建的 session 的 ID,之后的请求客户端会将此 key-value 放到 cookie 中一并请求服务器,服务器根据此 ID 寻找对应的 session 对象了;
注意:当浏览器关闭后,会话结束,由于 cookie 消失所以对应的 session 对象标识消失,而对应的服务器上的 session 依然存在,但已经成为报废数据等待 GC 回收了。
对应 session 的 ID 可以利用此方法得到:session.getId();
HttpSession(接口)
HttpSession 是 Java 平台对 session 机制的实现规范,仅仅是个接口,具体到每个 web 应用服务器的提供商,除了对规范支持之外,仍然会有一些规范里没有规定的细微差异。
HttpSession 生命周期
若 Servlet 是客户端访问的第一个WEB应用的资源,则只有调用了 request.getSession() 或request.getSession(true) 才会创建 HttpSession 对象。
如何获取
request.getSession(boolean create)
create 为 false,若没有和当前JSP页面关联的 HttpSession 对象,则返回 null;若有返回
create 为 true,若没有 HttpSession 对象,服务器创建一个新的 HttpSession 返回,若有就返回关联的
request.getSession() 等同于request.getSession(true)
什么时候销毁
直接调用HttpSession的invalidate()方法:使HttpSession失效
服务器卸载了当前 Web 应用。
超出 HttpSession 的过期时间。
JWT的Token认证机制实现
使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程:
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
- 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
- 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
概括: 简言之 JWT 的 Token 就是服务端签发的字符串是由 base64 后的 header(第一部分) + base64 后的 payload(第二部分) + 对前两者拼接后的字符进行加盐 secret 组合加密的(第三部分) 三部分用.间隔,这个字符串一般每次请求都放在请求头里如 Authorization:Bearer +上面token字符串,服务端每次先取出这个字符串校验,无状态(也称:服务端可扩展行):Token机制在服务端不需要存储 session 信息,因为 Token 自身包含了所有登录用户的信息,只需要在客户端的 cookie 或本地介质存储状态信息
1 什么是JWT
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
2 JWT 组成
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
1
头部(Header)
头部用于描述关于该 JWT 的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个 JSON 对象。
在头部指明了签名算法是 HS256 算法。 我们进行 BASE64 编码 http://base64.xpcha.com/,编码后的字符串
如下: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9,Base64是一种基于64个可打印字符来表示二进制数据的表示方法
载荷(playload)
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,
这些有效信息包含三个部分{“typ”:“JWT”,“alg”:“HS256”}
(1)标准中注册的声明(建议但不强制使用)
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
(2)公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
(3)私有的声明
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
这个指的就是自定义的claim。比如前面那个结构举例中的admin和name都属于自定的claim。这些claim跟JWT标准规定的claim区别在于:JWT规定的claim,JWT的接收方在
拿到JWT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。
定义一个payload:
{"sub":"1234567890","name":"John Doe","admin":true}
然后将其进行base64编码,得到Jwt的第二部分: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
签证(signature)
jwt 的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret
这个部分需要 base64 加密后的 header 和 base64 加密后的payload使用.连接组成的字符串,然后通过header 中声明的加密方式进行加盐secret组合加密,
然后就构成了jwt的第三部分: TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
将这三部分用.连接成一个完整的字符串,构成了最终的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:secret 是保存在服务器端的,jwt 的签发生成也是在服务器端的,secret 就是用来进行 jwt 的签发和 jwt 的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发 jwt 了。
优缺点
优点
1.更适用于移动应用: Cookie 不支持手机端访问的
2.无状态
缺点
1.注销登录等场景下 token 还有效
退出登录;
修改密码;
服务端修改了某个用户具有的权限或者角色;
用户的帐户被删除/暂停。
用户由管理员注销; 都有这些问题
解决方案
将 token 存入内存数据库如 redis。如果需要让某个 token 失效就直接从 redis 中删除这个 token。但这样需每次使用 token 发送请求都要先从 DB 中查询 token 是否存在的步骤,且违背 JWT 无状态原则。
1
2.token 的续签问题
颁发时有效期设置多久合适?都不合适
折中解决方案:每次请求时校验还有多久过期,快过期重新颁发,这也牺牲了性能
Shiro 和 spring-security
Apache Shiro 是一个强大且易用的Java安全框架,【身份验证、授权、密码加密和会话管理】
shiro的主要功能、核心组件
主要功能
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
核心组件
Subject:subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
SecuritryManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm:Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
可以将Realm类比为SpringMVC中的Dao层(比他更加安全)。
认证和授权
Realm 是真正负责处理认证和授权的组件。
SecurityManager 要完成认证,要 Realm 返回一个 AuthenticationInfo 携带存储库中用户认证信息,用来与提交的信息进行比对,如不匹配,会认证失败。
SecurityManager 要完成授权,需要 Realm 返回一个 AuthorizationInfo
认证依赖于方法 doGetAuthenticationInfo
授权依赖于方法 doGetAuthorizationInfo
supports(AuthenticationToken token) 方法可以处理非 UsernamePasswordToken 的情况。
public class MyRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Object principal = principals.getPrimaryPrincipal();
System.out.println(“RealmForDouble授权中---->用户:”+principal);
SimpleAuthorizationInfo info = null;
Set roles = new HashSet<>();
if (“admin”.equals(principal)){
roles.add(“admin”);
}
if (“guest”.equals(principal)){
roles.add(“guest”);
}
info = new SimpleAuthorizationInfo(roles);
return info;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println(“RealmForDouble认证中---->用户:”+token.getPrincipal());
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String password=“123456”;// 假设这是从数据库中查询到的用户密码
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(upToken.getUsername(),password,this.getName());
return info;
}
}
shiro+redis 实现 session 共享
实现逻辑就是 shiro 的拦截器会首先去 redis 里面获取 session ,作为当本次请求的 session
优缺点对比
shiro的优点
shiro的代码更易于阅读,且使用更加简单;
shiro可以用于非web环境,不跟任何框架或容器绑定,独立运行;
shiro 通过 filter 过滤器拦截请求判断是否需要登录,校验 session 是否合法(是否登录了)
shiro 可以做登录和授权,我们只用了登录功能,是否有权限访业务功能是通过人员、角色、权限表来控制的
shiro 的缺点
授权第三方登录需要手动实现;
spring-security 的优点
spring-security对spring结合较好,项目是spring-boot等搭建的,使用起来更加方便;
有更好的spring社区进行支持;
支持oauth授权,官方网站:spring-security-oauth
spring-security的缺点
相比shiro,spring-security比较复杂,没有shiro清晰易懂;