当前位置:网站首页>一盏茶的功夫我学会了JWT单点登录
一盏茶的功夫我学会了JWT单点登录
2022-07-13 17:54:00 【js同学你好】
文章目录
什么是JWT
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
传统的session认证
http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来。
基于session认证所显露的问题
Session:
每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
扩展性:
用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
CSRF:
因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
基于token的鉴权机制
基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
流程上是这样的:
- 用户使用用户名密码来请求服务器
- 服务器进行验证用户的信息
- 服务器通过验证发送给用户一个token
- 客户端存储token,并在每次请求时附送上这个token值
- 服务端验证token值,并返回数据
这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *。
JWT长什么样?
JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
JWT的构成
第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).
header
jwt的头部承载两部分信息:
- 声明类型,这里是jwt
- 声明加密的算法 通常直接使用 HMAC SHA256
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.
playload
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分:
- 标准中注册的声明
- 公共的声明
- 私有的声明
标准中注册的声明 (建议但不强制使用) :
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
signature
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
- header (base64后的)
- payload (base64后的)
- secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret');
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
如何应用
一般是在请求头里加入Authorization,并加上Bearer标注:
fetch('api/user/1', {
headers: {
'Authorization': 'Bearer ' + token
}
})
整个验证流程如下:
优点
- 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
- 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
- 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
- 它不需要在服务端保存会话信息, 所以它易于应用的扩展
安全相关
- 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
- 保护好secret私钥,该私钥非常重要。
- 如果可以,请使用https协议
项目使用
项目导入依赖
<!-- JWT -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.2</version>
</dependency>
TokenUtils
/** * 这个类需要Spring托管 */
@Component
public class TokenUtils {
private static AdminService adminService;
@Autowired
public static void setAdminService(AdminService adminService) {
TokenUtils.adminService = adminService;
}
/** * * @param adminId adminID作为playLoad * @param sign 以password作为密钥 * 设置一个过期时间 * @return 返回String token */
public static String getToken(String adminId,String sign){
return JWT.create().withAudience(adminId)
.withExpiresAt(DateUtil.offsetHour(new Date(),2))
.sign(Algorithm.HMAC256(sign));
}
/** * 获取当前登录中的admin对象 * @return admin对象 */
public static Admin getCurrentAdmin(){
try {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
String token = request.getHeader("token");
if (StrUtil.isNotBlank(token)) {
//头部解码取出adminID
String adminId = JWT.decode(token).getAudience().get(0);
return adminService.getById(Integer.valueOf(adminId));
}
} catch (JWTDecodeException e) {
return null;
}
return null;
}
测试
String token = TokenUtils.getToken("1111", "123");
System.out.println("============================="+token);

拦截处理:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
// 没有映射到该方法就直接通过
if (!(handler instanceof HandlerMethod)){
return true;
}
if (StrUtil.isBlank(token)) {
throw new ServiceException(Constants.CODE_401,"无token请重新登录");
}
String adminId;
try {
adminId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException e) {
throw new ServiceException(Constants.CODE_401,"验证失败重新登录");
}
Admin admin = adminService.getById(adminId);
if (admin==null) {
throw new ServiceException(Constants.CODE_401,"用户不存在");
}
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(admin.getPassword())).build();
try {
verifier.verify(token);
} catch (JWTVerificationException e) {
throw new ServiceException(Constants.CODE_401,"验证失败重新登录");
}
return true;
}
在那个地方需要进行拦截:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor())
.addPathPatterns("/**") // 拦截所有请求,通过判断token是否合法来决定是否需要登录
.excludePathPatterns("/user/login", "/user/register", "/**/export", "/**/import");
}
@Bean
public JwtInterceptor jwtInterceptor() {
return new JwtInterceptor();
}
登录方法:
@RequestMapping("/login")
public Admin login(@RequestBody Admin admin){
Admin one = adminService.getAdmin(admin);
if (one!=null) {
String token = TokenUtils.getToken(one.getAccount().toString(), one.getPassword());
admin.setToken(token);
return admin;
}else {
throw new ServiceException(Constants.CODE_600,"账号密码错误");
}
}
每一次的请求都要带着这个token作为认证只有通过认证才能从后端请求数据
let admin = localStorage.getItem("admin") ? JSON.parse(localStorage.getItem("admin")) : null
config.headers['token'] = admin.token; // 设置请求头


边栏推荐
- Max3232ese problem record and solution
- [Multisim] problems and solutions of Multisim Simulation "zero crossing comparator"
- 工作方法小结
- Dynamic memory allocation principle of C language and the use of heap (malloc, calloc, realloc, free)
- Spark入门
- 02-FeatureScaling归一化
- 如何使用Keil5中的虚拟示波器进行软件仿真
- ##DHCP-MASTER自动化部署
- matlab的通用命令
- go语言websocket库Gorilla Websocket
猜你喜欢
![[hbuilderx development uniapp] automatic code formatting plug-in installation and configuration](/img/84/b32cd4e4a63a040a0edca961fe4d89.png)
[hbuilderx development uniapp] automatic code formatting plug-in installation and configuration

vant Weapp组件库中 自定义修改van-button 按钮宽高大小

梯度下降法的向量化

The use of gy-53 infrared laser ranging module and the realization of PWM mode code

如何使用Keil5中的虛擬示波器進行軟件仿真

嵌入式软件开发 STM32F407 按键输入 标准库版
![[matlab] matlab lesson 3 - advanced drawing](/img/f8/aec64319d44d54bf4fef26939bebf9.png)
[matlab] matlab lesson 3 - advanced drawing
![[stm32f1] drive DHT11 (cubemx configuration) (HAL Library)](/img/c8/b6ab4f00c2f28f2cfa0be8a7898805.png)
[stm32f1] drive DHT11 (cubemx configuration) (HAL Library)

C语言实现汉诺塔(程序执行步骤详解)

树莓派系统镜像的下载和烧录
随机推荐
Download and installation of CodeBlocks on the official website
MPU6050的一些使用方法汇总
Supervisor series: 4. Sub process
Using idea IntelliJ to view bytecode files
[PCB] some experience about video game hardware design and PCB drawing (continuously updated)
【信号调理】精密检波电路和PCB示例
【STM32F1】驱动DHT11(CubeMX配置)(HAL库)
Chapter 4 stm32+ld3320+syn6288+dht11 realize voice acquisition of temperature and humidity values (Part 1)
Virtual memory location structure (reserved area, code area, stack area, heap area, literal constant area) and variable modifiers (const, auto, static, register, volatile, extern)
[Go语言入门] 14 Go语言goroutine和通道详解
vscode插件安装介绍
ES6 let 、const 详解
Use MessageBox to realize window confession applet (with source code)
Blue Bridge Cup embedded Hal library Tim_ BASE
如何将电子签名透明化处理
3.6 formatting numbers and strings
Chapter II use of syn6288 speech synthesis module
Summary of working methods
[Multisim] problems that must be paid attention to when using ne5532p series operational amplifier simulation
[Multisim] problems and solutions of Multisim Simulation "zero crossing comparator"