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

前言

代码可以参考
需要把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 )。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/556824.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java泛型和注解,泛型 · 注解和泛型 · 看云

[TOC]# 泛型## 为什么要使用泛型在之前学过的集合框架中&#xff0c;List和Map都使用了泛型技术来确认其内容的数据类型。如果不使用泛型&#xff0c;在程序运行阶段&#xff0c;会带来数据类型转型的错误风险。~~~List list new ArrayList();list.add("tom");for (…

java程序单引号报错,javapoigetInpuStream报错br/是这样的, 爱问知识人

是这样的&#xff0c;我写了重载了两个getInputStream方法&#xff0c;当调用不带参数的方法时&#xff0c;运行正常&#xff0c;当调用带参的方法则报 Can not find a java.io.InputStream with the name [inputStream] in是这样的&#xff0c;我写了重载了两个getInputStream…

学习Spring Boot:(十七)Spring Boot 中使用 Redis

前言 Redis是一个由Salvatore Sanfilippo写的key-value存储系统。 edis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。 通常被称为数据结构服务器&#xff0c;因为值&#xff08;va…

jqueryd登录异步请求 java,ajaxd的js和jquery实现

先来看一下javascript的。var httpxml;httpxml new XMLHttpRequest();httpxml.onreadystatechangefunction(){ //当服务器响应就绪时执行函数(就是说服务器准备好了你可以发请求了)if(httpxml.status 200 && httpxml.readyState 4){//这里200和4代表响应的状态&…

学习Spring Boot:(十八)Spring Boot 中session共享

前言 前面我们将 Redis 集成到工程中来了&#xff0c;现在需要用它来做点实事了。这次为了解决分布式系统中的 session 共享的问题&#xff0c;将 session 托管到 Redis。 正文 引入依赖 除了上篇文章中引入 spring-boot-starter-data-redis&#xff0c;还需要 spring-sess…

matlab 码元扩展,扩频通信及matlab仿真

扩展频谱通信以及直接扩频的matlab仿真号无关)扩展频谱后成为宽频带信号&#xff0c;然后再进行传输的一种系统。待传输的基带信号就是信源发出的数字信号。特定的扩频函数通常选用各种伪随机序列(扩频码)&#xff0c;其码元传输速率远大于基带信号速率&#xff0c;因而和基带信…

学习Spring Boot:(十九)Shiro 中使用缓存

前言 在 shiro 中每次去拦截请求进行权限认证的时候&#xff0c;都会去数据库查询该用户的所有权限信息&#xff0c; 这个时候就是有一个问题了&#xff0c;因为用户的权限信息在短时间内是不可变的&#xff0c;每次查询出来的数据其实都是重复数据&#xff0c;没必要每次都去…

matlab安装无效距离过远,求助matlab的远程序

