JSD-2204-Session-Token-JWT-Day12
1.开发自定义的登录流程
目前,在passport
项目中,登录是由Security框架提供的页面的表单来输入用户名、密码,且由Security框架自动处理登录流程,不适合前后端分离的开发模式!所以,需要自行开发登录流程!
关于自定义的登录流程,主要需要:
- 在业务逻辑实现类中,调用Security的验证机制来执行登录认证
- 在控制器类中,自定义处理请求,用于接收登录请求及请求参数,并调用业务逻辑实现类来实现认证
1.1关于在Service中调用Security的认证机制:
当需要调用Security框架的认证机制时,需要使用AuthenticationManager
对象,可以在Security配置类中重写authenticationManager()
方法,在此方法上添加@Bean
注解,由于当前类本身是配置类,所以Spring框架会自动调用此方法,并将返回的结果保存到Spring容器中:
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {return super.authenticationManager();
}
在IAdminService
中添加处理登录的抽象方法:
void login(AdminLoginDTO adminLoginDTO);
在AdminServiceImpl
中,可以自动装配AuthenticationManager
对象:
@Autowired
private AuthenticationManager authenticationManager;
并实现接口中的方法:
@Override
public void login(AdminLoginDTO adminLoginDTO) {// 日志log.debug("开始处理【管理员登录】的业务,参数:{}", adminLoginDTO);// 调用AuthenticationManager执行认证Authentication authentication = new UsernamePasswordAuthenticationToken(adminLoginDTO.getUsername(), adminLoginDTO.getPassword());authenticationManager.authenticate(authentication);log.debug("认证通过!");
}
1.2在控制器中接收登录请求,并调用Service:
在根包下创建pojo.dto.AdminLoginDTO
类:
@Data
public class AdminLoginDTO implements Serializable {private String username;private String password;
}
在AdminController
中添加处理请求的方法:
@ApiOperation("管理员登录")
@ApiOperationSupport(order = 50)
@PostMapping("/login")
public JsonResult<Void> login(AdminLoginDTO adminLoginDTO) {log.debug("准备处理【管理员登录】的请求:{}", adminLoginDTO);adminService.login(adminLoginDTO);return JsonResult.ok();
}
为了保证能对以上路径直接发起请求,需要将此路径(/admins/login
)添加到Security配置类的“白名单”中。
完成后,启动项目,可以通过Knife4j的调试来测试登录,当登录成功时将响应正确,当用户名或密码错误时,将响应错误(需要统一处理异常)。
1.3注意:即使登录成功,也不可以实现其它请求的访问!
2.关于Session
HTTP协议本身是无状态协议,所以,无法识别用户的身份!
为了解决此问题,经编程时,引入了Session机制,用于保存用户的某些信息,可识别用户的身份!
Session的本身是在服务器端的内存中一个类似Map结构的数据,每个客户端在提交请求时,都会携带一个由服务器端首次响应时分配的Session ID,作为Map的Key,由于此Session ID具有极强的唯一性,所以,每个客户端的Session ID理论上都是不相同的,从而服务器可以识别客户端!
由于Session是保存在服务器端的内存中的,在一般使用时,并不适用于集群!
3.Token
Token:令牌,票据。
目前,推荐使用Token来保存用户的身份标识,使之可以用于集群!
相比Session ID是没有信息含义的,Token则是有信息含义的数据,当客户端向服务器端提交登录请求后,服务器商认证通过就会将此用户的信息保存在Token中,并将此Token响应到客户端,后续,客户端在每次请求时携带Token,服务器端即可识别用户的身份!
4.JWT
JWT = JSON Web Token
JWT是使用JSON格式表示一系列的数据的Token。
当需要使用JWT时,应该在项目中添加依赖:
<!-- JJWT(Java JWT) -->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
然后,通过测试,实现生成JWT和解析JWT。
@Slf4j
public class JwtTests {// 密钥(盐)String secretKey = "nmlfdasfdsaurefuifdknjfdskjhajhef";// 测试生成JWT@Testpublic void testGenerateJwt() {// 准备ClaimsMap<String, Object> claims = new HashMap<>();claims.put("id", 9527);claims.put("name", "liulaoshi");// JWT的组成部分:Header(头),Payload(载荷),Signature(签名)String jwt = Jwts.builder()// Header:用于声明算法与此数据的类型,以下配置的属性名是固定的.setHeaderParam("alg", "HS256").setHeaderParam("typ", "jwt")// Payload:用于添加自定义数据,并声明有效期.setClaims(claims).setExpiration(new Date(System.currentTimeMillis() + 3 * 60 * 1000))// Signature:用于指定算法与密钥(盐).signWith(SignatureAlgorithm.HS256, secretKey).compact();log.debug("JWT = {}", jwt);// eyJhbGciOiJIUzI1NiIsInR5cCI6Imp3dCJ9// .// eyJuYW1lIjoibGl1bGFvc2hpIiwiaWQiOjk1MjcsImV4cCI6MTY1OTkzMTUyMX0// .// TFyWBZ3l-y6rYbEYiVBbQjqnFNsFFR07K8lDES9TPs4// eyJhbGciOiJIUzI1NiIsInR5cCI6Imp3dCJ9.eyJuYW1lIjoibGl1bGFvc2hpIiwiaWQiOjk1MjcsImV4cCI6MTY1OTkzOTM0N30.7rj8Lhus1EYXUxE4Zy1wx1WFpbvxIQEmya3-A9WZP20// eyJhbGciOiJIUzI1NiIsInR5cCI6Imp3dCJ9.eyJuYW1lIjoibGl1bGFvc2hpIiwiaWQiOjk1MjcsImV4cCI6MTY1OTkzOTUzMH0.lwD_PzrqGXEgQs3KmMjsYzTmhsKbGhKnd1WkDkFpj5M}@Testpublic void testParseJwt() {String jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6Imp3dCJ9.eyJuYW1lIjoibGl1bGFvc2hpIiwiaWQiOjk1MjcsImV4cCI6MTY1OTkzOTUzMH0.lwD_PzrqGXEgQs3KmMjsYzTmhsKbGhKnd1WkDkFpj5M";Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();Object id = claims.get("id");Object name = claims.get("name");log.debug("id={}", id);log.debug("name={}", name);}}
如果JWT数据已经过期,将出现错误:
io.jsonwebtoken.ExpiredJwtException: JWT expired at 2022-08-08T12:05:21Z. Current time: 2022-08-08T14:11:34Z, a difference of 7573854 milliseconds. Allowed clock skew: 0 milliseconds.
如果JWT签名有误(JWT数据的最后一段出错,或生成与解析时使用的secretKey不同),将出现错误:
io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
如果JWT数据格式有误,将出现错误:
io.jsonwebtoken.MalformedJwtException: Unable to read JSON value: {"alg|b:"HS256","typ":"jwt"}
4.1关于JWT在项目中的应用
4.1.1生成JWT
应该在用户登录时,视为”认证成功“后,生成JWT,并将此数据响应到客户端。
在业务层,调用AuthenticationManager
的authenticate()
方法后,得到的返回结果例如:
UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=root, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[权限列表]], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[权限列表]
]
可以看到,认证返回的数据中将包含成功认证的用户信息,也是当初用于执行认证的信息(UserDetailsServiceImpl
中返回的结果),可以从此认证结果中获取用户相关数据,并写入到JWT中,则需要:
- 将业务接口中的登录方法返回值类型改为
String
,表示认证成功后返回的JWT - 将业务实现类中的登录方法返回值一并修改
- 在业务实现类中,当认证成功后,获取需要写入到JWT中的数据(例如:用户名等),并生成JWT,返回JWT
关于业务实现类的登录方法:
@Override
public String login(AdminLoginDTO adminLoginDTO) {// 日志log.debug("开始处理【管理员登录】的业务,参数:{}", adminLoginDTO);// 调用AuthenticationManager执行认证Authentication authentication = new UsernamePasswordAuthenticationToken(adminLoginDTO.getUsername(), adminLoginDTO.getPassword());Authentication authenticateResult = authenticationManager.authenticate(authentication);log.debug("认证通过,返回的结果:{}", authenticateResult);log.debug("认证结果中的Principal的类型:{}",authenticateResult.getPrincipal().getClass().getName());// 处理认证结果User loginUser = (User) authenticateResult.getPrincipal();log.debug("认证结果中的用户名:{}", loginUser.getUsername());// 生成JWTString secretKey = "nmlfdasfdsaurefuifdknjfdskjhajhef";// 准备ClaimsMap<String, Object> claims = new HashMap<>();claims.put("username", loginUser.getUsername());// JWT的组成部分:Header(头),Payload(载荷),Signature(签名)String jwt = Jwts.builder()// Header:用于声明算法与此数据的类型,以下配置的属性名是固定的.setHeaderParam("alg", "HS256").setHeaderParam("typ", "jwt")// Payload:用于添加自定义数据,并声明有效期.setClaims(claims).setExpiration(new Date(System.currentTimeMillis() + 14 * 24 * 60 * 60 * 1000))// Signature:用于指定算法与密钥(盐).signWith(SignatureAlgorithm.HS256, secretKey).compact();log.debug("生成的JWT:{}", jwt);return jwt;
}
在控制器中,将处理登录请求的方法的返回值类型改为JsonResult<String>
,并在调用业务方法时获取返回值,封装到返回的对象中:
@ApiOperation("管理员登录")
@ApiOperationSupport(order = 50)
@PostMapping("/login")
public JsonResult<String> login(AdminLoginDTO adminLoginDTO) {log.debug("准备处理【管理员登录】的请求:{}", adminLoginDTO);String jwt = adminService.login(adminLoginDTO);return JsonResult.ok(jwt);
}
完成后,重启项目,在Knife4j的调试功能中,使用正常的用户名和密码发起登录请求,将响应JWT结果,例如:
{"state": 20000,"data": "eyJhbGciOiJIUzI1NiIsInR5cCI6Imp3dCJ9.eyJleHAiOjE2NjExNTIzOTUsInVzZXJuYW1lIjoic3VwZXJfYWRtaW4ifQ.rFACBsBY8w8oNpR80n2YiplsEUIqw5bnCIsC5UAqsww"
}
4.2在服务器端检查并解析JWT
经过以上登录认证并响应JWT后,客户端在后续发起请求时,应该自主携带JWT数据,而服务器端应该尝试检查并解析JWT。
由于客户端在发起多种不同请求时都应该携带JWT,且服务器端都应该检查并尝试解析,所以,服务器端检查并解析的过程,应该发生在比较”通用“的组件中,即无论客户端提交的是哪个路径的请求,这个组件都应该执行!通常,会使用”过滤器“组件进行处理。
在项目的根包下创建filter.JwtAuthrozationFilter
类,继承自OncePerRequestFilter
,并在此类上添加@Component
注解:
@Slf4j
@Component
public class JwtAuthorizationFilter extends OncePerRequestFilter {public JwtAuthorizationFilter() {log.debug("创建过滤器:JwtAuthorizationFilter");}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {log.debug("执行JwtAuthorizationFilter");// 过滤器链继续执行,相当于:放行filterChain.doFilter(request, response);}}
关于客户端提交请求时携带JWT数据,业内通用的做法是在请求头中添加Authorization
属性,其值就是JWT数据,所以,服务器端获取JWT的做法应该是:从请求头中的Authorization
属性中获取JWT数据!
package cn.tedu.csmall.passport.filter;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;@Slf4j
@Component
public class JwtAuthorizationFilter extends OncePerRequestFilter {public JwtAuthorizationFilter() {log.debug("创建过滤器:JwtAuthorizationFilter");}@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {log.debug("执行JwtAuthorizationFilter");// 从请求头中获取JWTString jwt = request.getHeader("Authorization");log.debug("从请求头中获取JWT:{}", jwt);// 判断JWT数据是否不存在if (!StringUtils.hasText(jwt) || jwt.length() < 80) {log.debug("获取到的JWT是无效的,直接放行,交由后续的组件继续处理!");// 过滤器链继续执行,相当于:放行filterChain.doFilter(request, response);// 返回,终止当前方法本次执行return;}// 尝试解析JWTString secretKey = "nmlfdasfdsaurefuifdknjfdskjhajhef";Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();Object username = claims.get("username");log.debug("从JWT中解析得到username:{}", username);// 准备Authentication对象,后续会将此对象封装到Security的上下文中List<SimpleGrantedAuthority> authorities = new ArrayList<>();authorities.add(new SimpleGrantedAuthority("临时使用的权限"));Authentication authentication = new UsernamePasswordAuthenticationToken(username, null, authorities);// 将用户信息封装到Security的上下文中SecurityContext securityContext = SecurityContextHolder.getContext();securityContext.setAuthentication(authentication);log.debug("已经向Security的上下文中写入:{}", authentication);// 过滤器链继续执行,相当于:放行filterChain.doFilter(request, response);}}
完成后,还需要将此过滤器添加在Security框架的UsernamePasswordAuthenticationFilter
过滤器之前,需要在Security配置类中,先自动装配自定义的过滤器对象:
@Autowired
private JwtAuthorizationFilter jwtAuthorizationFilter;
然后,在configurer(HttpSecurity http)
方法中添加:
// 将“JWT过滤器”添加在“认证过滤器”之前
http.addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class);
最后,在JWT过滤器执行之初,先清除Security上下文中的数据,以避免”一旦提交JWT将认证对象存入到Security上下文中,后续不携带JWT也能访问“的问题:
// 清除Security上下文中的数据
SecurityContextHolder.clearContext();
完成后,启动项目,在Knife4j的调试功能中,携带JWT可以发起任何需要登录才能访问的请求,反之,这些请求不携带JWT将不允许访问。
JSD-2204-Session-Token-JWT-Day12相关推荐
- 熬夜彻底搞懂Cookie Session Token JWT
一切的根源就是因为 HTTP 是一个无状态的协议. HTTP 是一个无状态的协议 什么是无状态呢?就是说这一次请求和上一次请求是没有任何关系的,互不认识的,没有关联的. 看过电影<夏洛特烦恼&g ...
- 什么是 Cookie Session 和 JWT
无状态 Q: 大家都知道HTTP是无状态的协议, 那怎么理解无状态呢? A: 想象一下你们公司有个看门的门卫, 记性特别差, 还是个脸盲, 但是特别有职业操守, 每次出门回来都管你要出入证; 有一次, ...
- 转载:Session与JWT的使用
登录认证,估计是所有系统中最常见的功能了,并且也是最基础.最重要的功能.为了做好这一块而诞生了许多安全框架,比如最常见的Shiro.Spring Security等. 本文是一个系列文章,最终的目的是 ...
- 快速了解会话管理三剑客cookie、session和JWT
更多内容,欢迎关注微信公众号:全菜工程师小辉.公众号回复关键词,领取免费学习资料. 存储位置 三者都是应用在web中对http无状态协议的补充,达到状态保持的目的 cookie:cookie中的信息是 ...
- 【JavaWeb】认证授权(一)—— Session、JWT
文章目录 1.基础知识 2.实现 2.1Session 2.1.1基本实现 2.1.2添加过滤器 2.1.3上下文对象 2.2JWT 2.2.1基本实现 2.2.2添加拦截器 2.2.3上下文对象 1 ...
- 2、cookie session token详解
cookie session token详解 转自:http://www.cnblogs.com/moyand/ 发展史 1.很久很久以前,Web 基本上就是文档的浏览而已, 既然是浏览,作为服务器, ...
- JSON Web Token (JWT)生成Token及解密实战
转载自 JSON Web Token (JWT)生成Token及解密实战 昨天讲解了JWT的介绍.应用场景.优点及注意事项等,今天来个JWT具体的使用实践吧. 从JWT官网支持的类库来看,jjwt是J ...
- JSON Web Token (JWT),服务端信息传输安全解决方案
转载自 JSON Web Token (JWT),服务端信息传输安全解决方案 JWT介绍 JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑独立的基于JSON对 ...
- cookie session token 之间的区别
cookie 和session的区别 1.cookie数据存放在客户的浏览器上,session数据放在服务器上. 2.cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗 ...
- STS创建Spring Boot项目实战(Rest接口、数据库、用户认证、分布式Token JWT、Redis操作、日志和统一异常处理)
STS创建Spring Boot项目实战(Rest接口.数据库.用户认证.分布式Token JWT.Redis操作.日志和统一异常处理) 1.项目创建 1.新建工程 2.选择打包方式,这边可以选择为打 ...
最新文章
- linux查看msf安装目录,linux系统安装msf的过程详解
- python 获取你电脑纯文本文档内容!解决IndentationError: expected an indented block报错!
- 来电掉队,共享充电宝或许只是外表光鲜
- python random.choice报错_如何解决mtrand.RandomState.choice中的内存错误...
- C++类的组合和前向引用
- excel进销存管理系统_通用Excel助力企业定制开发信息化系统常用功能模块
- dos debug命令
- 阿里巴巴 连接池 druid 的使用、maven依赖
- 学生用计算机如何clean,windows installer clean up,教您电脑如何使用清理实用工具
- 前端打包混编压缩js代码,如何不重新打包,修改js文件内部配置参数?
- 三帧差分 matlab,三帧差分法
- 建材物资管理系统(软件定义)
- 前端代码检测重复率工具
- 极米Z7X对比当贝D5X区别 哪个值得买
- 离散型随机变量及其分布
- 三层架构和SpringMVC概述
- GoPass系列免杀基础(一)
- leetcode热题HOT100汇总——java题解已完结撒花
- java实现iam登录认证,获取IAM用户Token(使用密码)
- 打字会覆盖后面原有的字解决方案
热门文章
- ThinkPHP中文水印和图片水印结合
- RestCloud 微服务监控中心
- Qiyuan - 接小球游戏4.0
- SAMA5D27-移植8G NAND Flash(SAM-BA修改)
- CentOS 7 下的软件安装方法及策略
- ftp工具绿色版,推荐5款好用的ftp工具绿色版,fyp客户端下载
- 基于STM32F407的ADC解析-ADC1多通道扫描模式电压采集实验(启用DMA传输数据)
- PIC 1508 TIM1的定时器中断使用
- YC2440+wiggler小板+H-JATG+PCI转并口卡开发环境的搭建
- 2020第六届“美亚杯”团队赛WP