此篇基于 SpringBoot 整合 Shiro & Jwt 进行鉴权 相关代码编写与解析

首先我们创建 JwtFilter 类 继承自 BasicHttpAuthenticationFilter

org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter

此类是个过滤器,后期配置Shiro时会引用到

重写4个重要的方法 其执行顺序亦是如下

1. preHandle(..)  我理解为是前置处理
2. isAccessAllowed(..) 请求是否被允许
3. isLoginAttempt(..) 是否是尝试登陆,去查实[请求头]里是否包含[Authorization]
4. executeLogin(..) 执行登陆操作 其会调用getSubject(request, response).login(jwtToken)进行登陆验权

preHandle 方法

可以理解为前置处理,我们在这进行一些跨域的必要设置

HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) 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
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);

isAccessAllowed 方法

其实这个方法我们会手动调用 isLoginAttempt 方法及 executeLogin 方法

isLoginAttempt判断用户是否想尝试登陆,判断依据为请求头中是否包含 Authorization 授权信息,也就是 Token 令牌

如果有则再执行executeLogin方法进行登陆验证操作,就是我们整合后的鉴权操作,因为用Token抛开了Session,此处就相当于是否存在Session的操作,存在则表明登陆成功,不存在则需要登陆操作,或者Session过期需要重新登陆是一个原理性质,此方法在这里是验证JwtToken是否合法,不合法则返回401需要重新登陆

不合法的原因大致一这些

  • Token不正确 可以是被篡改 不能解析
  • Token过期
  • Token已被注销(需自己去实现)
    @Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { if (this.isLoginAttempt(request, response)) { // 进行验证登陆JWT 可以trycatch下可以捕获Token过期异常等信息
this.executeLogin(request, response); } else {
// 没有携带Token
HttpServletRequest httpRequest = (HttpServletRequest)request;
String httpMethod = httpRequest.getMethod();
String requestURI = httpRequest.getRequestURI();
logger.info("当前请求 {} Authorization属性(Token)为空 请求类型 {}", requestURI, httpMethod);
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
try (PrintWriter out = httpServletResponse.getWriter()) { out.append("用户认证失败" + msg);
} catch (IOException e) {
//logger.error("直接返回Response信息出现IOException异常", e);
}
return false;
}
return true;
}

isLoginAttempt 方法 是否尝试登陆

    @Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
String token = this.getAuthzHeader(request);
return token != null;
}

executeLogin 方法

执行登陆操作 其实就是对 Token 进行验证操作,这里我们需要另外一个类去处理 Token 验证 (MyRealm 类的doGetAuthenticationInfo方法)

    @Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) {
String token = this.getAuthzHeader(request);
//这里需要自己实现对Token验证操作
JwtToken jwtToken = new JwtToken(token);
getSubject(request, response).login(jwtToken);//如果登陆失败会抛出异常(Token鉴权失败)
return true;
}

创建 JwtToken 类 继承自AuthenticationToken

org.apache.shiro.authc.AuthenticationToken

这里本来是存取用户名及密码的字段用来登陆,现在因为是Token不存在需要携带用户名 所以把字段都设置成Token

public class JwtToken implements AuthenticationToken {
private static final long serialVersionUID = -634556778977L; private String token; public JwtToken(String token) {
this.token = token;
} @Override
public Object getPrincipal() {
return token;
} @Override
public Object getCredentials() {
return token;
}
}

JwtConfig类的创建

这个类用于创建 Token 及解码 Token 里的信息

