SpringBoot Shiro JWT

1.建表

DDL.sql

CREATE TABLE `t_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_name` varchar(30) NOT NULL DEFAULT '',
`password` varchar(32) NOT NULL DEFAULT '',
`salt` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; CREATE TABLE `t_sys_role` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`role_name` varchar(20) DEFAULT NULL COMMENT '角色名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表'; CREATE TABLE `t_sys_permission` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL COMMENT '权限名称解释',
`url` varchar(60) DEFAULT NULL COMMENT '权限',
`permission` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表'; CREATE TABLE `t_user_role` (
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(11) DEFAULT NULL COMMENT '用户id',
`role_id` bigint(11) DEFAULT NULL COMMENT '角色id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色对应表'; CREATE TABLE `t_sys_permission` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL COMMENT '权限名称解释',
`permission` varchar(30) DEFAULT NULL,
`url` varchar(60) DEFAULT NULL COMMENT '权限',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';

DML.sql

-- 创建角色
INSERT INTO `t_sys_role` VALUES (null, '管理员');
INSERT INTO `t_sys_role` VALUES (null, '普通用户'); -- 创建用户
INSERT INTO `t_user` VALUES (null, 'admin', '856aea89ad509f163284abb75579dcfc', 'admin');
INSERT INTO `t_user` VALUES (null, 'user', 'ea6151b14a3b64331d6c33e645fccef6', 'user'); -- 创建权限
INSERT INTO `t_sys_permission` VALUE(null, '超级用户权限', 'root:all', '/**');
INSERT INTO `t_sys_permission` VALUE(null, '查看用户列表', 'user:list', '/admin/userList'); -- 给用户配置角色
INSERT INTO `t_user_role` VALUE(null, 14, 1);
INSERT INTO `t_user_role` VALUE(null, 15, 2); -- 给角色配置权限
INSERT INTO `t_role_permission` VALUE(null, 1, 1);
INSERT INTO `t_role_permission` VALUE(null, 2, 2);

4. springboot 项目加入shiro依赖

		<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.4.0</version>
</dependency> <!-- 这个用于开启缓存功能 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency> <!-- jwt jar-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>

5. 配置shiro

package com.grady.shirodemo.config;

import com.grady.shirodemo.filter.JWTFilter;
import com.grady.shirodemo.realm.JWTRealm;
import com.grady.shirodemo.realm.UserRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
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 org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import javax.servlet.Filter;
import java.util.*; @Configuration
public class ShiroConfig { /**
* 最重要的一个bean
**/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 加入自定义的拦截器,会拦截(所有被“jwt”标记的方法)请求到url访问的方法
Map<String, Filter> filterMap = new HashMap<>();
filterMap.put("jwt", new JWTFilter());
shiroFilterFactoryBean.setFilters(filterMap); shiroFilterFactoryBean.setSecurityManager(securityManager);
// 设置未登录跳转到的url
shiroFilterFactoryBean.setLoginUrl("/toLogin");
shiroFilterFactoryBean.setUnauthorizedUrl("/unAuthorize"); // 设置拦截器与url映射关系map
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/unAuthorize", "anon");
// 所有请求通过我们自己的JWT Filter
// 注意越先设置的url会越先生效,原因你懂的(LinkedHashMap 有先后顺序)
filterChainDefinitionMap.put("/**", "jwt");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean;
} // 这里是配置密码转换逻辑matcher(此处会把传进来的密码以MD5的算法序列化5次)
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("MD5");
matcher.setHashIterations(5);
matcher.setStoredCredentialsHexEncoded(true);
return matcher;
} // 核心bean,所有的realm都要在这里加进去
@Bean
public DefaultWebSecurityManager securityManager(UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setAuthenticator(modularRealmAuthenticator());
// realm只能在你setAuthenticator下设置入securityManager,否则不生效
List<Realm> list = new ArrayList<>();
list.add(userRealm());
list.add(jwtRealm());
securityManager.setRealms(list);
// 这里虽是方法调用,但是引用地址都是一样的,所以是同一个对象
securityManager.setCacheManager(ehCacheManager()); // 关闭Shiro自带的session
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO); return securityManager;
} // 这是用账号密码来校验的realm
@Bean
public UserRealm userRealm() {
UserRealm realm = new UserRealm();
//启用授权缓存
realm.setAuthorizationCachingEnabled(true);
realm.setCredentialsMatcher(hashedCredentialsMatcher());
return realm;
} // 这是通过token来校验的realm
@Bean
public JWTRealm jwtRealm() {
return new JWTRealm();
} /**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
} /**
* 配置异常处理器,无权限时会走这里
* @return
*/
@Bean
public SimpleMappingExceptionResolver resolver() {
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "/unAuthorize");
resolver.setExceptionMappings(properties);
return resolver;
} /**
* shiro缓存管理器;
* 需要添加到securityManager中
* @return
*/
@Bean
public EhCacheManager ehCacheManager(){
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
return cacheManager;
} /**
* 系统自带的Realm管理,主要针对多realm
* */
@Bean
public ModularRealmAuthenticator modularRealmAuthenticator(){
ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
// 这里配置多个realm只要一个通过就算通过(会串型执行)
modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
return modularRealmAuthenticator;
}
}

