前言

代码可以参考
需要把Web应用做成无状态的,即服务器端无状态,就是说服务器端不会存储像会话这种东西,而是每次请求时access_token进行资源访问。这里我们将使用 JWT 1,基于散列的消息认证码,使用一个密钥和一个消息作为输入,生成它们的消息摘要。该密钥只有服务端知道。访问时使用该消息摘要进行传播,服务端然后对该消息摘要进行验证。

认证步骤

  1. 客户端第一次使用用户名密码访问认证服务器,服务器验证用户名和密码,认证成功,使用用户密钥生成JWT并返回
  2. 之后每次请求客户端带上JWT
  3. 服务器对JWT进行验证

自定义 jwt 拦截器

/*** oauth2拦截器,现在改为 JWT 认证*/
public class OAuth2Filter extends FormAuthenticationFilter {/*** 设置 request 的键,用来保存 认证的 userID,*/private final static String USER_ID = "USER_ID";@Resourceprivate JwtUtils jwtUtils;/*** logger*/private static final Logger LOGGER = LoggerFactory.getLogger(OAuth2Filter.class);/*** shiro权限拦截核心方法 返回true允许访问resource,** @param request* @param response* @param mappedValue* @return*/@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {String token = getRequestToken((HttpServletRequest) request);try {// 检查 token 有效性//ExpiredJwtException JWT已过期//SignatureException JWT可能被篡改Jwts.parser().setSigningKey(jwtUtils.getSecret()).parseClaimsJws(token).getBody();} catch (Exception e) {// 身份验证失败,返回 false 将进入onAccessDenied 判断是否登陆。onLoginFail(response);return false;}Long userId = getUserIdFromToken(token);// 存入到 request 中,在后面的业务处理中可以使用request.setAttribute(USER_ID, userId);return true;}/*** 当访问拒绝时是否已经处理了;* 如果返回true表示需要继续处理;* 如果返回false表示该拦截器实例已经处理完成了,将直接返回即可。** @param request* @param response* @return* @throws Exception*/@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {if (isLoginRequest(request, response)) {if (isLoginSubmission(request, response)) {return executeLogin(request, response);} else {return true;}} else {onLoginFail(response);return false;}}/*** 鉴定失败,返回错误信息* @param token* @param e* @param request* @param response* @return*/@Overrideprotected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {try {((HttpServletResponse) response).setStatus(HttpStatus.BAD_REQUEST.value());response.getWriter().print("账号活密码错误");} catch (IOException e1) {LOGGER.error(e1.getMessage(), e1);}return false;}/*** token 认证失败** @param response*/private void onLoginFail(ServletResponse response) {HttpServletResponse httpResponse = (HttpServletResponse) response;((HttpServletResponse) response).setStatus(HttpStatus.UNAUTHORIZED.value());try {response.getWriter().print("没有权限,请联系管理员授权");} catch (IOException e) {LOGGER.error(e.getMessage(), e);}}/*** 获取请求的token*/private String getRequestToken(HttpServletRequest httpRequest) {//从header中获取tokenString token = httpRequest.getHeader(jwtUtils.getHeader());//如果header中不存在token,则从参数中获取tokenif (StringUtils.isBlank(token)) {return httpRequest.getParameter(jwtUtils.getHeader());}if (StringUtils.isBlank(token)) {// 从 cookie 获取 tokenCookie[] cookies = httpRequest.getCookies();if (null == cookies || cookies.length == 0) {return null;}for (Cookie cookie : cookies) {if (cookie.getName().equals(jwtUtils.getHeader())) {token = cookie.getValue();break;}}}return token;}/*** 根据 token 获取 userID** @param token token* @return userId*/private Long getUserIdFromToken(String token) {if (StringUtils.isBlank(token)) {throw new KCException("无效 token", HttpStatus.UNAUTHORIZED.value());}Claims claims = jwtUtils.getClaimByToken(token);if (claims == null || jwtUtils.isTokenExpired(claims.getExpiration())) {throw new KCException(jwtUtils.getHeader() + "失效,请重新登录", HttpStatus.UNAUTHORIZED.value());}return Long.parseLong(claims.getSubject());}}

将自定义shiro拦截器,设置到 ShiroFilterFactoryBean 中,然后将需要进行权限验证的 path 进行设置拦截过滤。

登陆