@ConfigurationProperties(prefix = "config.jwt")
@Component
public class JwtConfig { private String secret;
private long expire;
private String header; /**
* 生成token
*
* @param subject
* @return
*/
public String createToken(String subject) {
Date nowDate = new Date();
//过期时间 默认单位 (天)
Date expireDate = new Date(nowDate.getTime() + expire * 1000 * 60 * 60 * 24); return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(subject)
.setIssuedAt(nowDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
} /**
* 获取token中注册信息
*
* @param token
* @return
*/
public Claims getTokenClaim(String token) {
try {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
} catch (Exception e) {
return null;
}
} /**
* 获取token 解析信息
* @param token
* @return
*/
public String getTokenInfo(String token){
Claims claims=getTokenClaim(token);
if(claims==null){
throw new RuntimeException("Token 信息异常");
}
String tokenInfo=claims.getSubject();
if(StringUtils.isBlank(tokenInfo)){
throw new RuntimeException("Token 信息异常 解析值为空");
}
return tokenInfo;
} /**
* 验证token是否过期失效
*
* @param expirationTime
* @return
*/
public boolean isTokenExpired(Date expirationTime) {
return expirationTime.before(new Date());
} /**
* 获取token失效时间
*
* @param token
* @return
*/
public Date getExpirationDateFromToken(String token) {
return getTokenClaim(token).getExpiration();
} /**
* 获取用户名从token中
*/
public String getUsernameFromToken(String token) {
return getTokenClaim(token).getSubject();
} /**
* 获取jwt发布时间
*/
public Date getIssuedAtDateFromToken(String token) {
return getTokenClaim(token).getIssuedAt();
} ...set get
}

配置文件信息

config.jwt.secret=123!@#
config.jwt.expire=15
config.jwt.header=Authorization

实现自己的Realm 创建MyRealm类 继承自AuthorizingRealm

import io.jsonwebtoken.Claims;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.stereotype.Component; import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; /**
* @author zy
*/
@Component
public class MyRealm extends AuthorizingRealm { private static Logger logger = LogManager.getLogger(MyRealm.class); /**
* 这里需要实现自己的用户登陆验证信息及 数据权限相关的信息获取
*/
@Resource
private UserService userService; @Resource
private JwtConfig jwtConfig; @Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
} /**
* 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("====================数据权限认证====================");
String username = jwtConfig.getTokenInfo(principals.toString());
UserInfo user = userService.getUserAndRole(username); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); List<Role> roles = user.getRoles(); if (roles.isEmpty()) {
logger.warn("该用户 {} 没有角色,默认赋予user角色", username);
Role r = new Role();
r.setRole("user");
roles.add(r);
} /**
* 这里是我自己实现的数据权限认证可做参考用
*/
Set<String> permissionSet = new HashSet<>(roles.size() * 16);
for (Role role : roles) {
List<Permission> temList = role.getPermissions();
if (temList == null || temList.isEmpty()) {
logger.warn("该角色 {} 没有赋予相应权限信息", role.getRole());
continue;
}
for (Permission tem : temList) {
if (tem.getId().indexOf(":") > -1) {
permissionSet.add(tem.getId());
}
}
} simpleAuthorizationInfo.setRoles(roles.stream().map(Role::getRole).collect(Collectors.toSet()));
simpleAuthorizationInfo.setStringPermissions(permissionSet);
return simpleAuthorizationInfo;
} /**
* 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) {
logger.info("====================Token认证====================");
String token = auth.getCredentials().toString();
Claims claims = jwtConfig.getTokenClaim(token);
if (claims == null) {
throw new AuthenticationException("解析Token异常");
}
if (jwtConfig.isTokenExpired(claims.getExpiration())) {
throw new AuthenticationException("Token过期");
}
String username = claims.getSubject();
if (username == null || username == "") {
logger.error("Token中帐号为空");
throw new AuthenticationException("Token中帐号为空");
}
UserInfo user = userService.getUserByName(username);
if (user == null) {
throw new AuthenticationException("该帐号不存在");
}
DataContextHolder.setCurrentUser(user);
return new SimpleAuthenticationInfo(token, token, getName());
}
}

配置Shiro 创建ShiroConfig类

LifecycleBeanPostProcessor这个类并不一定要手动创建,手动创建可能存在一些问题。我遇见的坑就在这里。至于原因希望大家不吝赐教

import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate; import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map; /**
* @author zy
*/
@Configuration
public class ShiroConfig { @Bean("securityManager")
public DefaultWebSecurityManager getManager(MyRealm myRealm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(myRealm); // 关闭Shiro自带的session
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
manager.setSubjectDAO(subjectDAO); // 设置自定义Cache缓存 根据项目情况而设置
manager.setCacheManager(new CustomCacheManager());
return manager;
} /**
* 添加自己的过滤器,自定义url规则
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager, RedisTemplate<String, String> redisTemplate, JwtConfig jwtConfig) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); //配置过滤器 对 『anon』不进行拦截
Map<String, Filter> filterMap = new HashMap<>(3);
filterMap.put("anon", new AnonymousFilter());
filterMap.put("jwt", new JwtFilter(redisTemplate, jwtConfig)); factoryBean.setFilters(filterMap);
factoryBean.setSecurityManager(securityManager); factoryBean.setLoginUrl("/login"); LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>(16); // 配置不过滤
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/testpage", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/login/**", "anon");
filterChainDefinitionMap.put("/unauthorized/**", "anon");
// swagger
filterChainDefinitionMap.put("/swagger**/**", "anon");
filterChainDefinitionMap.put("/v2/**", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon"); filterChainDefinitionMap.put("/**", "jwt"); factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return factoryBean;
} // @Bean
// public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
// return new LifecycleBeanPostProcessor();
// } @Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}

Shiro&Jwt验证的更多相关文章

  1. 踩坑之路---JWT验证

    使用JWT验证客户的携带的token 客户端在请求接口时,需要在request的head中携带一个token令牌 服务器拿到这个token解析获取用户资源,这里的资源是非重要的用户信息 目前我的理解, ...

  2. SpringBoot2.0+Shiro+JWT 整合

    SpringBoot2.0+Shiro+JWT 整合 JSON Web Token(JWT)是一个非常轻巧的规范.这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息. 我们利用一定的编 ...

  3. 基于Shiro,JWT实现微信小程序登录完整例子

    小程序官方流程图如下,官方地址 : https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html ...

  4. spring-boot-plus集成Shiro+JWT权限管理

    SpringBoot+Shiro+JWT权限管理 Shiro Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理. 使用Shiro的易于理解的API,您可以 ...

  5. 使用Shiro+JWT完成的微信小程序的登录(含讲解)

    使用Shiro+JWT完成的微信小程序的登录 源码地址https://github.com/Jirath-Liu/shiro-jwt-wx 微信小程序用户登陆,完整流程可参考下面官方地址,本例中是按此 ...

  6. Shiro (Shiro + JWT + SpringBoot应用)

    Shiro (Shiro + JWT + SpringBoot应用) 目录 Shiro (Shiro + JWT + SpringBoot应用) 1.Shiro的简介 2.Shiro + JWT + ...

  7. SpringMVC+Apache Shiro+JPA(hibernate)案例教学(三)给Shiro登录验证加上验证码

    序: 给Shiro加入验证码,有多种方式,当然你也可以通过继承修改FormAuthenticationFilter类,通过Shiro去验证验证码.具体实现请百度: 应用Shiro到Web Applic ...

  8. golang学习笔记10 beego api 用jwt验证auth2 token 获取解码信息

    golang学习笔记10 beego api 用jwt验证auth2 token 获取解码信息 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放 ...

  9. shiro 身份验证

    shiro身份验证: 参考链接:http://jinnianshilongnian.iteye.com/blog/2019547 即在应用中证明是本人进行操作,一般通过用户名来证明 在shiro中,用 ...

随机推荐

  1. 2、gitlab使用及权限管理

    目录 1.创建用户组... 2 2.创建用户... 4 3.新建项目... 5 4.linux端访问项目... 6 4.1 通过ssh方式访问... 6 4.2 通过http访问... 9 5.win ...

  2. 01 : Java入门

    Java概述 Java之父·詹姆斯·高斯林(James Gosling) 1977年获得了加拿大卡尔加里大学计算机科学学士学位,1983年获得了美国卡内基梅隆大学计算机科学博士学位,毕业后到IBM工作 ...

  3. vue学习指南:第十二篇(详细) - Vue的 路由 第二篇 ( 路由按需加载(懒加载))

    各位朋友 因 最近工作繁忙,小编停更了一段时间,快过年了,小编祝愿 大家 事业有成 学业有成 快乐健康 2020开心过好每一天.从今天开始 我会抽时间把 Vue 的知识点补充完整,以及后期会带给大家更 ...

  4. Python 排序---sort与sorted学习(这是摘录别人的资源总结,自己可临摹学习)

    第一种:内建方法sort() 可以直接对列表进行排序 用法: list.sort(func=None, key=None, reverse=False(or True)) 对于reverse这个boo ...

  5. 保存数据到文件的模块(json,pickle,shelve,configparser,xml)_python

    一.各模块的主要功能区别 json模块:将数据对象从内存中完成序列化存储,但是不能对函数和类进行序列化,写入的格式是明文.  (与其他大多语言交互的类型) pickle模块:将数据对象从内存中完成序列 ...

  6. 【15】【有点特殊的dp】 剪绳子

    题目 给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m.n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m] .请问 k[0]k[1]...* ...

  7. windows查询端口,杀进程

    C:\Users\chenquan>tasklist | findstr 4720Thunder.exe 4720 Console 1 3,456 K C:\Users\chenquan> ...

  8. spring的IOC过程剖析

    先看下类的关系图,看图说话

  9. windows10 +ubuntu双系统

    1,安装之前的准备: 制作启动盘 确定给ubuntu多少分区并且清理为free状态 确定电脑的开机引导方式,传统方式引号和uefi引导并不一样,因此我们需要根据引导方式选择新系统制作什么样的启动盘 在 ...

  10. Java上传图片到Ftp,包含上传后文件大小为0的问题和Properties配置文件的读取

    准备工作:需要使用coomos-net jar包.下载地址 一. 上传图片到FTP,文件大小为0的问题,解决:将ftp模式修改为Passive模式就可以了. //将ftp模式修改为Passive模式 ...