6. 完成两个realm的编写

UserRealm.java

package com.grady.shirodemo.realm;

import com.grady.shirodemo.entity.model.User;
import com.grady.shirodemo.entity.model.UserPermission;
import com.grady.shirodemo.entity.model.UserRole;
import com.grady.shirodemo.service.PermissionService;
import com.grady.shirodemo.service.UserService;
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.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors; public class UserRealm extends AuthorizingRealm { @Autowired
UserService userService; @Autowired
PermissionService permissionService; @Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
} /**
* 用于授权,进行权限校验
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("UserRealm : doGetAuthorizationInfo 鉴权");
// 这里即使抛出runtime异常也会被shiro Advicefilter 捕获
User user = (User) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
List<UserRole> roleList = permissionService.findRolesByUserId(user.getId());
Optional.ofNullable(roleList).ifPresent(roles -> {
authorizationInfo.addRoles(roles.stream().map(UserRole::getRoleName).collect(Collectors.toList()));
List<String> permList = new ArrayList<>();
roles.forEach(role -> {
// 这里即使抛出runtime异常也会被shiro Advicefilter 捕获
List<UserPermission> permissions = permissionService.findPermissionByRoleId(role.getId());
Optional.ofNullable(permissions).ifPresent(perms -> {
permList.addAll(perms.stream().map(UserPermission::getPermission).collect(Collectors.toList()));
});
});
authorizationInfo.addStringPermissions(permList);
});
return authorizationInfo;
} /**
* 进行身份认证,即登录
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("UserRealm : doGetAuthenticationInfo 认证");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
User user = Optional.ofNullable(userService.findUserByUserName(token.getUsername()))
.orElseThrow(() -> new UnknownAccountException("无此用户"));
//用用户名作为唯一盐值,确保序列化后的MD5值唯一
ByteSource salt = ByteSource.Util.bytes(user.getUserName());
AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), salt, getName());
return info;
}
}

JWTRealm.java

package com.grady.shirodemo.realm;

import com.grady.shirodemo.entity.model.User;
import com.grady.shirodemo.jwt.JWTToken;
import com.grady.shirodemo.service.PermissionService;
import com.grady.shirodemo.service.UserService;
import com.grady.shirodemo.utils.JWTUtil;
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 java.util.Optional; public class JWTRealm extends AuthorizingRealm { @Autowired
UserService userService; @Autowired
PermissionService permissionService; @Override
public boolean supports(AuthenticationToken token) {
return token instanceof JWTToken;
} /**
* 用于授权,进行权限校验
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("JWTRealm : doGetAuthorizationInfo 鉴权");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
return authorizationInfo;
} @Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
System.out.println("JWTRealm : doGetAuthenticationInfo 认证");
String token = (String) auth.getCredentials();
String username = JWTUtil.getUsername(token);
if (username == null) {
throw new AuthenticationException("token invalid");
}
User user = Optional.ofNullable(userService.findUserByUserName(username))
.orElseThrow(() -> new UnknownAccountException("无此用户"));
if (!JWTUtil.verify(token, username, user.getPassword())) {
throw new AuthenticationException("Username or password error");
}
return new SimpleAuthenticationInfo(user, token, getName());
}
}

7. 配置Filter

package com.grady.shirodemo.filter;

import com.grady.shirodemo.jwt.JWTToken;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; public class JWTFilter extends BasicHttpAuthenticationFilter { private static final String TOKEN = "token"; /**
* 判断是否允许访问
* @param request
* @param response
* @param mappedValue
* @return
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (isLoginAttempt(request, response)) {
try {
executeLogin(request, response);
return true;
} catch (Exception e) {
e.printStackTrace();
// 如果responseUnAuthorize 重定向的url也是要被拦截的话会陷入死循话
responseUnAuthorize(request, response);
return false;
}
}
//如果请求头不存在 Token,则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 true
return true;
} @Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
return true;
} @Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String authorization = httpServletRequest.getHeader(TOKEN);
JWTToken token = new JWTToken(authorization);
// 提交给realm进行登入,如果错误他会抛出异常并被捕获
getSubject(request, response).login(token);
// 如果没有抛出异常则代表登入成功,返回true
return true;
} @Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
HttpServletRequest req = (HttpServletRequest) request;
String token = req.getHeader(TOKEN);
return token != null;
} private void responseUnAuthorize(ServletRequest request, ServletResponse resp) {
try {
HttpServletResponse httpServletResponse = (HttpServletResponse) resp;
httpServletResponse.sendRedirect("/unAuthorize");
} catch (IOException e) {
e.printStackTrace();
}
}
}

8. token 相关

JWToken

package com.grady.shirodemo.jwt;

import org.apache.shiro.authc.AuthenticationToken;

public class JWTToken implements AuthenticationToken {

    /**
* 密钥
*/
private String token; public JWTToken(String token) {
this.token = token;
} @Override
public Object getPrincipal() {
return token;
} @Override
public Object getCredentials() {
return token;
}
}

