SpringSecurity 整合 JWT

server/2024/10/22 8:22:47/
前言

前后端分离项目中,如果直接把 API 接口对外开放,我们知道这样风险是很大的,所以引入了 Spring Security ,但是我们在登陆后缺少了请求凭证部分。

什么是JWT?

JWT是 Json Web Token 的缩写。它是基于 RFC 7519 标准定义的一种可以安全传输的 小巧 和 自包含 的JSON对象。由于数据是使用数字签名的,所以是可信任的和安全的。JWT可以使用HMAC算法对secret进行加密或者使用RSA的公钥私钥对来进行签名。

JWT的工作流程

1、用户进入登录页,输入用户名、密码,进行登录;
2、服务器验证登录鉴权,如果改用户合法,根据用户的信息和服务器的规则生成 JWT Token
3、服务器将该 token 以 json 形式返回(不一定要json形式,这里说的是一种常见的做法)
4、用户得到 token,存在 localStorage、cookie 或其它数据存储形式中。以后用户请求 /protected 中的 API 时,在请求的 header 中加入 Authorization: Bearer xxxx(token)。此处注意token之前有一个7字符长度的 Bearer。
5、服务器端对此 token 进行检验,如果合法就解析其中内容,根据其拥有的权限和自己的业务逻辑给出对应的响应结果。
6、用户取得结果

如下如所示:

7790cc3aade467c985e2e4a8105b89f1.png

来看一下 JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

token 分成了三部分,头部(header),荷载(Payload) 和 签名(Signature),每部分用 . 分隔,其中头部和荷载使用了base64编码,分别解码之后得到两个JSON串:

第一部分-头部:

{"alg": "HS256","typ": "JWT"
}

alg字段为加密算法,这是告诉我们 HMAC 采用 HS512 算法对 JWT 进行的签名。

第二部分-荷载:

{"sub": "1234567890","name": "John Doe","iat": 1516239022
}

荷载的字段及含义:

  • iss: 该JWT的签发者
  • sub: 该JWT所面向的用户
  • aud: 接收该JWT的一方
  • exp(expires): 什么时候过期,这里是一个Unix时间戳
  • iat(issued at): 在什么时候签发的

这段告诉我们这个Token中含有的数据声明(Claim),这个例子里面有三个声明:sub, name 和 iat。在我们这个例子中,分别代表着
所面向的用户、用户名、创建时间,当然你可以把任意数据声明在这里。

第三部分-签名:

第三部分签名则不能使用base64解码出来,该部分用于验证头部和荷载数据的完整性。

JWT的生成和解析

引入依赖:

<!-- JWT -->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>

创建一个测试类尝试一下 JWT 的生成:

public class Test {public static void main(String[] args){String token = Jwts.builder()主题 放入用户名.setSubject("niceyoo")自定义属性 放入用户拥有请求权限.claim("authorities","admin")失效时间.setExpiration(new Date(System.currentTimeMillis() + 7 * 60 * 1000))签名算法和密钥.signWith(SignatureAlgorithm.HS512, "tmax").compact();System.out.println(token);}}

控制台打印如下:

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJuaWNleW9vIiwiYXV0aG9yaXRpZXMiOiJhZG1pbiIsImV4cCI6MTU1OTQ1ODM1M30.keCiHrcEr0IWXfZLocgHS8znn7uSiaZW1IT6bTs-EQG0NPsb6-Aw_XbGQea4mez2CcAflgMqtzIpsDjZsUOVug

数据声明(Claim)是一个自定义属性,可以用来放入用户拥有请求权限。上边为简单直接传了一个 ‘admin’。

再看看解析:

public static void main(String[] args){try {解析tokenClaims claims = Jwts.parser().setSigningKey("tmax").parseClaimsJws("eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJuaWNleW9vIiwiYXV0aG9yaXRpZXMiOiJhZG1pbiIsImV4cCI6MTU1OTQ1OTc2Mn0.MkSJtGaVePLa-eM3gylh1T3fwODg-6ceDDOxscXAQKun-qNrbQFcKPNqXhblbXPNLhaJyEnwugNANCTs98UNmA").getBody();System.out.println(claims);获取用户名String username = claims.getSubject();System.out.println("username:"+username);获取权限String authority = claims.get("authorities").toString();System.out.println("权限:"+authority);} catch (ExpiredJwtException e) {System.out.println("jwt异常");} catch (Exception e){System.out.println("异常");}
}

