在前后端分离的项目中我们通常会采用jwttoken的方式来作为跨域身份验证解决方案。

引入依赖

<dependency><groupId>org.crazycake</groupId><artifactId>shiro-redis-spring-boot-starter</artifactId><version>3.2.1</version>
</dependency><!-- jwt -->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>

在配置文件中配置我们的jwt信息

petrichor:jwt:# 加密秘钥secret: f4e2e52034348f86b67cde581c0f9eb5# token有效时长,7天,单位秒expire: 604800header: token

准备一个jwt工具类,用来生成或解析jwt

@Slf4j
@Data
@Component
@ConfigurationProperties(prefix = "petrichor.jwt")// 绑定配置文件中的配置
public class JwtUtils {private String secret;// 盐private long expire;// 有效时长private String header;/*** 生成jwt token*/public String generateToken(long userId) {Date nowDate = new Date();//过期时间Date expireDate = new Date(nowDate.getTime() + expire * 1000);return Jwts.builder().setHeaderParam("typ", "JWT").setSubject(userId+"").setIssuedAt(nowDate).setExpiration(expireDate).signWith(SignatureAlgorithm.HS512, secret).compact();}// 解析public Claims getClaimByToken(String token) {try {return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();}catch (Exception e){log.debug("validate is token error ", e);return null;}}/*** token是否过期* @return  true:过期*/public boolean isTokenExpired(Date expiration) {return expiration.before(new Date());}
}

准备一个JwtToken类用来将生成的jwt保存在token中

// 将JWT保存在token中,shiro默认supports的是UsernamePasswordToken,而我们现在采用了jwt的方式,所以这里我们自定义一个JwtToken
public class JwtToken implements AuthenticationToken {private String token;public JwtToken(String jwt) {this.token = jwt;}@Overridepublic Object getPrincipal() {return token;}@Overridepublic Object getCredentials() {return token;}
}

jwt过滤器

因为 JWT 的整合,我们需要自定义自己的过滤器 JWTFilter

@Component
// 继承的是Shiro内置的AuthenticatingFilter,内置了可以自动登录方法的过滤器
public class JwtFilter extends AuthenticatingFilter {@AutowiredJwtUtils jwtUtils;@Override// 实现登录,生成自定义支持的JwtTokenprotected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {HttpServletRequest request = (HttpServletRequest) servletRequest;String jwt = request.getHeader("Authorization");// 从请求头中拿到JWTif(StringUtils.isEmpty(jwt)) {// 判断JWT是否为空return null;}return new JwtToken(jwt);// 不为空则生成token}@Override// 拦截校验protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {HttpServletRequest request = (HttpServletRequest) servletRequest;String jwt = request.getHeader("Authorization");if(StringUtils.isEmpty(jwt)) {// 当这个请求没有JWT时,就不需要交给shiro做登录处理,直接交给注解进行拦截,可以访问一些无需登录就可以访问的接口return true;} else {// 校验jwt, jwt用Claims对象来存自定义的信息Claims claim = jwtUtils.getClaimByToken(jwt);// 解析if(claim == null || jwtUtils.isTokenExpired(claim.getExpiration())) {throw new ExpiredCredentialsException("token已失效,请重新登录");}// JWT没问题就可以交给shiro执行登录return executeLogin(servletRequest, servletResponse);}}@Override// 登录失败,出现异常时,把异常信息封装然后抛出protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {HttpServletResponse httpServletResponse = (HttpServletResponse) response;Throwable throwable = e.getCause() == null ? e : e.getCause();Result result = Result.fail(throwable.getMessage());String json = JSONUtil.toJsonStr(result);// 将对象转化未jsontry {httpServletResponse.getWriter().print(json);} catch (IOException ioException) {}return false;}
}

代码中的Result类是我们自己封装的一个结果集

@Data
// 统一结果封装
public class Result implements Serializable {private int code; // 200是正常,非200表示异常private String msg;private Object data;public static Result succ(Object data) {return succ(200, "操作成功", data);}public static Result succ(int code, String msg, Object data) {Result r = new Result();r.setCode(code);r.setMsg(msg);r.setData(data);return r;}public static Result fail(String msg) {return fail(400, msg, null);}public static Result fail(String msg, Object data) {return fail(400, msg, data);}public static Result fail(int code, String msg, Object data) {Result r = new Result();r.setCode(code);r.setMsg(msg);r.setData(data);return r;}
}