求助matlab的远程序function varargout a1(varargin)% A1 M-file for a1.fig% A1, by itself, creates a new A1 or raises the existing% singleton*.%% H A1 returns the handle to a new A1 or the handle to% the existing singleton*.%% A1(CALLBACK,hObject,eventData,…

学习Spring Boot:(二十)使用 MongoDB

前言 MongoDB&#xff08;来自于英文单词“Humongous”&#xff0c;中文含义为“庞大” &#xff09;是可以应用于各种规模的企业、各个行业以及各类应用程序的开源数据库。基于分布式文件存储的数据库。由C语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。MongoD…

php 事件调度,PHP单元测试调度事件

如何在函数调用期间测试事件是否被调度&#xff1f;public function updateUser() {//Do some update stuff$event new UserUpdated($user);$event->attach([new SendEmailAddressChangeEmail($emailAddress),new SendEmailAddressChangeEmail($oldEmailAddress),]);$event…

学习Spring Boot:(二十一)使用 EhCache 实现数据缓存

前言 当多次查询数据库影响到系统性能的时候&#xff0c;可以考虑使用缓存&#xff0c;来解决数据访问新能的问题。 SpringBoot 已经为我们提供了自动配置多个 CacheManager 的实现&#xff0c;只要去实现使用它就可以了。 一般的系统都是优先使用 EhCache&#xff0c;它工作…

php如何解决报错,php 启动报错如何解决_PHP教程

复制代码 代码如下:[rootabc lnmp]# service php-fpm startStarting php-fpm eAccelerator: Could not allocate 67108864 bytes, the maximum size the kernel allows is 33554432 bytes. Lower the amount of memory request or increase the limit in /proc/sys/kernel/shmm…

学习Spring Boot:(二十二)使用 AOP

前言 AOP&#xff0c;意为&#xff1a;面向切面编程&#xff0c;通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。基于AOP实现的功能不会破坏原来程序逻辑&#xff0c;因此它可以很好的对业务逻辑的各个部分进行隔离&#xff0c;从而使得业务逻辑各部分之间的…

matlab怎么求hadamard,hadamard matlab

3、图像Hadamard变换 数字图像处理 ? Matlab没有提供图像Hadamard变换功能,不过 提供了求Hadamard变换矩阵的功能。例如使用 命令hadamard(8)能够得到下面[8 8]的......(A) 1.0000 命令 hadamard 矩阵 函数 hadamard 格式 H hadamard(n) 例 1-15 >> hhadamard(4) h 1 1…

学习Spring Boot:(二十三)Spring Boot 中使用 Docker

前言 简单的学习下怎么在 Spring Boot 中使用 Docker 进行构建&#xff0c;发布一个镜像&#xff0c;现在我们通过远程的 docker api 构建镜像&#xff0c;运行容器&#xff0c;发布镜像等操作。 这里只介绍两种方式&#xff1a; 远程命令 api &#xff08;需要知道 Docker …

nginx php大文件上传,Nginx+PHP上传大文件设置

nginx的修改send_timeout 60;fastcgi_connect_timeout 300;fastcgi_send_timeout 300;fastcgi_read_timeout 300;client_max_body_size 30m; 可以全局设置也可以每个网站单独设置php的修改upload_max_filesize 10Mpost_max_size 15M 该项应该要大于 upload_max_filesizemax_inp…

学习Spring Boot:(二十四)多数据源配置与使用

前言 随着业务量增大&#xff0c;可能有些业务不是放在同一个数据库中&#xff0c;所以系统有需求使用多个数据库完成业务需求&#xff0c;我们需要配置多个数据源&#xff0c;从而进行操作不同数据库中数据。 正文 JdbcTemplate 多数据源 配置 需要在 Spring Boot 中配置…

matlab 超限像素平滑法,matlab超限像素平滑法_图像增强技术.ppt

主讲&#xff1a;苏菡 susuhansicnu.edu.cn 图象增强技术概述 目标&#xff1a;“视觉”效果更好&#xff0c;图象保真度不是首要目标 方法&#xff1a; 空间域增强&#xff1a;直接处理图象的象素 频率域增强&#xff1a;修改图象的傅立叶变换 评价&#xff1a;高度主观 视觉解…

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

前言 由于 Ehcache 存在于单个 java 程序的进程中&#xff0c;无法满足多个程序分布式的情况&#xff0c;需要将多个服务器的缓存集中起来进行管理&#xff0c;需要一个缓存的寄存器&#xff0c;这里使用的是 Redis。 正文 当应用程序要去缓存中读取数据&#xff0c;但是缓存…

php ai库,收藏 | 深度学习框架、AI库、ML库、NLP库、CV库汇总!

一份包含深度学习框架、AI工具/库、机器学习工具/库、NLP工具/库、CV工具/库的汇总&#xff0c;并附有说明文档(有些是中英文文档都有)。之所以附带文档而不是源码&#xff0c;有两方面原因&#xff1a;先看文档&#xff0c;找出最适合自己的一个或两个(太多没必要&#xff0c;…