控制台打印:

{sub=niceyoo, authorities=admin, exp=1559459762}
username:niceyoo
权限:admin

JWT 本身没啥难度,但安全整体是一个比较复杂的事情,JWT 只不过提供了一种基于 token 的请求验证机制。但我们的用户权限,对于 API 的权限划分、资源的权限划分,用户的验证等等都不是JWT负责的。也就是说,请求验证后,你是否有权限看对应的内容是由你的用户角色决定的。所接下来才是我们的重点,Spring Security 整合 JWT。

集成JWT

要想要 JW T在 Spring 中工作,我们应该新建一个 JWT filter,并把它配置在 WebSecurityConfig 中。

WebSecurityConfigurerAdapter.java

java">@Slf4j
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsServiceImpl userDetailsService;@Autowiredprivate AuthenticationSuccessHandler successHandler;@Autowiredprivate AuthenticationFailHandler failHandler;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());加密}@Overrideprotected void configure(HttpSecurity http) throws Exception {ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();registry.and()表单登录方式.formLogin().permitAll()成功处理类.successHandler(successHandler)失败.failureHandler(failHandler).and().logout().permitAll().and().authorizeRequests()任何请求.anyRequest()需要身份认证.authenticated().and()关闭跨站请求防护.csrf().disable()前后端分离采用JWT 不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()添加JWT过滤器 除已配置的其它请求都需经过此过滤器.addFilter(new JWTAuthenticationFilter(authenticationManager(), 7));}
}

JWTAuthenticationFilter.java