Realm类

@Component
public class AccountRealm extends AuthorizingRealm {@AutowiredJwtUtils jwtUtils;@AutowiredUserService userService;@Override// 判断这个token是否是JwtToken,让realm支持jwt的凭证校验public boolean supports(AuthenticationToken token) {return token instanceof JwtToken;}@Override// 授权protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return null;}@Override// 认证protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {JwtToken jwtToken = (JwtToken) token;// 前面supports方法已经验证过这个token是JwtTokenString userId = jwtUtils.getClaimByToken((String) jwtToken.getPrincipal()).getSubject();User user = userService.getById(Long.valueOf(userId));if (user == null) {throw new UnknownAccountException("账户不存在");}if (user.getStatus() == -1) {throw new LockedAccountException("账户已被锁定");}AccountProfile profile = new AccountProfile();// 存储用户信息的载体BeanUtil.copyProperties(user, profile);// 将user中的用户信息复制到profilereturn new SimpleAuthenticationInfo(profile, jwtToken.getCredentials(), getName());}
}
@Data
// 登录成功之后返回的一个用户信息的载体
public class AccountProfile implements Serializable {private Long id;private String username;private String avatar;private String email;}

ShiroConfig 配置类

如果考虑到我们的项目后面需要做服务集群与负载均衡,我们就需要将Redis顺带配置进来,shiro的缓存和会话信息我们都存储在Redis当中。所以当集群中的服务需要获得缓存和会话信息数据时,就可以从redis中获取。这样就能实现会话共享。

@Configuration
public class ShiroConfig {@AutowiredJwtFilter jwtFilter;@Bean// shiro整合redis,为了解决shiro的权限数据和会话信息能保存到redis中,实现会话共享public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();// 引入 redisSessionDAOsessionManager.setSessionDAO(redisSessionDAO);return sessionManager;}@Bean// shiro整合redis,为了解决shiro的权限数据和会话信息能保存到redis中,实现会话共享public DefaultWebSecurityManager securityManager(AccountRealm accountRealm,SessionManager sessionManager,RedisCacheManager redisCacheManager) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(accountRealm);//引入 sessionManagersecurityManager.setSessionManager(sessionManager);// 引入 redisCacheManagersecurityManager.setCacheManager(redisCacheManager);return securityManager;}@Beanpublic ShiroFilterChainDefinition shiroFilterChainDefinition() {DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();Map<String, String> filterMap = new LinkedHashMap<>();filterMap.put("/**", "jwt");// 拦截,需要经过jwt过滤器才可以chainDefinition.addPathDefinitions(filterMap);return chainDefinition;}@Bean("shiroFilterFactoryBean")public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,ShiroFilterChainDefinition shiroFilterChainDefinition) {ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();shiroFilter.setSecurityManager(securityManager);Map<String, Filter> filters = new HashMap<>();filters.put("jwt", jwtFilter);// 注入jwt过滤器shiroFilter.setFilters(filters);Map<String, String> filterMap = shiroFilterChainDefinition.getFilterChainMap();// 获取shiroFilterChainDefinition方法中设置的拦截shiroFilter.setFilterChainDefinitionMap(filterMap);return shiroFilter;}}

跨域处理

在前后端分离项目中,因为这时候前端和后端的代码是在不同机器上运行的,两个地址不在一个域名下,这个时候前端脚本在进行ajax访问的时候浏览器就会报跨域相关的错误。跨域请求会被浏览器的同源策略限制,所以我们要在服务器配置跨域处理。

在JwtFilter类中添加

@Override
// 跨域处理
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpServletRequest = WebUtils.toHttp(request);HttpServletResponse httpServletResponse = WebUtils.toHttp(response);httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));// 跨域时会首先发送一个OPTIONS请求,这里我们给OPTIONS请求直接返回正常状态if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());return false;}return super.preHandle(request, response);
}

跨域配置

