网站首页 > java教程 正文
一、背景引入
由于Http协议本身是无状态的,那么服务器是怎么识别两次请求是不是来自同一个客户端呢,传统用户识别是基于seeion和cookie实现的。大致流程如下:
- 用户向服务器发送用户名和密码请求用户进行校验,校验通过后创建session绘画,并将用户相关信息保存到session中服务器将sessionId回写到用户浏览器cookie中用户以后的请求,都会鞋带cookie发送到服务器服务器得到cookie中的sessionId,从session集合中找到该用户的session回话,识别用户
这种模式有很多缺点,对于分布式架构的支持以及扩展性不是很好。而且session是保存在内存中,单台服务器部署如果登陆用户过多占用服务器资源也多,做集群必须得实现session共享的话,集群数量又不易太多,否则服务器之间频繁同步session也会非常耗性能。当然也可以引入持久层,将session保存在数据库或者redis中,保存数据库的话效率不高,存redis效率高,但是对redis依赖太重,如果redis挂了,影响整个应用。还有一种办法就是不存服务器,而是把用户标识数据存在浏览器,浏览器每次请求都携带该数据,服务器做校验,这也是JWT的思想。
二、JWT介绍
2.1 概念介绍
Json Web Token(JWT)是目前比较流行的跨域认证解决方案,是一种基于JSON的开发标准,由于数据是可以经过签名加密的,比较安全可靠,一般用于前端和服务器之间传递信息,也可以用在移动端和后台传递认证信息。
2.2 组成结构
JWT就是一段字符串,格式如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxIn0.qfd0GelhE1aGr15LrnYlIZ_3UToaOM5HeMcXrmDG
由于三部分组成,之间用"."接。第一部分是头信息Header,中间部分是载荷Payload,最后部分是签名信息Signature。
头信息Header:描述JWT基本信息,typ表示采用JWT令牌,alg(algorithm)表示采用什么算法进行签名,常见算法有HmacSHA256(HS256)、HmacSHA384(HS384)、HmacSHA512(HS512)、SHA256withECDSA(ES256)、SHA256withRSA(RS256)、SHA512withRSA(RS512)等。如果采用HS256则头信息结构为:
{ "typ": "JWT", "alg": "HS256 }
载荷Payload:载荷(也可以叫载体)是具体的传输内容,包括一些标准属性,iss: 该JWT的签发者,exp: 过期时间戳,iat: 签发时间戳,jti: JWTID等等。也可以添加其他需要传递的内容数据。结构为:
{ "iss": "kkk", "iat": 1548818203, "exp": 1548818212, "sub": "test.com }
签名Signature:对头信息和载荷进行签名,保证传输过程中信息不被篡改,比如:将头信息和载荷分别进行base64加密得到字符串a和b,将字符串a和b以点相连并签名得到字符串c,将字符串a、b、c以点相连得到最终token。
2.3 验证流程
使用JWT的验证流程为:
- 用户提交用户名,密码到服务器后台后台验证通过,服务器端生成Token字符串,返回到客户端客户端保存Token,下一次请求资源时,附带上Token信息服务器端验证Token是否由服务器签发的(一般在拦截器中验证),若Token验证通过,则返回需要的资源
验证流程和基于session大体相同,只不过不是基于session,而是采用拦截器在代码中实验验证,返回给客户端的也不是sessionid,而是经过一定算法得出来的token字符串。
2.4 源码分析
Java中有封装好的开源哭JWT可以直接使用,下面就分析下关键代码验证以下内容。
Header头信息结构分析关键源码如下:
//token生成方法 public static void main(String[] args) { String token= JWT.create().withAudience("audience") .withIssuedAt(new Date()) .withSubject("subject") .withExpiresAt(new Date()).withJWTId("jtiid") .sign(Algorithm.HMAC256(user.getPassword())); } public abstract class Algorithm { private final String name; private final String description; //...其他方法省略... public static Algorithm HMAC256(String secret) throws IllegalArgumentException { return new HMACAlgorithm("HS256", "HmacSHA256", secret); } //...其他方法省略... } class HMACAlgorithm extends Algorithm { private final CryptoHelper crypto; private final byte[] secret; //...其他方法省略... HMACAlgorithm(String id, String algorithm, byte[] secretBytes) throws IllegalArgumentException { this(new CryptoHelper(), id, algorithm, secretBytes); } //...其他方法省略.. } public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCreationException { if (algorithm == null) { throw new IllegalArgumentException("The Algorithm cannot be null."); } else { this.headerClaims.put("alg", algorithm.getName()); this.headerClaims.put("typ", "JWT"); String signingKeyId = algorithm.getSigningKeyId(); if (signingKeyId != null) { this.withKeyId(signingKeyId); } public final class JWTCreator { private final Algorithm algorithm; private final String headerJson; private final String payloadJson; private JWTCreator(Algorithm algorithm, Map<String, Object> headerClaims, Map<String, Object> payloadClaims) throws JWTCreationException { this.algorithm = algorithm; try { ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); module.addSerializer(ClaimsHolder.class, new PayloadSerializer()); mapper.registerModule(module); mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); this.headerJson = mapper.writeValueAsString(headerClaims); this.payloadJson = mapper.writeValueAsString(new ClaimsHolder(payloadClaims)); } catch (JsonProcessingException var6) { throw new JWTCreationException( "Some of the Claims couldn't be converted to a valid JSON format.", var6); } } //...其他方法省略...
headerClaims是一个Map,包括两个属性typ和alg,typ值固定JWT,alg传过来的签名算法这里使用的
HmacSHA256简称HS256。typ和alg组成Header头信息。
Payload载荷结构分析关键源码如下:
public abstract class JWT { public JWT() { } public static DecodedJWT decode(String token) throws JWTDecodeException { return new JWTDecoder(token); } public static Verification require(Algorithm algorithm) { return JWTVerifier.init(algorithm); } public static Builder create() { return JWTCreator.init(); } } public static class Builder { private final Map<String, Object> payloadClaims = new HashMap(); private Map<String, Object> headerClaims = new HashMap(); Builder() { } public JWTCreator.Builder withHeader(Map<String, Object> headerClaims) { this.headerClaims = new HashMap(headerClaims); return this; } public JWTCreator.Builder withKeyId(String keyId) { this.headerClaims.put("kid", keyId); return this; } public JWTCreator.Builder withIssuer(String issuer) { this.addClaim("iss", issuer);//签发人 return this; } public JWTCreator.Builder withSubject(String subject) { this.addClaim("sub", subject);//主题 return this; } public JWTCreator.Builder withAudience(String... audience) { this.addClaim("aud", audience);//接受一方 return this; } public JWTCreator.Builder withExpiresAt(Date expiresAt) { this.addClaim("exp", expiresAt);//过期时间 return this; } public JWTCreator.Builder withNotBefore(Date notBefore) { this.addClaim("nbf", notBefore);//生效时间 return this; } public JWTCreator.Builder withIssuedAt(Date issuedAt) { this.addClaim("iat", issuedAt);//签发时间 return this; } public JWTCreator.Builder withJWTId(String jwtId) { this.addClaim("jti", jwtId);//编号 return this; } public JWTCreator.Builder withClaim(String name, Boolean value) throws IllegalArgumentException { this.assertNonNull(name); this.addClaim(name, value); return this; } public JWTCreator.Builder withClaim(String name, Integer value) throws IllegalArgumentException { this.assertNonNull(name); this.addClaim(name, value); return this; } //...其他方法省略... }
Payload是一个json对象,存放需要传递的数据,JTW默认规定了几个属性,如果需要添加其他属性可以调用其重载方法witchClaim()添加。
Signature签名部分源码如下:
private String sign() throws SignatureGenerationException { String header = Base64.encodeBase64URLSafeString( this.headerJson.getBytes(StandardCharsets.UTF_8)); String payload = Base64.encodeBase64URLSafeString( this.payloadJson.getBytes(StandardCharsets.UTF_8)); String content = String.format("%s.%s", header, payload); byte[] signatureBytes = this.algorithm.sign( content.getBytes(StandardCharsets.UTF_8)); String signature = Base64.encodeBase64URLSafeString(signatureBytes); return String.format("%s.%s", content, signature); }
从这里可以看出,所谓token就是分别对header和payload的json字符串做Base64加密得到a和b,并将结果拼接一起,在进行签名得到c,最终把a、b、c三部分内容以点拼接起来形成token,返回客户端保存,客户端以后每次请求都在header中加入token,服务器采用拦截器方式获取header中的token做校验,识别用户。
三、示例
3.1 数据准备
创建用户表
3.2 搭建springboot工程
设置工程Group、Artifact、Version、Name等信息
Spring Boot的版本选择2.0.8,选择导入web的起步器
创建工程成功之后,将各个包创建出来,工程目录结构如下:
3.3 引入pom依赖
3.4 编写application.yml配置文件
3.5 编写User实体类
Result类:用于统一返回消息的封装
TokenUtil类,用于生成token
VerifyToken注解类:加到controller方法上表示该方法需要验证token。
3.6 编写mapper接口和service层
mapper类:
UserService接口:
UserServiceImpl实现类:
3.7 编写拦截器和全局异常处理器
AuthInterceptor拦截器类:用于token验证。
全局异常处理器GloabllExceptionHandler:用于异常的捕获。
3.8 编写配置类及controller
拦截器配置类InterceptorConfig:配置拦截所有请求
UserController类:
3.9 测试
测试1:使用postman发送get请求http://localhost:8088/user/getUser?id=1
测试2:发送post请求http://localhost:8088/user/login 密码故意输错
测试3:发送post请求http://localhost:8088/user/login 填正确的用户名密码
猜你喜欢
- 2024-09-27 Java日期时间工具类(java 日期类)
- 2024-09-27 JAVA中计算两个日期时间的差值竟然也有这么多门道
- 2024-09-27 一个MySQL时间戳精度引发的血案(mysql时间戳字段)
- 2024-09-27 前端大佬问我MySQL怎么查询最近10分钟的数据?我是这么回答他的
- 2024-09-27 如何将ABAP时间戳转换为JAVA时间戳,原理是什么呢?
- 2024-09-27 JAVA8时间工具类(java8时间工具类有哪些)
- 2024-09-27 Java中有哪些可以用于日期和时间的API?
- 2024-09-27 java的时间戳的长度为什么是固定的?它是如何做到的
- 2024-09-27 Java中关于时间的那些处理方法(java中时间的数据类型)
你 发表评论:
欢迎- 最近发表
-
- Java常量定义防暴指南:从"杀马特"到"高富帅"的华丽转身
- Java接口设计原则与实践:优雅编程的艺术
- java 包管理、访问修饰符、static/final关键字
- Java工程师的代码规范与最佳实践:优雅代码的艺术
- 编写一个java程序(编写一个Java程序计算并输出1到n的阶乘)
- Mycat的搭建以及配置与启动(mycat部署)
- Weblogic 安装 -“不是有效的 JDK Java 主目录”解决办法
- SpringBoot打包部署解析:jar包的生成和结构
- 《Servlet》第05节:创建第一个Servlet程序(HelloSevlet)
- 你认为最简单的单例模式,东西还挺多
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)