JWTUtil

package com.grady.shirodemo.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT; import java.io.UnsupportedEncodingException;
import java.util.Date; public class JWTUtil { // 过期时间30天
private static final long EXPIRE_TIME = 24 * 60 * 30 * 1000; private static final String CLAIM_STR = "username";
/**
* 校验token是否正确
*
* @param token 密钥
* @param username 登录名
* @param password 密码
* @return
*/
public static boolean verify(String token, String username, String password) {
try {
// 这里与密码无关,是生成(或确认)token的算法
Algorithm algorithm = Algorithm.HMAC256(password);
JWTVerifier verifier = JWT.require(algorithm).withClaim(CLAIM_STR, username).build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception e) {
return false;
}
} /**
* 获取登录名
*
* @param token
* @return
*/
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim(CLAIM_STR).asString();
} catch (JWTDecodeException e) {
return null;
}
} /**
* 生成token签名,指定时间后过期,一经生成不可修改,令牌在指定时间内一直有效
* @param userNo 用户编号
* @param secret 用户的密码
* @return 加密的token
*/
public static String sign(String userNo, String secret) {
try {
Date date = new Date(System.currentTimeMillis()+EXPIRE_TIME);
// 这里与密码无关,是生成token的算法
Algorithm algorithm = Algorithm.HMAC256(secret);
// 附带username信息
return JWT.create()
.withClaim(CLAIM_STR, userNo)
.withExpiresAt(date)
.sign(algorithm);
} catch (UnsupportedEncodingException e) {
return null;
}
}
}