/*** 解决跨域问题*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOriginPatterns("*").allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS").allowCredentials(true).maxAge(3600).allowedHeaders("*");}
}

Springboot整合Shiro+JWT相关推荐

  1. 【Gorho】springboot整合Shiro+jwt 前后端分离 超级详细的shiro+jwt鉴权过程

    shiro+jwt+springboot 说在前面 简介 项目环境(pom.xml) 项目结构(各种包和类) 鉴权流程 具体代码 配置Shiro 配置JWTUtils 定义JwtFilter 定义Jw ...

  2. springboot整合shiro + jwt + redis实现权限认证(上手即用)

    目录 前言 项目结构 依赖导入 建数据库表 建表语句 使用插件生成增删改查 添加MyRealm 添加ShiroConfig 添加JwtFilter JWT相关得类 JwtToken JwtAudien ...

  3. 补习系列(6)- springboot 整合 shiro 一指禅

    欢迎添加华为云小助手微信(微信号:HWCloud002 或 HWCloud003),输入关键字"加群",加入华为云线上技术讨论群:输入关键字"最新活动",获取华 ...

  4. SpringBoot 整合Shiro 一指禅

    目标 了解ApacheShiro是什么,能做什么: 通过QuickStart 代码领会 Shiro的关键概念: 能基于SpringBoot 整合Shiro 实现URL安全访问: 掌握基于注解的方法,以 ...

  5. springboot整合shiro

    springboot整合shiro 导入依赖 <!-- shiro鉴权框架--> <dependency><groupId>org.apache.shiro< ...

  6. 补习系列- springboot 整合 shiro 一指禅

    目标 了解ApacheShiro是什么,能做什么: 通过QuickStart 代码领会 Shiro的关键概念: 能基于SpringBoot 整合Shiro 实现URL安全访问: 掌握基于注解的方法,以 ...

  7. 补习系列(6)-SpringBoot 整合Shiro 一指禅

    目标 了解ApacheShiro是什么,能做什么: 通过QuickStart 代码领会 Shiro的关键概念: 能基于SpringBoot 整合Shiro 实现URL安全访问: 掌握基于注解的方法,以 ...

  8. springboot整合shiro使用shiro-spring-boot-web-starter

    此文章仅仅说明在springboot整合shiro时的一些坑,并不是教程 增加依赖 <!-- 集成shiro依赖 --> <dependency><groupId> ...

  9. springboot整合shiro和session的详细过程和自定义登录拦截器

    文章目录 1.shiro依赖 2.shiro配置 shiro过滤器配置: 关联自定义的其他管理器 自定义会话工厂: 3.登陆时记录用户信息 4.shiro一些工具类的学习 5.自定义登录拦截器 shi ...

最新文章

  1. ajax报403错,django使用ajax post数据出现403错误如何解决
  2. CentOS 7.x自定义开机启动设置
  3. Python中常见的数据类型小结
  4. LabelImg操作及快捷键
  5. 猜数字(HDU-2178)
  6. python爬虫接口_python爬虫之百度API调用方法
  7. 直接在电脑屏幕上画画_电脑屏幕保护膜有那些你知道吗?
  8. php xml构造,C++_C语言实现xml构造解析器,纯C实现xml构造解析器,所有实 - phpStudy...
  9. 网络安全:系统进程的基本概述
  10. Windows 内核会换为 Linux 吗?
  11. CCF201903-2 二十四点游戏(JAVA版)
  12. 数字信号处理-希尔伯特变换
  13. Android Studio设置Eclipse快捷键
  14. 一纬度横直线等于多公里_【地理】高中地理必修一知识点总结,考前必看
  15. ICP许可证的作用是什么?ICP许可证可以转让吗?
  16. html 5与css 3权威指南 第2版 pdf,html5与css3权威指南
  17. double浮点数转字符串算法
  18. C语言开发过程中段错误处理方法之经典
  19. Python培训班多少钱
  20. Unity 粒子制作简单飞舞纸片特效

热门文章

  1. 【题解】信使(msner)
  2. C++:实现量化Path generation路径生成测试实例
  3. oj1974: C语言实验——输出字符串
  4. ClickHouse基础
  5. Python的GUI开发:小试wxPython(上)
  6. 医学计算机专业考研,网评十大最痛苦专业:计算机、医学上榜
  7. Java小白读完这篇文章秒变大神,P8大师的赞叹不已
  8. death coming一直连接服务器,Death Coming死活进不去怎么解决
  9. 北京语言大学计算机大赛获奖,学堂在线携手北京语言大学 助力中国大学生计算机设计大赛...
  10. ASP.NET饭店管理系统