1. 解析JWT时可能出现的错误

如果使用过期的JWT,在解析时将出现错误:

io.jsonwebtoken.ExpiredJwtException: JWT expired at 2022-09-06T17:33:03Z. Current time: 2022-09-08T09:04:26Z, a difference of 142283930 milliseconds.  Allowed clock skew: 0 milliseconds.

如果使用的JWT数据的签名有误,在解析时将出现错误:

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 !L��؈�������)]P�

后续,将需要对这3种异常进行捕获并处理!

2. 使用JWT实现认证

在使用Spring Security框架处理认证时,如果认证通过,必须把通过认证的用户的信息存入到SecurityContext(Spring Security框架的上下文)对象中,后续,Spring Security框架会自动的尝试从SecurityContext中获取认证信息,如果获取到有效的认证信息,则视为“已登录”,否则,将视为“未登录”!

使用JWT实现认证需要完成的开发任务:

  • 当认证通过时生成JWT,并将JWT响应到客户端
  • 当客户端后续提交请求时,应该自觉携带JWT,而服务器端将对JWT进行解析,如果解析成功,将得此客户端的用户信息,并将认证信息存入到SecurityContext

3. 当认证通过时生成JWT,并将JWT响应到客户端

首先,需要修改IAdminService中处理认证的方法(login()方法)的声明,将返回值类型修改为String

String login(AdminLoginInfoDTO adminLoginInfoDTO);

并且,AdminServiceImpl中方法的声明也同步修改,在实现过程中,当通过认证后,应该生成JWT并返回:

@Override
public String login(AdminLoginInfoDTO adminLoginInfoDTO) {log.debug("开始处理【登录认证】的业务,参数:{}", adminLoginInfoDTO);// 调用AuthenticationManager的authenticate()方法执行认证// 在authenticate()方法的执行过程中// Spring Security会自动调用UserDetailsService对象的loadUserByUsername()获取用户详情// 并根据loadUserByUsername()返回的用户详情自动验证是否启用、判断密码是否正确等Authentication authentication= new UsernamePasswordAuthenticationToken(adminLoginInfoDTO.getUsername(),adminLoginInfoDTO.getPassword());Authentication authenticateResult= authenticationManager.authenticate(authentication);log.debug("Spring Security已经完成认证,且认证通过,返回的结果:{}", authenticateResult);log.debug("返回认证信息中的当事人(Principal)类型:{}", authenticateResult.getPrincipal().getClass().getName());log.debug("返回认证信息中的当事人(Principal)数据:{}", authenticateResult.getPrincipal());// 从认证返回结果中取出当事人信息User principal = (User) authenticateResult.getPrincipal();String username = principal.getUsername();log.debug("认证信息中的用户名:{}", username);// 生成JWT,并返回// 准备Claims值Map<String, Object> claims = new HashMap<>();claims.put("username", username);// JWT的过期时间Date expiration = new Date(System.currentTimeMillis() + 15 * 24 * 60 * 60 * 1000);log.debug("即将生成JWT数据,过期时间:{}", expiration);// JWT的组成:Header(头:算法和Token类型)、Payload(载荷)、Signature(签名)String secretKey = "97iuFDVDfv97iuk534Tht3KJR89kBGFSBgfds";String jwt = Jwts.builder()// Header.setHeaderParam("alg", "HS256").setHeaderParam("typ", "JWT")// Payload.setClaims(claims).setExpiration(expiration)// Signature.signWith(SignatureAlgorithm.HS256, secretKey).compact();log.debug("已经生成JWT数据:{}", jwt);return jwt;
}

提示:以上生成JWT的代码暂未封装!

最后,在AdminController中,将处理认证的方法(login()方法)的返回值类型由JsonResult<Void>修改为JsonResult<String>,并且,在方法体中,调用IAdminService的认证方法时,必须获取返回值,最终将此返回值封装到JsonResult对象中,响应到客户端:

// http://localhost:9081/admins/login
@ApiOperation("管理员管理")
@ApiOperationSupport(order = 88)
@PostMapping("/login")
public JsonResult<String> login(AdminLoginInfoDTO adminLoginInfoDTO) {String jwt = adminService.login(adminLoginInfoDTO);return JsonResult.ok(jwt);
}

4. 解析JWT并处理SecurityContext

当客户端成功的通过认证后,将可以得到JWT,后续,客户端可以携带JWT提交请求,但是,作为服务器端,并不知道客户端将会向哪个URL提交请求,或者说,不管客户端向哪个URL提交请求,服务器端都应该尝试解析JWT,以识别客户端的身份,则解析JWT的代码可以使用“过滤器”组件来实现!

过滤器(Filter):是Java EE中的核心组件,此组件是最早接收到请求的组件!并且,此组件可作用于若干个请求的处理过程。

关于客户端携带JWT,业内通用的做法是:将JWT携带在请求头(Request Header)中名为Authorization的属性中!

所以,此过滤器将固定的通过请求头(Request Header)中的Authorization属性获取JWT数据,并尝试解析。

由于Spring Security框架判断是否登录的标准是:在SecurityContext中是否存在认证信息!所以,当成功解析JWT数据后,应该将认证信息保存到SecurityContext中。

另外,还有几个细节:

  • 一旦SecurityContext中存在认证信息,在后续的访问中,即使不携带JWT数据,只要在SecurityContext还存在此前存入的认证信息,就会被视为“已经通过认证”,所以,为了避免此问题,应该在接收到请求的那一刻就直接清除SecurityContext
  • 认证的过程应该是“先将认证信息存入到SecurityContext(由我们的过滤器执行),再判断是否是通过认证的状态(由Spring Security的过滤器等组件执行)”,所以,当前过滤器必须在Spring Security的相关过滤器之前执行。

