shiro

shrio笔记

认证

授权

JWT简介(Json Web Token)

1、导入依赖库


<!--shiro--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-web</artifactId><version>1.5.3</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.5.3</version></dependency><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.10.3</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.11</version></dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpcore</artifactId><version>4.4.13</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

2、生成令牌(需要密钥、过期时间、用户id)

emos:jwt:#密钥secret: abc123456#令牌过期时间(天)expire:  5#令牌缓存时间(天数)cache-expire: 10

3、验证令牌的有效性(内容是否有效、是否过期)

创建JwtUtil工具类

package com.example.emoswxapi.config.shiro;import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.emoswxapi.exception.EmosException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import java.util.Date;@Component
@Slf4j
public class JwtUtil {/*
1、导入依赖库
2、生成令牌(需要密钥、过期时间、用户id)
3、验证令牌的有效性(内容是否有效、是否过期)
*///密钥(在配置文件里定义)@Value("${emos.jwt.secret}")private String secret;//过期时间(天)@Value("${emos.jwt.expire}")private int expire;
//userId方法传进来public String createToken(int userId) {Date date = DateUtil.offset(new Date(), DateField.DAY_OF_YEAR, expire).toJdkDate();Algorithm algorithm = Algorithm.HMAC256(secret); //创建加密算法对象JWTCreator.Builder builder = JWT.create();String token = builder.withClaim("userId", userId).withExpiresAt(date).sign(algorithm);return token;}//通过令牌获取userIdpublic int getUserId(String token) {try {DecodedJWT jwt = JWT.decode(token);return jwt.getClaim("userId").asInt();} catch (Exception e) {throw new EmosException("令牌无效");}}//验证令牌的内容和过期public void verifierToken(String token) {Algorithm algorithm = Algorithm.HMAC256(secret); //创建加密算法对象JWTVerifier verifier = JWT.require(algorithm).build();verifier.verify(token);}
}

4、把令牌字符串封装为认证对象

package com.example.emoswxapi.config.shiro;import org.apache.shiro.authc.AuthenticationToken;public class OAuth2Token implements AuthenticationToken {private String token;public OAuth2Token(String token){this.token = token;}@Overridepublic Object getPrincipal() {return token;}@Overridepublic Object getCredentials() {return token;}
}

5、实现认证和授权(创建AuthorizingRealm类的子类,实现认证和授权方法)

package com.example.emoswxapi.config.shiro;import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.Set;@Component
public class OAuth2Realm extends AuthorizingRealm {@Autowiredprivate JwtUtil jwtUtil;//传入的令牌是否符合要求@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof OAuth2Token;}/*** 授权(验证权限时调用)*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();//TODO 查询用户的权限列表//TODO 把权限列表添加到info对象中return info;}/*** 认证(登录时调用)*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//TODO 从令牌中获取userId,然后检测该账户是否被冻结。SimpleAuthenticationInfo info = new SimpleAuthenticationInfo();//TODO 往info对象中添加用户信息、Token字符串return info;}
}

6、如何设计令牌的刷新机制
为什么要刷新令牌?
令牌一旦产生就保存在客户端
即使用户一直在登录使用系统,也不会重新生成令牌
令牌到期,用户必须重新登录
令牌应该自动续期

双令牌机制
设置长短日期的令牌
短日期的令牌失效就用长日期的令牌

令牌缓存机制
令牌缓存到redis上面
缓存的令牌过期时间是客户端令牌的一倍
如果客户端令牌过期,缓存令牌没有过期,则生成新的令牌
如果客户端令牌过期,缓存令牌也过期,则用户必须登录

在这里插入图片描述

7、创建存储令的媒介类

package com.example.emoswxapi.config.shiro;import org.springframework.stereotype.Component;
//创建存储令的媒介类
@Component
public class ThreadLocalToken {private ThreadLocal local=new ThreadLocal();public void setToken(String token){local.set(token);}public String getToken(){return (String) local.get();}public void clear(){local.remove();}
}

8、拦截所有请求

创建过滤器

判断哪些请求应该被Shiro处理
option请求直接放行(提交application/json数据,请求被分成options和post两次)
其余所有请求都被Shrio处理

