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. RPA应用场景-考勤审批

    场景概述 考勤审批 所涉系统名称 考勤系统,微信 人工操作(时间/次) 5分钟 所涉人工数量 43 操作频率 不定时 场景流程 1.客户领导长期出差,又不想对考勤系统做深度开发: 2.员工请假后,领导 ...

  2. Nginx通过bat文件快速启动停止

    新建文本文件NginxRun.bat.(名字无所谓,后缀名得是bat) 将以下代码复制到bat文件中即可. @echo off ::进入D盘 d: ::进入nginx目录 这里是自己的nginx目录 ...

  3. 用 40 块搞个游戏机「GitHub 热点速览 v.22.27」

    作者:HelloGitHub-小鱼干 最便宜的小霸王游戏机都超过了五十,但是现在有了 PicoBoot 你用 40 块的树莓派就能搞出个任天堂游戏机(NGC).PicoBoot 替换了 NGC 的 I ...

  4. Python 用configparser读写ini文件

    一.configparser 简介Python用于读写ini文件的一个官方标准库.具体详见官网链接 二.configparser 部分方法介绍 方法 描述 read(filenames) filesn ...

  5. putchar与getchar

    #include <stdio.h>#include <stdlib.h>void myputs(char*p) //此处的*号是标志,标志这P是一个指针{ if(p==NUL ...

  6. Linux教我们丢掉键盘

    前言 本篇博客并非博主编写,而是跟着黑马教程整理的. 因为Linux都是一些操作命令,也不想深入研究,所以就用此博客记录一下平时的基本知识,方便日后查找. 一.常用命令 命令 对应英文 作用 ls l ...

  7. 手机APP无法抓包HTTPS解决方案

    问题表现:某个APP的HTTPS流量抓取不到,Fiddler报错,但可以正常抓取其它的HTTPS流量 可能原因: 1.Flutter应用,解决方案:https://www.cnblogs.com/lu ...

  8. Kubuntu安装字体

    打开设置,选择字体-字体管理器,再把网上下载好的ttf字体包解压,选择安装即可.(建议选为系统字体) Kubuntu20.04LTS

  9. java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : DerInputStream.getLength(): lengthTag=111, too big.

    RSA用私钥签名的时候发现报错,删除以下内容即可 -----BEGIN PRIVATE KEY----- -----END PRIVATE KEY----- import org.apache.com ...

  10. SpringWeb 拦截器

    前言 spring拦截器能帮我们实现验证是否登陆.验签校验请求是否合法.预先设置数据等功能,那么该如何设置拦截器以及它的原理如何呢,下面将进行简单的介绍 1.设置 HandlerInterceptor ...