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,并将此数据响应到客户端。

在业务层,调用AuthenticationManagerauthenticate()方法后,得到的返回结果例如:

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相关推荐

  1. 熬夜彻底搞懂Cookie Session Token JWT

    一切的根源就是因为 HTTP 是一个无状态的协议. HTTP 是一个无状态的协议 什么是无状态呢?就是说这一次请求和上一次请求是没有任何关系的,互不认识的,没有关联的. 看过电影<夏洛特烦恼&g ...

  2. 什么是 Cookie Session 和 JWT

    无状态 Q: 大家都知道HTTP是无状态的协议, 那怎么理解无状态呢? A: 想象一下你们公司有个看门的门卫, 记性特别差, 还是个脸盲, 但是特别有职业操守, 每次出门回来都管你要出入证; 有一次, ...

  3. 转载:Session与JWT的使用

    登录认证,估计是所有系统中最常见的功能了,并且也是最基础.最重要的功能.为了做好这一块而诞生了许多安全框架,比如最常见的Shiro.Spring Security等. 本文是一个系列文章,最终的目的是 ...

  4. 快速了解会话管理三剑客cookie、session和JWT

    更多内容,欢迎关注微信公众号:全菜工程师小辉.公众号回复关键词,领取免费学习资料. 存储位置 三者都是应用在web中对http无状态协议的补充,达到状态保持的目的 cookie:cookie中的信息是 ...

  5. 【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 ...

  6. 2、cookie session token详解

    cookie session token详解 转自:http://www.cnblogs.com/moyand/ 发展史 1.很久很久以前,Web 基本上就是文档的浏览而已, 既然是浏览,作为服务器, ...

  7. JSON Web Token (JWT)生成Token及解密实战

    转载自 JSON Web Token (JWT)生成Token及解密实战 昨天讲解了JWT的介绍.应用场景.优点及注意事项等,今天来个JWT具体的使用实践吧. 从JWT官网支持的类库来看,jjwt是J ...

  8. JSON Web Token (JWT),服务端信息传输安全解决方案

    转载自 JSON Web Token (JWT),服务端信息传输安全解决方案 JWT介绍 JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑独立的基于JSON对 ...

  9. cookie session token 之间的区别

    cookie 和session的区别 1.cookie数据存放在客户的浏览器上,session数据放在服务器上. 2.cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗 ...

  10. STS创建Spring Boot项目实战(Rest接口、数据库、用户认证、分布式Token JWT、Redis操作、日志和统一异常处理)

    STS创建Spring Boot项目实战(Rest接口.数据库.用户认证.分布式Token JWT.Redis操作.日志和统一异常处理) 1.项目创建 1.新建工程 2.选择打包方式,这边可以选择为打 ...

最新文章

  1. linux查看msf安装目录,linux系统安装msf的过程详解
  2. python 获取你电脑纯文本文档内容!解决IndentationError: expected an indented block报错!
  3. 来电掉队,共享充电宝或许只是外表光鲜
  4. python random.choice报错_如何解决mtrand.RandomState.choice中的内存错误...
  5. C++类的组合和前向引用
  6. excel进销存管理系统_通用Excel助力企业定制开发信息化系统常用功能模块
  7. dos debug命令
  8. 阿里巴巴 连接池 druid 的使用、maven依赖
  9. 学生用计算机如何clean,windows installer clean up,教您电脑如何使用清理实用工具
  10. 前端打包混编压缩js代码,如何不重新打包,修改js文件内部配置参数?
  11. 三帧差分 matlab,三帧差分法
  12. 建材物资管理系统(软件定义)
  13. 前端代码检测重复率工具
  14. 极米Z7X对比当贝D5X区别 哪个值得买
  15. 离散型随机变量及其分布
  16. 三层架构和SpringMVC概述
  17. GoPass系列免杀基础(一)
  18. leetcode热题HOT100汇总——java题解已完结撒花
  19. java实现iam登录认证,获取IAM用户Token(使用密码)
  20. 打字会覆盖后面原有的字解决方案

热门文章

  1. ThinkPHP中文水印和图片水印结合
  2. RestCloud 微服务监控中心
  3. Qiyuan - 接小球游戏4.0
  4. SAMA5D27-移植8G NAND Flash(SAM-BA修改)
  5. CentOS 7 下的软件安装方法及策略
  6. ftp工具绿色版,推荐5款好用的ftp工具绿色版,fyp客户端下载
  7. 基于STM32F407的ADC解析-ADC1多通道扫描模式电压采集实验(启用DMA传输数据)
  8. PIC 1508 TIM1的定时器中断使用
  9. YC2440+wiggler小板+H-JATG+PCI转并口卡开发环境的搭建
  10. 2020第六届“美亚杯”团队赛WP