java">@Slf4j
public class JWTAuthenticationFilter extends BasicAuthenticationFilter   {private Integer tokenExpireTime;public JWTAuthenticationFilter(AuthenticationManager authenticationManager, Integer tokenExpireTime) {super(authenticationManager);this.tokenExpireTime = tokenExpireTime;}public JWTAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {super(authenticationManager, authenticationEntryPoint);}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {String header = request.getHeader(SecurityConstant.HEADER);if(StrUtil.isBlank(header)){header = request.getParameter(SecurityConstant.HEADER);}Boolean notValid = StrUtil.isBlank(header) || (!header.startsWith(SecurityConstant.TOKEN_SPLIT));if (notValid) {chain.doFilter(request, response);return;}try {//UsernamePasswordAuthenticationToken 继承 AbstractAuthenticationToken 实现 Authentication//所以当在页面中输入用户名和密码之后首先会进入到 UsernamePasswordAuthenticationToken验证(Authentication),UsernamePasswordAuthenticationToken authentication = getAuthentication(header, response);SecurityContextHolder.getContext().setAuthentication(authentication);}catch (Exception e){e.toString();}chain.doFilter(request, response);}private UsernamePasswordAuthenticationToken getAuthentication(String header, HttpServletResponse response) {//用户名String username = null;//权限List<GrantedAuthority> authorities = new ArrayList<>();try {//解析tokenClaims claims = Jwts.parser().setSigningKey(SecurityConstant.JWT_SIGN_KEY).parseClaimsJws(header.replace(SecurityConstant.TOKEN_SPLIT, "")).getBody();logger.info("claims:"+claims);//获取用户名username = claims.getSubject();logger.info("username:"+username);//获取权限String authority = claims.get(SecurityConstant.AUTHORITIES).toString();logger.info("authority:"+authority);if(!StringUtils.isEmpty(authority)){authorities.add(new SimpleGrantedAuthority(authority));}} catch (ExpiredJwtException e) {ResponseUtil.out(response, ResponseUtil.resultMap(false,401,"登录已失效,请重新登录"));} catch (Exception e){log.error(e.toString());ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"解析token错误"));}if(StrUtil.isNotBlank(username)) {//踩坑提醒 此处password不能为nullUser principal = new User(username, "", authorities);return new UsernamePasswordAuthenticationToken(principal, null, authorities);}return null;}
}

接下来我们启动项目看看:

访问项目中已有的链接:

http://localhost:7777/tmax/videoCategory/getAll

认证一波:
img

其中 niceyoo、 为数据库用户信息

登陆成功后获取返回的 token,注意,此 token 是由 JWT 生成的:

javascript"> String token = SecurityConstant.TOKEN_SPLIT + Jwts.builder()主题 放入用户名.setSubject(username)自定义属性 放入用户拥有请求权限.claim(SecurityConstant.AUTHORITIES, authorities)失效时间.setExpiration(new Date(System.currentTimeMillis() + 7 * 60 * 1000))签名算法和密钥.signWith(SignatureAlgorithm.HS512, SecurityConstant.JWT_SIGN_KEY).compact();

浏览器返回 token 如下:

ad45accf0b31c606a10c568acbddec19.png

然后我们通过 token 凭证去访问上边的方法:

e3c05b207e1ec11e1f23560ef1b724b6.png

后台打印信息:

claims:{sub=niceyoo, authorities=admin, exp=1559472866}
username:niceyoo
authority:admin

随便改一下 token ,返回如下:

172cbf88bbb16a6679931885c2bdd2c4.png

转载:https://www.cnblogs.com/niceyoo/p/10964277.html


http://www.ppmy.cn/server/133845.html

相关文章

衡石分析平台系统分析人员手册-仪表盘控件概述

控件​ 控件是仪表盘的基本组成单位。控件种类很多&#xff0c;有展示分析数据的图表类类控件&#xff0c;有展示图片、文字的展示类控件&#xff0c;还有可导出数据、刷新数据、过滤数据等功能类控件。一个完整的仪表盘由多种不同功能的控件构成。 控件类型​ 根据控件是否展…

数据结构与算法--返回袋子数

去商店买苹果&#xff0c;商店只提供两种类型的袋子&#xff0c;只能装下6个苹果的袋子和只能装下8个苹果的袋子。买的苹果&#xff0c;必须用袋子装满&#xff0c;如果装不满&#xff0c;则不买。 给定一个正整数&#xff0c;返回至少使用多少个袋子。 public class Code_Appl…

MySQL-30.索引-介绍

一.索引 为什么需要索引&#xff1f;当我们没有建立索引时&#xff0c;要在一张数据量极其庞大的表中查询表里的某一个值&#xff0c;会非常的消耗时间。以一个6000000数据量的表为例&#xff0c;查询一条记录的时间耗时约为13s&#xff0c;这是因为要查询符合某个值的数据&am…

【JavaScript】Javascript基础Day01:let/const变量、数据类型、ES6模板字符串

Javascript——Day01 01. Javascript简介和体验02. Javascript书写位置03. Javascript注释和结束符04. Js输入和输出语句和字面量05. 变量的声明和赋值06. 变量的更新以及输入用户名案例07. 交换两个变量案例08. 变量的本质和命名规则09. var和let区别10. 数组的基本使用11. 常…

3. IoC 与DI

一、 定义 IoC&#xff0c;即控制反转&#xff0c;把对象的调用权交给容器&#xff0c;通过容器来实现对象的装配和管理。DI&#xff0c;即依赖注入&#xff0c;对象之间依赖关系由容器在运行期决定&#xff0c;由容器动态的将依赖关系注入到对象之中。DI&#xff0c;是对IoC更…

鸿蒙开发超好用的 UI 组件和工具类库 BasicLibrary

大家好&#xff0c;我是 V 哥。你在学习HarmonyOS NEXT 开发吗&#xff0c;今天 V 哥给你推荐一款超好用的三方库BasicLibrary&#xff0c;BasicLibrary 是一个基于 API 11 封装的基本库&#xff0c;旨在提升鸿蒙开发效率。它包含了一些常用的 UI 组件和实用工具类&#xff0c;…

Qt 实战(11)样式表 | 11.1、样式表简介

文章目录 一、样式表简介1、简介2、样式表语法2.1、样式规则2.2、选择器类型2.3、伪状态2.4、设置子控件状态 3、样式表继承与优先级3.1、样式表继承3.2、样式表优先级3.3、解决冲突3.4、样式表层叠 4、总结 前言&#xff1a; 在开发图形用户界面&#xff08;GUI&#xff09;应…

React 中级阶段学习计划

React 中级阶段学习计划 目标 掌握状态管理和路由。能够调用API并处理异步数据。学会使用CSS-in-JS和CSS Modules进行样式处理。 学习内容 状态管理 React Context API Context API&#xff1a;用于在组件树中传递数据&#xff0c;避免多层props传递。示例&#xff1a;im…