导航
[封装01-设计模式] 设计原则 和 工厂模式(简单抽象方法) 适配器模式 装饰器模式
[封装02-设计模式] 命令模式 享元模式 组合模式 代理模式
[React 从零实践01-后台] 代码分割
[React 从零实践02-后台] 权限控制
[React 从零实践03-后台] 自定义hooks
[React 从零实践04-后台] docker-compose 部署react+egg+nginx+mysql
[React 从零实践05-后台] Gitlab-CI使用Docker自动化部署
[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend
[源码-vue06] Vue.nextTick 和 vm.$nextTick
[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI
[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[深入21] 数据结构和算法 - 二分查找和排序
[深入22] js和v8垃圾回收机制
[深入23] JS设计模式 - 代理,策略,单例
[前端学java01-SpringBoot实战] 环境配置和HelloWorld服务
[前端学java02-SpringBoot实战] mybatis + mysql 实现歌曲增删改查
[前端学java03-SpringBoot实战] lombok,日志,部署
[前端学java04-SpringBoot实战] 静态资源 + 拦截器 + 前后端文件上传
[前端学java05-SpringBoot实战] 常用注解 + redis实现统计功能
[前端学java06-SpringBoot实战] 注入 + Swagger2 3.0 + 单元测试JUnit5
[前端学java07-SpringBoot实战] IOC扫描器 + 事务 + Jackson
[前端学java08-SpringBoot实战总结1-7] 阶段性总结
[前端学java09-SpringBoot实战] 多模块配置 + Mybatis-plus + 单多模块打包部署
[前端学java10-SpringBoot实战] bean赋值转换 + 参数校验 + 全局异常处理
[前端学java11-SpringSecurity] 配置 + 内存 + 数据库 = 三种方式实现RBAC
[前端学java12-SpringSecurity] JWT
(一) 前置知识
(1) 一些单词
signature 签名
bearer 持票人
secure 安全的 // security 安全的
organize 组织
interval 间隔
poll 轮询 // polling interval 轮询间隔
brand 品牌 // brand color 品牌色
calendar 日历
algorithm 算法
amount 数量,总计,金额
verify 验证
(2) 传统的session认证
- session的具体认证流程
- 1.用户提交用户名,密码到服务器
- 2.服务器认证通过后,在
session
中保存相关数据,比如用户名,角色,登陆时间,等 - 3.服务器向客户端返回一个
session_id
,并写入cookie
- 4.客户端
以后的每一次请求
,都会携带cookie
,即将session_id
传回服务器 - 5.服务器收到
session_id
,找到之前保存的数据
,由此得知用户的身份
- sesstion其实就是一个 ( 对象 )
cookie <-> sessionId <-> session
- 缺点
- 集群服务
- 跨域
- csrf
- 扩展性不好:如果是 ( 集群服务 ),或者需要 ( 跨域 ),则需要session共享,( 每台服务器都要能读取session )
- 使用cookie携带信息,可能会遭到 csrf攻击
- 代码测试
@RestController
public class TestSessionController {
@GetMapping("/session")
public String getSession(
@RequestParam("username") String username,
HttpServletRequest request
) {
// 登陆成功后,将用户信息写入session
// 用户信息写入session后,会将 ( session_id ) 存入 ( cookie ),以后每次请求都会携带cookie,cookie中有 ( JSESSIONID )
request.getSession().setAttribute("username", username);
return "服务端session保存成功";
}
}
(3) Base64URL
- jwt的header和payload ( 串型化 ) 的算法是 ( base64url )
- jwt作为一个令牌,有些场合可能会放到 URL( 比如
api.example.com/?token=xxx
)+ / =
在url中有特殊含义所以要被替换掉,换成下面三种,就是base64url算法=
=> 被省略+
=> 换成 -/
=> 换成 _
(4) cookie复习
- 定义:cookie是 ( 服务器 ) 保存在浏览器的 ( 一小段文本信息 )
- 大小:每个cookie的大小一般不能
- cookie主要作用
- 1.分辨两个请求是否来自于同意服务器
- 2.保存一些状态信息
- cookie包含的内容
- cookie的名字
- cookie的值,里面是 ( 真正的数字 )
- 到期时间
- 所属域名 - 默认是当前域名
- 生效的路径 - 莫扔是当前网址
浏览器可以设置不接受cookie,也可以设置不向服务器发送cookie
- 如何查看浏览器是否打开cookie?
window.navigator.cookieEnabled
- 如何返回当前网页的cookie?
document.cookie
- 共享cookie的条件?
- http回应 =====> cookie的生成【【set-cookie】】
- 服务器在浏览器保存cookie,要在 (
http回应
)(头信息
) 里放置 (set-cookie
) 字段 - 如果包含多个
set-cookie
字段则表示设置了多个cookie保存在浏览器中 - 除了cookie的值,还可以设置其他属性
- 服务器在浏览器保存cookie,要在 (
- http请求 =====> cookie的发送 【【cookie】】
- (
cookie字段
) 可以包含 (多个cookie
),用 (;
) 分割
- (
- 如何改变先前设置的cookie????????
- 必须同时满足4个条件,即必须四个都匹配,只要一个值不一样就会重新生成cookie
- key
- domain
- path
- secure
- 必须同时满足4个条件,即必须四个都匹配,只要一个值不一样就会重新生成cookie
- 服务器收到cookie时,有两点是不知道的
- cookie的各种属性,比如过期时间
- 哪个域名设置的cookie,一级域名还是二级域名
- cookie的属性
- Expires Max-Age
- Expires
- Expires 指定一个具体的 到期时间 ,UTC格式
- 如果 ( 不设置Expires ) 或者 ( Expires设置为null ) 则相当于 ( session ),即相当于只要关闭浏览器窗口session结束,cookie就会被删除
- 浏览器是根据 ( 本地时间 ) 决定cookie是否过期
- Max-Age
- 表示从现在开始,cookie存在的秒数
- 如果 Set-Cookie 没有指定 Expires 或者 Max-Age,则是 session-cookie
- Domain
- Domain属性指定浏览器发出 HTTP 请求时,哪些域名要附带这个 Cookie
- 如果没有指定该属性,浏览器会默认将其设为当前 URL 的一级域名
- 如果服务器在Set-Cookie字段指定的域名,不属于当前域名,浏览器会拒绝这个 Cookie
- Path
- Path属性指定浏览器发出 HTTP 请求时,哪些路径要附带这个 Cookie
- Secure // security 安全
- Secure属性指定浏览器只有在加密协议 HTTPS 下,才能将这个 Cookie 发送到服务器
- HttpOnly
- HttpOnly属性指定该 Cookie 无法通过
JavaScript
脚本拿到- 主要是
Document.cookie属性
XMLHttpRequest对象
Request API
- 都拿不到该属性
- HttpOnly属性指定该 Cookie 无法通过
(5) Calendar 和 Date
(1) 实例化
Date date = new Date();
Calendar calendar = Calendar.getInstance();
log.info("calendar{}", calendar.toString());
log.info("date{}", date.toString());
(2) 实例化特定的时间
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date date = dateFormat.parse("2020-02-02 02:20:20");
String format = dateFormat.format(date);
log.info("format{}", format);
String parse2 = dateFormat.format(new Date());
log.info("parse2{}", parse2);
Calendar instance = Calendar.getInstance();
instance.setTime(date);
Date time = instance.getTime();
log.info("time{}", time);
(6) ( jackson - ObjectMapper
) 实现 ( map ) 和 ( object ) 和 ( json ) 三者的相互转换
- 使用 jackson 来实现
- map object json
- new ObjectMapper()
writeValueAsString
=> 将各种类型转成jsonreadValue
=> 将json转成各种值convertValue
=> 各种值的相互转换
@SpringBootTest
@Slf4j
public class JwtTest {
@Test
public void object2map() throws JsonProcessingException {
JwtUserBean jwtUserBean = new JwtUserBean(1, "admin", "admin", "admin"); // bean
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
stringObjectHashMap.put("a", "a");
stringObjectHashMap.put("b", "b");
// object => json
ObjectMapper objectMapper = new ObjectMapper(); // jackson => ObjectMapper
String s = objectMapper.writeValueAsString(jwtUserBean); // writeValueAsString()
log.info("object => json: {}", s);
// map => json
ObjectMapper objectMapper1 = new ObjectMapper(); // jackson => ObjectMapper
String s1 = objectMapper1.writeValueAsString(stringObjectHashMap); // writeValueAsString()
log.info("map => json: {}", s1);
// json => object
ObjectMapper objectMapper3 = new ObjectMapper(); // jackson => ObjectMapper
JwtUserBean jwtUserBean1 = objectMapper3.readValue(s, JwtUserBean.class);
log.info("json => object: {}", jwtUserBean1);
// json => map
ObjectMapper objectMapper4 = new ObjectMapper(); // jackson => ObjectMapper
Map map = objectMapper4.readValue(s1, Map.class);
log.info("json => map: {}", map);
// object => map !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ( object和map的相互转化 )
ObjectMapper objectMapper5 = new ObjectMapper(); // jackson => ObjectMapper
Map map1 = objectMapper5.convertValue(jwtUserBean, Map.class);
log.info("object => map: {}", map);
}
}
(二) JWT
- JWT
- JWT是
json web tokens
的缩写 - 服务端不在保存session,即服务端变成无状态的了
- JWT是
(1) JWT的数据结构
- JWT表现上是一个很长的字符串,用 (
.
) 分成三段- header 头部
- payload 负载
- signature 签名
header.payload.signature
- header
- header是一个json对象
- header对象中有两个属性
alg
typ
- alg 表示签名算法 => 默认是
hs256
- typ 表示令牌的类型 => 一般都是
JWT
{ "alg": "HS256", "typ": "JWT" }
- payload
- payload也是一个json对象,用来存放实际需要传递的数据
- 注意:jwt默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分
- signature
-
signature是对前两部分的签名,防止数据串改
-
(2) JWT的存储
- 可以存储在 (
cookie
) 中,也可以存储在 (localStorage
) 中 - 发送请求时,最好是放在
header
头中,也可以放在cookie中但是不能跨域Authorization: Bearer <token>
(3) JWT在springboot中的使用
(3.1) 安装依赖
<!-- JWT -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.15.0</version>
</dependency>
(3.2) 生成jwt
@RestController
@Slf4j
public class JwtTest {
@GetMapping("/jwt-test")
public void testJwt() throws ParseException {
HashMap<String, Object> HeaderMap = new HashMap<>();
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND, 2000); // 2000s 过期
String token = JWT.create() // 生成jwt
.withHeader(HeaderMap) // header
.withClaim("username", "woow_wu7") // ------ payload
.withClaim("age", 20) // ------------------- payload
.withExpiresAt(instance.getTime()) // 过期时间
.sign(Algorithm.HMAC256("secretXx"));// ---------------- signature
log.info("token: {}", token);
}
(3.3) 验证jwt
根据 ( 令牌 ) 和 ( 签名 ) 解析数据
@RestController
@Slf4j
public class JwtTest {
@GetMapping("/jwt-test")
public void testJwt() throws ParseException {
// 1. JWT加密
HashMap<String, Object> HeaderMap = new HashMap<>();
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND, 2000); // 2000s 过期
String token = JWT.create() // 生成jwt
.withHeader(HeaderMap) // header
.withClaim("username", "woow_wu7") // ------ payload
.withClaim("age", 20) // ------------------- payload
.withExpiresAt(instance.getTime()) // 过期时间
.sign(Algorithm.HMAC256("secretXx"));// ---------------- signature 签名 (algorithm是算法的意思)
log.info("token: {}", token);
// 2. JWT验证
// secretXx 生成验证对象
// 注意:Algorithm.HMAC256() 的参数就是上面生成jwt时 ( 签名 ) 时传入的签名字符串
JWTVerifier secretXx = JWT.require(Algorithm.HMAC256("secretXx")).build();
DecodedJWT verify = secretXx.verify(token);
String username = verify.getClaim("username").asString(); // -- 通过token,获取username,注意过期时间,过期后是拿不到username的
Integer age = verify.getClaim("age").asInt(); // -------------- 通过token,获取age
log.info("传入token,验证token中的username:{}", username);
log.info("传入token,验证token中的age:{}", age);
Date expiresAt = verify.getExpiresAt(); // -------------------- 通过token,获取过期时间
log.info("token的过期时间:{}", expiresAt);
}
}
(3.4) JWT常见的异常信息
- SignatureVerificationException ------ 签名不一致异常
- TokenExpiredException --------------- token过期异常
- AlgorithmMismatchException ---------- 算法不匹配异常,algorithm是算法的意思
- InvalidClaimException --------------- 失效的payload异常
(3.5) JWT的封装
public class JwtUtil {
// private 表示只能自己访问,子类和其他类都不能访问
// static 静态类,表示可以通过类本身来访问
// final 表示非继承类,不能被其他类继承
private static final String SIGNATURE = "randomString!";
// JWT 生成
// token => header.payload.signature
public static String getToken(Map<String, Object> map) {
// 过期时间设置
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE, 7); // ------------ 过期时间,这里是天为单位,即7天后过期
JWTCreator.Builder builder = JWT.create(); // ----------------- jwt
map.forEach((k, v) -> {
builder.withClaim(String.valueOf(k), String.valueOf(v)); // jwt payload
});
String token = builder
.withExpiresAt(instance.getTime()) // ----------------- jwt 过期时间
.sign(Algorithm.HMAC256(SIGNATURE));// ---------------- jwt signature
return token;
}
// JWT 验证 + 获取信息
// 验证token的合法性,并获取token中的信息
public static DecodedJWT verify(String token) {
DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGNATURE)).build().verify(token);
return verify; // 返回 verify 则可以通过 verify.getClaim() 获取到 ( token ) 中存入的 ( payload )
}
}
(3.6) JWT在springboot中的使用
(1) 登陆成功返回token
@RestController
@Slf4j
public class JwtLoginTestController {
// 如果service是通过实现类实现的接口,这里还是注入interface
@Autowired
JwtLoginTestServiceInterface jwtLoginTestServiceInterface;
@GetMapping("/jwt-login")
public Object getJwtUser(
@RequestParam String username,
@RequestParam String password
) {
HashMap<Object, Object> stringObjectHashMap = new HashMap<>(); // ------------------- 结果map
Object jwtUser = jwtLoginTestServiceInterface.getJwtUser(username, password); // ---- object,查询数据库返回值
Map map = new ObjectMapper().convertValue(jwtUser, Map.class); //-------------------- object => map
String token = JwtUtil.getToken(map); // 生成token
try {
stringObjectHashMap.put("msg", "请求成功");
stringObjectHashMap.put("token", token);
} catch (Exception e) {
stringObjectHashMap.put("msg", "请求失败");
stringObjectHashMap.put("data", e.getMessage());
}
return stringObjectHashMap;
}
}
(2) 访问接口,做token验证,是否过期expire,捕捉异常exception等
@Test
private Map<String, Object> testToken2() {
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
try {
JwtUtil.verify("\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6ImFkbWluIiwicm9sZXMiOiJhZG1pbiIsImlkIjoiMSIsImV4cCI6MTYyMzU3MTU5MywidXNlcm5hbWUiOiJhZG1pbiJ9.JNME8wbaedF1EYyr9agbTs9pmTxkQ8Iwxh0WJB1Zwig\"");
// 验证成功
stringObjectHashMap.put("msg", "请求成功");
return stringObjectHashMap;
} catch (SignatureVerificationException e) { // --------- 签名错误
e.printStackTrace();
} catch (TokenExpiredException e) { // ------------------ 过期
e.printStackTrace();
} catch (AlgorithmMismatchException e) { // ------------- 算法不匹配
e.printStackTrace();
} catch (InvalidClaimException e) { // ------------------ 无效payload
e.printStackTrace();
}
stringObjectHashMap.put("msg", "请求失败");
return stringObjectHashMap;
}