所以,在根包下创建filter.JwtAuthorizationFilter类,以解析JWT、向SecurityContext中存入认证信息:

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.GrantedAuthority;
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;/*** 解析JWT的过滤器** 1. 首先,清除SecurityContext中的认证信息* 2. 如果客户端没有携带JWT,则放行,由后续的组件进行处理* 3. 如果客户端携带了有效的JWT,则解析,并将解析结果用于创建认证对象,最终,将认证对象存入到SecurityContext** @author java@tedu.cn* @version 0.0.1*/
@Slf4j
@Component
public class JwtAuthorizationFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {log.debug("处理JWT的过滤器开始执行……");// 清除SecurityContext中原有的认证信息// 避免曾经成功访问过,后续不携带JWT也能被视为“已认证”SecurityContextHolder.clearContext();// 尝试从请求头中获取JWT数据String jwt = request.getHeader("Authorization");log.debug("尝试从请求头中获取JWT数据:{}", jwt);// 判断客户端是否携带了有效的JWT数据,如果没有,直接放行if (!StringUtils.hasText(jwt) || jwt.length() < 113) {log.debug("获取到的JWT被视为【无效】,过滤器执行【放行】");filterChain.doFilter(request, response);return;}// 程序执行到此处,表示客户端携带了有效的JWT,则尝试解析log.debug("获取到的JWT被视为【有效】,则尝试解析……");String secretKey = "97iuFDVDfv97iuk534Tht3KJR89kBGFSBgfds";Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();String username = claims.get("username", String.class);log.debug("从JWT中解析得到【username】的值:{}", username);// 准备权限,将封装到认证信息中List<GrantedAuthority> authorityList = new ArrayList<>();GrantedAuthority authority = new SimpleGrantedAuthority("这是一个山寨的权限");authorityList.add(authority);// 准备存入到SecurityContext的认证信息Authentication authentication= new UsernamePasswordAuthenticationToken(username, null, authorityList);// 将认证信息存入到SecurityContext中SecurityContext securityContext = SecurityContextHolder.getContext();securityContext.setAuthentication(authentication);log.debug("过滤器执行【放行】");filterChain.doFilter(request, response);}}

然后,在SecurityConfiguration中自动装配此过滤器:

@Autowired
private JwtAuthorizationFilter jwtAuthorizationFilter;

并在configurer()方法中补充:

// 将JWT过滤器添加在Spring Security的UsernamePasswordAuthenticationFilter之前
http.addFilterBefore(jwtAuthorizationFilter,UsernamePasswordAuthenticationFilter.class);

完成后,重启项目,在Knife4j的在线API文档中,先不携带JWT并使用正确的账号登录,然后,携带登录返回的JWT即可向那些不在白名单中的URL进行访问!

5. 关于账号的权限

当处理认证时,应该从数据库中查询出此用户的权限,并且,将权限封装到UserDetails对象中,当认证成功后,返回的认证对象中的当事人信息就会包含权限信息,接下来,可以将权限信息也写入到JWT中!

后续,在解析JWT时,也可以从中解析得到权限信息,并将权限信息存入到SecurityContext中,则后续Spring Security的相关组件可以实现对权限的验证!

6. 查询管理员的权限

在处理认证时,会调用AdminMapper接口中的AdminLoginInfoVO getLoginInfoByUsername(String username);方法,此方法的返回值应该包含管理员的权限。

则SQL语句大致是:

selectams_admin.id,ams_admin.username,ams_admin.password,ams_admin.enable,ams_permission.value
from ams_admin
left join ams_admin_role on ams_admin.id=ams_admin_role.admin_id
left join ams_role_permission on ams_admin_role.role_id=ams_role_permission.role_id
left join ams_permission on ams_role_permission.permission_id=ams_permission.id
where username='root';

为了保证查询结果可以封装权限信息,需要在返回值类型中添加属性:

@Data
public class AdminLoginInfoVO implements Serializable {private Long id;private String username;private String password;private Integer enable;private List<String> permissions; // 新增}

然后,重新配置getLoginInfoByUsername()方法映射的SQL查询:

<!-- AdminLoginInfoVO getLoginInfoByUsername(String usernanme); -->
<select id="getLoginInfoByUsername" resultMap="LoginResultMap">SELECT<include refid="LoginQueryFields"/>FROMams_adminLEFT JOIN ams_admin_role ON ams_admin.id=ams_admin_role.admin_idLEFT JOIN ams_role_permission ON ams_admin_role.role_id=ams_role_permission.role_idLEFT JOIN ams_permission ON ams_role_permission.permission_id=ams_permission.idWHEREusername=#{username}
</select><sql id="LoginQueryFields"><if test="true">ams_admin.id,ams_admin.username,ams_admin.password,ams_admin.enable,ams_permission.value</if>
</sql><!-- collection标签:用于配置返回结果类型中List类型的属性 -->
<!-- collection标签的ofType属性:List中的元素类型 -->
<!-- collection子级:需要配置如何创建出List中的每一个元素 -->
<resultMap id="LoginResultMap" type="cn.tedu.csmall.passport.pojo.vo.AdminLoginInfoVO"><id column="id" property="id"/><result column="username" property="username"/><result column="password" property="password"/><result column="enable" property="enable"/><collection property="permissions" ofType="java.lang.String"><constructor><arg column="value"/></constructor></collection>
</resultMap>

完成后,应该及时测试!

jsd2205-csmall-passport(Day13)相关推荐

  1. JSD-2204-Session-Token-JWT-Day12

    1.开发自定义的登录流程 目前,在passport项目中,登录是由Security框架提供的页面的表单来输入用户名.密码,且由Security框架自动处理登录流程,不适合前后端分离的开发模式!所以,需 ...

  2. 关于Spring Security框架 关于单点登录sso

    1.Spring Security的作用 Spring Security主要解决了认证和授权相关的问题. 认证(Authenticate):验证用户身份,即登录. 授权(Authorize):允许用户 ...