    @PostMapping("/login")@ApiOperation("系统登陆")public ResponseEntity<String> login(@RequestBody SysUserLoginForm userForm) {String kaptcha = ShiroUtils.getKaptcha(Constants.KAPTCHA_SESSION_KEY);if (!userForm.getCaptcha().equalsIgnoreCase(kaptcha)) {throw new KCException("验证码不正确!");}UsernamePasswordToken token = new UsernamePasswordToken(userForm.getUsername(), userForm.getPassword());Subject currentUser = SecurityUtils.getSubject();currentUser.login(token);//账号锁定if (getUser().getStatus() == SysConstant.SysUserStatus.LOCK) {throw new KCException("账号已被锁定,请联系管理员");}// 登陆成功后直接返回 token ,然后后续放到 header 中认证return ResponseEntity.status(HttpStatus.OK).body(jwtUtils.generateToken(getUserId()));}

JwtUtils

我前面给 jwt 设置了三个参数

# jwt 配置
jwt:# 加密密钥secret: 61D73234C4F93E03074D74D74D1E39D9 #blog.wuwii.com# token有效时长expire: 7 # 7天,单位天# token 存在 header 中的参数header: token

jwt 工具类的编写

@ConfigurationProperties(prefix = "jwt")
@Component
public class JwtUtils {/*** logger*/private Logger logger = LoggerFactory.getLogger(JwtUtils.class);/*** 密钥*/private String secret;/*** 有效期限*/private int expire;/*** 存储 token*/private String header;/*** 生成jwt token** @param userId 用户ID* @return token*/public String generateToken(long userId) {Date nowDate = new Date();return Jwts.builder().setHeaderParam("typ", "JWT")// 后续获取 subject 是 userid.setSubject(userId + "").setIssuedAt(nowDate).setExpiration(DateUtils.addDays(nowDate, expire))// 这里我采用的是 HS512 算法.signWith(SignatureAlgorithm.HS512, secret).compact();}/*** 解析 token,* 利用 jjwt 提供的parser传入秘钥,** @param token token* @return 数据声明 Map<String, Object>*/public Claims getClaimByToken(String token) {try {return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();} catch (Exception e) {return null;}}/*** token是否过期** @return true:过期*/public boolean isTokenExpired(Date expiration) {return expiration.before(new Date());}public String getSecret() {return secret;}public void setSecret(String secret) {this.secret = secret;}public int getExpire() {return expire;}public void setExpire(int expire) {this.expire = expire;}public String getHeader() {return header;}public void setHeader(String header) {this.header = header;}
}

总结

由于 JWT 这种方式,服务端不需要保存任何状态,所以服务端不需要使用 session 保存用户信息,单元测试也比较方便,虽然中间转码解码会消耗一些性能,但是影响不大,还比较方便的应用在 SSO (Single Sign On )。


学习Spring Boot:(十六)使用Shiro与JWT 实现认证服务相关推荐

  1. (转)Spring Boot(十六):使用 Jenkins 部署 Spring Boot

    http://www.ityouknow.com/springboot/2017/11/11/spring-boot-jenkins.html enkins 是 Devops 神器,本篇文章介绍如何安 ...

  2. 学习Spring Boot:(二十六)使用 RabbitMQ 消息队列

    前言 前面学习了 RabbitMQ 基础,现在主要记录下学习 Spring Boot 整合 RabbitMQ ,调用它的 API ,以及中间使用的相关功能的记录. 相关的可以去[我的博客/Rabbit ...

  3. 学习Spring Boot:(二十五)使用 Redis 实现数据缓存

    前言 由于 Ehcache 存在于单个 java 程序的进程中,无法满足多个程序分布式的情况,需要将多个服务器的缓存集中起来进行管理,需要一个缓存的寄存器,这里使用的是 Redis. 正文 当应用程序 ...

  4. Spring Boot(十四):spring boot整合shiro-登录认证和权限管理

    Spring Boot(十四):spring boot整合shiro-登录认证和权限管理 使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公司都会涉 ...

  5. springboot 拦截器 日志_跟武哥一起学习Spring Boot,一份全面详细的学习教程

    SpringBoot现在已经是企业开发项目的标准框架,至少新项目一般都会使用SpringBoot框架,发现有一个SpringBoot的笔记写的非常好,非常全面, 也非常认真,即使你对SpringBoo ...

  6. symfony入门学习资料之十六:Symfony框架启动过程介绍

    symfony入门学习资料之十六:Symfony框架启动过程介绍 Symfony框架的核心本质是把Request转换成Response的一个过程.从入口文件(web_dev.php)的源码可以看个大概 ...

  7. 2022 年学习 Spring Boot 开发的最佳书籍

    在我们之前的文章中,我们查看了学习 Java 编程的必读书籍我们在其中探索了一些您可以利用的资源来加快 Java 开发的速度.在此基础上,在用 vanilla Java 编写一段时间后,您将意识到组织 ...

  8. Spring Boot (十五): Spring Boot + Jpa + Thymeleaf 增删改查示例

    <p>这篇文章介绍如何使用 Jpa 和 Thymeleaf 做一个增删改查的示例.</p> 先和大家聊聊我为什么喜欢写这种脚手架的项目,在我学习一门新技术的时候,总是想快速的搭 ...

  9. Spring Boot 极简集成 Shiro

    点击关注公众号,Java干货及时送达 1. 前言 Apache Shiro是一个功能强大且易于使用的Java安全框架,提供了认证,授权,加密,和会话管理. Shiro有三大核心组件: Subject: ...

  10. 我的MYSQL学习心得(十六) 优化

    原文:我的MYSQL学习心得(十六) 优化 我的MYSQL学习心得(十六) 优化 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看 ...

最新文章

  1. GitHub热门教程:100天搞定机器学习(中文版)
  2. kvm虚拟机_通过QEMU-GuestAgent实现从外部注入写文件到KVM虚拟机内部
  3. vue搜索好友_Vue实现类似通讯录功能(中)
  4. nginx 内置变量
  5. Ubuntu 配置swftools(Ubuntu14.04)
  6. CPU虚拟化系列文章之虚拟机切入和退出
  7. OAuth2.0授权协议与客户端授权码模式详解
  8. php扩展拦截请求,PHP的拦截器实例分析
  9. Spring Cloud入门五 hystrix
  10. 常用m脚本控制simulink模块方法
  11. 广州科二化龙考场_广州考驾照[科目二]化龙考场.考试详解
  12. 早这么讲运算放大器的开环增益,我现在都是高手了
  13. 佳沛金果水果的文案,水果佳沛金果文案高级感
  14. 微软edge浏览器安装包下载地址-Microsoft edge download
  15. TensorFlow北大公开课学习笔记-4.1损失函数
  16. 查询销量最高的产品mysql_MYSQL之——查询练习题
  17. Microsoft Office Word 2010-2016中公式不能自动斜体的解决方法
  18. 第五周 思维导图与快速学习
  19. 谷歌地图谷歌地图_为您的Google地图增添真实感
  20. AJAX---发送POST请求、Get请求、请求四步、解决低版本的缓存问题

热门文章

  1. 蓝牙连接不上车要hfp_如何正确使用车载蓝牙播放器呢?
  2. haarcascades---各种分类器xml文件下载地址
  3. [转载] java synchronized静态同步方法与非静态同步方法,同步语句块
  4. centos php fpm 停止_如何关闭php-fpm进程?
  5. covariance matrix r语言_时间序列分析|ARIMAX模型分步骤详解和R中实践
  6. 陕西2021高考成绩在哪查询,2021陕西高考成绩查询入口
  7. 人工智能ai 学习_人工智能中强化学习的要点
  8. 调整灰度图像的大小,而无需在Python中使用任何内置函数
  9. 案例:Redis 问题汇总和相关解决方案
  10. 第四章语法分析和语法分析程序