Springboot shiro JWT集成总结的更多相关文章

  1. springboot+shiro+jwt

    1.添加依赖 <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-sp ...

  2. SpringBoot+Shiro+JWT前后端分离实现用户权限和接口权限控制

    1. 引入需要的依赖 我使用的是原生jwt的依赖包,在maven仓库中有好多衍生的jwt依赖包,可自己在maven仓库中选择,实现大同小异. <dependency> <groupI ...

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

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

  4. 基于shiro+jwt的真正rest url权限管理,前后端分离

    代码地址如下:http://www.demodashi.com/demo/13277.html bootshiro & usthe bootshiro是基于springboot+shiro+j ...

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

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

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

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

  7. spring-boot+mybatisPlus+shiro的集成demo 我用了5天

    spring-boot + mybatis-plus + shiro 的集成demo我用了五天 关于shiro框架,我还是从飞机哪里听来的,就连小贱都知道,可我母鸡啊.简单百度了下,结论很好上手,比s ...

  8. 在前后端分离的SpringBoot项目中集成Shiro权限框架

    参考[1].在前后端分离的SpringBoot项目中集成Shiro权限框架 参考[2]. Springboot + Vue + shiro 实现前后端分离.权限控制   以及跨域的问题也有涉及

  9. 教你 Shiro + SpringBoot 整合 JWT

    本篇文章将教大家在 shiro + springBoot 的基础上整合 JWT (JSON Web Token) 如果对 shiro 如何整合 springBoot 还不了解的可以先去看我的上一篇文章 ...

随机推荐

  1. Java基础-并发篇

    3.1. JAVA 并发知识库 3.2. JAVA 线程实现/创建方式 3.2.1. 继承 Thread 类 ​ Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例. ...

  2. React技巧之字符串插值

    原文链接:https://bobbyhadz.com/blog/react-string-interpolation 作者:Borislav Hadzhiev 正文从这开始~ 总览 在React中,使 ...

  3. 女朋友说:你要搞懂了MySQL三大日志,我就让你嘿嘿嘿!

    1. 背景 MySQL实现事务.崩溃恢复.集群的主从复制,底层都离不开日志,所以日志是MySQL的精华所在.只有了解MySQL日志,才算是彻底搞懂MySQL. 今天一灯就带你深入浅出的学习MySQL的 ...

  4. CesiumJS 2022^ 源码解读[7] - 3DTiles 的请求、加载处理流程解析

    目录 1. 3DTiles 数据集的类型 2. 创建瓦片树 2.1. 请求入口文件 2.2. 创建树结构 2.3. 瓦片缓存机制带来的能力 3. 瓦片树的遍历更新 3.1. 三个大步骤 3.2. 遍历 ...

  5. 详解SQL中Groupings Sets 语句的功能和底层实现逻辑

    摘要:本文首先简单介绍 Grouping Sets 的用法,然后以 Spark SQL 作为切入点,深入解析 Grouping Sets 的实现机制. 本文分享自华为云社区<深入理解 SQL 中 ...

  6. scrapy框架入门

    scrapy迄今为止依然是世界上最好用,最稳定的爬虫框架,相比于其他直接由函数定义的程序, scrapy使用了面向对象并对网页请求的过程分成了很多个模块和阶段,实现跨模块和包的使用,大大提升了代码的稳 ...

  7. Vmware虚拟机硬件兼容性

    All virtual machines have a hardware version. The hardware version indicates which virtual hardware ...

  8. Modeling Conversation Structure and Temporal Dynamics for Jointly Predicting Rumor Stance and Veracity(ACL-19)

    记录一下,论文建模对话结构和时序动态来联合预测谣言立场和真实性及其代码复现. 1 引言 之前的研究发现,公众对谣言消息的立场是识别流行的谣言的关键信号,这也能表明它们的真实性.因此,对谣言的立场分类被 ...

  9. 传统 API 管理与测试过程正面临严峻的挑战

    随着测试左移思想的引入, API (应用程序编程接口)经济的飞速增长导致对 API 管理平台的需求相应增加.越来越多的企业注重并关注接口测试.单纯的做接口测试或者做好接口测试的本质工作其实并不复杂: ...

  10. 2022-7-15 pan小堂 数组排序算法

    二分查找(理解) public ych class{ public static void main(String[] args){ ///运用二分查找需要 数组在的值是递升的 int[] arr1 ...