判断Token是真的过期还是假的过期
真过期,返回提示信息,让用户重新登录
假过期,就生成新的令牌,返回给客户端

存储新令牌
ThreadLocalToken
Redis

package com.example.emoswxapi.config.shiro;import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;@Component
@Scope("prototype") //产生多例的对象,不然就是单例的
public class OAuth2Filter extends AuthenticatingFilter {@Autowiredprivate ThreadLocalToken threadLocalToken;@Value("${emos.jwt.cache-expire}")private int cacheExpire;@Autowiredprivate JwtUtil jwtUtil;@Autowiredprivate RedisTemplate redisTemplate;/*** 拦截请求之后,用于把令牌字符串封装成令牌对象*/@Overrideprotected AuthenticationToken createToken(ServletRequest request,ServletResponse response) throws Exception {//获取请求tokenString token = getRequestToken((HttpServletRequest) request);if (StringUtils.isBlank(token)) {return null;}return new OAuth2Token(token);}/*** 拦截请求,判断请求是否需要被Shiro处理*/@Overrideprotected boolean isAccessAllowed(ServletRequest request,ServletResponse response, Object mappedValue) {HttpServletRequest req = (HttpServletRequest) request;// Ajax提交application/json数据的时候,会先发出Options请求// 这里要放行Options请求,不需要Shiro处理if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {return true;}// 除了Options请求之外,所有请求都要被Shiro处理return false;}/*** 该方法用于处理所有应该被Shiro处理的请求*/@Overrideprotected boolean onAccessDenied(ServletRequest request,ServletResponse response) throws Exception {HttpServletRequest req = (HttpServletRequest) request;HttpServletResponse resp = (HttpServletResponse) response;resp.setHeader("Content-Type", "text/html;charset=UTF-8");//允许跨域请求resp.setHeader("Access-Control-Allow-Credentials", "true");resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));threadLocalToken.clear();//获取请求token,如果token不存在,直接返回401String token = getRequestToken((HttpServletRequest) request);if (StringUtils.isBlank(token)) {resp.setStatus(HttpStatus.SC_UNAUTHORIZED);resp.getWriter().print("无效的令牌");return false;}try {jwtUtil.verifierToken(token); //检查令牌是否过期} catch (TokenExpiredException e) {//客户端令牌过期,查询Redis中是否存在令牌,如果存在令牌就重新生成一个令牌给客户端if (redisTemplate.hasKey(token)) {redisTemplate.delete(token);//删除令牌int userId = jwtUtil.getUserId(token);token = jwtUtil.createToken(userId);  //生成新的令牌//把新的令牌保存到Redis中redisTemplate.opsForValue().set(token, userId + "", cacheExpire, TimeUnit.DAYS);//把新令牌绑定到线程threadLocalToken.setToken(token);} else {//如果Redis不存在令牌,让用户重新登录resp.setStatus(HttpStatus.SC_UNAUTHORIZED);resp.getWriter().print("令牌已经过期");return false;}} catch (JWTDecodeException e) {resp.setStatus(HttpStatus.SC_UNAUTHORIZED);resp.getWriter().print("无效的令牌");return false;}boolean bool = executeLogin(request, response);return bool;}@Overrideprotected boolean onLoginFailure(AuthenticationToken token,AuthenticationException e, ServletRequest request, ServletResponse response) {HttpServletRequest req = (HttpServletRequest) request;HttpServletResponse resp = (HttpServletResponse) response;resp.setStatus(HttpStatus.SC_UNAUTHORIZED);resp.setContentType("application/json;charset=utf-8");resp.setHeader("Access-Control-Allow-Credentials", "true");resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));try {resp.getWriter().print(e.getMessage());} catch (IOException exception) {}return false;}/*** 获取请求头里面的token*/private String getRequestToken(HttpServletRequest httpRequest) {//从header中获取tokenString token = httpRequest.getHeader("token");//如果header中不存在token,则从参数中获取tokenif (StringUtils.isBlank(token)) {token = httpRequest.getParameter("token");}return token;}@Overridepublic void doFilterInternal(ServletRequest request,ServletResponse response, FilterChain chain) throws ServletException, IOException {super.doFilterInternal(request, response, chain);}
}

9、配置到shrio框架

把Filter和Realm添加到shrio框架

创建四个对象返回给springboot
SecurityManager 用于封装Realm对象
ShrioFilterFactoryBean 用于封装Filter对象 设置Filter拦截路径
LifecycleBeanPostProcessor 管理Shrio对象生命周期
AuthorizationAttributeSourceAdvisor AOP切面类 Web方法执行前,验证权限

package com.example.emoswxapi.config.shiro;import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;@Configuration
public class ShiroConfig {@Bean("securityManager")public SecurityManager securityManager(OAuth2Realm oAuth2Realm) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager.setRealm(oAuth2Realm);securityManager.setRememberMeManager(null);return securityManager;}@Bean("shiroFilter")public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager,OAuth2Filter oAuth2Filter) {ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();shiroFilter.setSecurityManager(securityManager);//oauth过滤Map<String, Filter> filters = new HashMap<>();filters.put("oauth2", oAuth2Filter);shiroFilter.setFilters(filters);Map<String, String> filterMap = new LinkedHashMap<>();filterMap.put("/webjars/**", "anon");filterMap.put("/druid/**", "anon");filterMap.put("/app/**", "anon");filterMap.put("/sys/login", "anon");filterMap.put("/swagger/**", "anon");filterMap.put("/v2/api-docs", "anon");filterMap.put("/swagger-ui.html", "anon");filterMap.put("/swagger-resources/**", "anon");filterMap.put("/captcha.jpg", "anon");filterMap.put("/user/register", "anon");filterMap.put("/user/login", "anon");filterMap.put("/test/**", "anon");filterMap.put("/**", "oauth2");shiroFilter.setFilterChainDefinitionMap(filterMap);return shiroFilter;}@Bean("lifecycleBeanPostProcessor")public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();advisor.setSecurityManager(securityManager);return advisor;}}

10、创建AOP切面类
拦截所有的web方法返回值
判断是否刷新生成新令牌(检查THreadLocal中是否保存令牌,把新令牌绑定到R对象中)

package com.example.emoswxapi.aop;import com.example.emoswxapi.common.util.R;
import com.example.emoswxapi.config.shiro.ThreadLocalToken;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;//AOP切面类
@Aspect
@Component
public class TokenAspect {@Autowiredprivate ThreadLocalToken threadLocalToken;@Pointcut("execution(public * com.example.emoswxapi.controller.*.*(..)))")public void aspect() {}//环绕时间,调用的方法可以拦截,返回的值也可以拦截@Around("aspect()")public Object around(ProceedingJoinPoint point) throws Throwable {R r = (R) point.proceed(); //方法执行结果String token = threadLocalToken.getToken();//如果ThreadLocal中存在Token,说明是更新的Tokenif (token != null) {r.put("token", token); //往响应中放置TokenthreadLocalToken.clear();}return r;}
}

11、精简返回给客户端的异常内容

@ControllerAdvice可以全局捕获SpringMVC异常
判断异常的类型 后端数据验证异常
未授权异常 你不具有相关权限
EmosException 精简异常内容
普通异常 后端执行异常

package com.example.emoswxapi.config;import com.example.emoswxapi.exception.EmosException;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;//精简返回异常
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {@ResponseBody@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)@ExceptionHandler(Exception.class)public String validExceptionHandler(Exception e) {log.error("执行异常",e);if (e instanceof MethodArgumentNotValidException) {MethodArgumentNotValidException exception = (MethodArgumentNotValidException) e;//将错误信息返回给前台return exception.getBindingResult().getFieldError().getDefaultMessage();}else if(e instanceof EmosException){EmosException exception=(EmosException)e;return exception.getMsg();}else if(e instanceof UnauthorizedException){return "你不具有相关权限";}else {return "后端执行异常";}}
}

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

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

相关文章

【SA8295P 源码分析】97 - QNX AIS Camera 框架介绍 及 Camera 工作流程分析

【SA8295P 源码分析】97 - QNX AIS Camera 框架介绍 及 Camera 工作流程分析 一、QNX AIS Server 框架分析二、QNX Hypervisor / Android GVM 方案介绍三、Camera APP 调用流程分析四、QCarCam 状态转换过程介绍五、Camera 加串-解串 硬件链路分析六、摄像头初始化检测过程介绍…

聊一聊Twitter的雪花算法

什么是Twitter的雪花算法方法&#xff1f; 这是一种在分布式系统中生成唯一ID的解决方案。Twitter在推文、私信、列表等方面使用这种方法。 •ID是唯一且可排序的•ID包含时间信息&#xff08;按日期排序&#xff09;•ID适用于64位无符号整数•仅包含数字值 符号位&#xff08…

【方案】智慧养殖:浅谈视频监控与AI智能识别技术助力奶牛高效智慧养殖

一、方案背景 随着科技的飞速发展&#xff0c;智能化养殖逐渐成为现代畜牧业的发展趋势。人工智能技术、物联网、视频技术、云计算、大数据等新兴技术&#xff0c;正在为奶牛养殖业带来全新的变革。越来越多的牧场、养殖场开始运用新技术来进行智能监管、提高生产效率、降低生…

Python机器学习实战-特征重要性分析方法(2):内置特征重要性(附源码和实现效果)

实现功能 一些模型&#xff0c;如线性回归和随机森林&#xff0c;可以直接输出特征重要性分数。这些显示了每个特征对最终预测的贡献。 实现代码 from sklearn.datasets import load_breast_cancer from sklearn.ensemble import RandomForestClassifier import matplotlib.…

C++笔记之文档术语——将可调用对象作为函数参数

C笔记之文档术语——将可调用对象作为函数参数 相关博文&#xff1a;C笔记之函数对象functors与可调用对象 文章目录 C笔记之文档术语——将可调用对象作为函数参数1.在函数参数中传递可调用对象2.‘在参数中传入可调用对象’和‘将可调用对象作为函数参数’哪个描述更加专业…

vuejs - - - - - 使用code编辑器codemirror

使用code编辑器codemirror 0. 效果图1. 依赖安装2. 组件封装3. 组件使用 0. 效果图 列表实现参考: 列表实现代码 1. 依赖安装 npm install codemirror codemirror-editor-vue3 jsonlint-mod 2. 组件封装 code-mirror-editor.vue <template><VueCodeMirrorclas…

C 初级学习笔记(基础)

目录 1.预处理器指令 预定义宏 预处理器运算符 &#xff08;\&#xff09; 参数化的宏 头文件 .h 引用头文件操作 2.函数&#xff08;标识符&关键字&运算符&#xff09;存储类 函数参数 a. 标识符&关键字 b. 运算符&#xff08;算术、关系、逻辑、位、赋…

软件测试常见术语和名词解释

1. Unit testing (单元测试)&#xff1a;指一段代码的基本测试&#xff0c;其实际大小是未定的&#xff0c;通常是一个函数或子程序&#xff0c;一般由开发者执行。 2. Integration testing (集成测试)&#xff1a;被测试系统的所有组件都集成在一起&#xff0c;找出被测试系统…

Linux 线程(thread)

进程线程区别 创建线程 #include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); -功能&#xff1a;创建一个子线程&#xff0c;一般情况下main函数所在的线程称为主线程&#xff0c;…

WSUS 修补程序管理的替代方法

什么是 WSUS Windows Server Update Services &#xff08;WSUS&#xff09; 是 Microsoft for Windows Server 提供的免费附加组件&#xff0c;WSUS 从Microsoft更新中下载必要的修补程序和更新&#xff0c;并将其分发到 Windows 网络中存在的 Windows 操作系统和相关Microso…

青大数据结构【2022】

关键字&#xff1a; next数组、下三角矩阵、完全二叉树结点、静态分布动态分布、迪杰斯特拉最短路径、二叉排序树失败ASL、排序比较、二叉排序树中序遍历、链表删除最大值 一、单选 二、简答 三、应用 四、算法分析 五、算法设计

分类散点图 stripplot() 加辅助线axhline() 多图合一

分类散点图 stripplot 加辅助线axhline 多图合一 效果图代码 画图没有什么可说的&#xff0c;直接上图 效果图 代码 # 绘制图&#xff0c; 查看是否数值在阈值上 plt.figure(figsize(30, 18)) n 0 for header, value_list in info_dict.items():ref_value_list ref_info_dic…