  3. 基于Spring Security与JWT实现单点登录

    基于RBAC的权限管理 RBAC(Role-Based Access Control):基于角色的访问控制 当前项目中,RBAC具体的表现为: 管理员表:ams_admin 角色表:ams_role ...

  4. Spring Security 框架详解

    SECURITY Spring Security框架 Spring Security框架主要解决了认证与授权的相关问题. 添加依赖 在Spring Boot项目中,需要使用Spring Securit ...

  5. Spring Security关于用户身份认证与授权

    Spring Security是用于解决认证与授权的框架. 创建spring项目,添加依赖 <!-- Spring Boot Security:处理认证与授权 --><depende ...

  6. My batis动态SQL

    通过xml配置文件的方式,配置Mapper中需要用到的SQL语句 之前配置执行的SQL语句是通过注解的方式, 注解方式的话如果SQL语句太长存在折行时会导致SQL语句不够直观, 一些复杂的关联查询 不 ...

  7. php composer 无法下载,php – composer无法安装laravel / passport

    我用它创建了一个新项目 laravel new blogposts 使用全局安装在我的ubuntu 18上的"Laravel Installer 2.0.1". 当我尝试使用安装护 ...

  8. Laravel 5.5 使用 Passport 实现 Auth 认证

    最近在写一个前后端分离项目,本来想用 Jwt-auth + Dingo 开发的,但是略感笨重,于是想到了 Laravel 的 Passport 和 5.5 新出的 Api Resource.Larav ...

  9. php oauth2.0 实例,详解laravel passport OAuth2.0的4种模式

    参考: 1... 熟悉的场景 某个网站,某用户未注册,注册时提示可微信账号登录(github, google都有类似 某网站是第三方(客户端), 认证服务器和资源服务器都在微信,资源是指微信的用户名, ...

最新文章

  1. 数十篇推荐系统论文被批无法复现:源码、数据集均缺失,性能难达预期
  2. Flex使用cookie保存登状态
  3. Dynamics CRM中跨域调用Web API 2
  4. The 'Microsoft.Jet.OLEDB.4.0' provider is not registered on the local machine.
  5. Linux crypto相关知识的汇总 Linux加密框架crypto对称算法和哈希算法加密模式
  6. Ext.Net 1.2.0_Ext.Net.TreePanel 勾选“纠结”发送给服务器端的方法
  7. Linux中的中断管理机制
  8. xadmin在Django 1.11中的使用及中英文切换
  9. 《Spring1之 第一次站立会议(重发)》
  10. 【学习OpenCV4】什么是图像的直方图?如何获取直方图?
  11. Candy leetcode java
  12. 在线课程培训系统源码 在线授课 在线教育源码 网课小程序源码
  13. 未来5年智慧城市宽带入户超百兆
  14. mirrorlink
  15. 关于升级短信源码开发接入SMPP通道
  16. 关于需求响应式公共交通的那些事(上)
  17. 2022年烷基化工艺模拟考试题及烷基化工艺模拟考试题库
  18. 每天一大杯可乐,会不会骨质酥松哇?
  19. 21点扑克游戏的出牌策略的研究
  20. 「行业/市场分析」简说

热门文章

  1. Ruoyi框架学习--Vue前端配置文件详解
  2. 微信表情150个限制怎么破?教你一招
  3. openmv 神经网络 超出内存_星瞳科技OpenMV视频教程22-神经网络cifar_10
  4. Spring Cloud实战(三)-监控中心
  5. SAP各种BOM详解(包含常用BAPI)
  6. java实现两个数运算_用java做简单的计算器类,实现两个数字的加减乘除运算
  7. 也谈把程序写好 —— 一点初级程序员的鄙见
  8. 光量子计算机的信息载体,如何使“孤傲”的光子改变彼此的量子态?
  9. 面向对象编程基本概念
  10. 论文那些事—ZOO: Zeroth Order Optimization Based Black-box Attacks