SpringBoot整合Shiro+JWT实现认证及权限校验
Posted 花伤情犹在
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot整合Shiro+JWT实现认证及权限校验相关的知识,希望对你有一定的参考价值。
序言
本文讲解如何使用SpringBoot
整合Shiro
框架来实现认证及权限校验,但如今的互联网已经成为前后端分离的时代,所以本文在使用SpringBoot
整合Shiro
框架的时候会联合JWT
一起搭配使用。
Shiro
Shiro
是apache
旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份
认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。
Shiro架构图
Shiro 核心组件
用户、角色、权限之间的关系
- 用户拥有不同角色
- 角色拥有不同权限
1、UsernamePasswordToken
,Shiro
用来封装用户登录信息,使用用户的登录信息来创建令牌 Token
。
2、SecurityManager
,Shiro
的核心部分,负责安全认证和授权。
3、Suject
,Shiro
的一个抽象概念,包含了用户信息。
4、Realm
,开发者自定义的模块,根据项目的需求,验证和授权的逻辑全部写在 Realm 中。
5、AuthenticationInfo
,用户的角色信息集合,认证时使用。
6、AuthorzationInfo
,角色的权限信息集合,授权时使用。
7、DefaultWebSecurityManager
,安全管理器,开发者自定义的Realm
需要注入到 DefaultWebSecurityManager
进行管理才能生效。
8、ShiroFilterFactoryBean
,过滤器工厂,Shiro
的基本运行机制是开发者定制规则,Shiro
去执行,具体的执行操作就是由ShiroFilterFactoryBean
创建的一个个 Filter 对象来完成。
JWT
JWT(JSON WEB TOKEN)
:JSON
网络令牌,JWT
是一个轻便的安全跨平台传输格式,定义了一个紧凑的自包含的方式在不同实体之间安全传输信息(JSON
格式)。它是在Web
环境下两个实体之间传输数据的一项标准。实际上传输的就是一个字符串。
JWT的构成
JWT
由三部分构成:Header
(头部)、Payload
(载荷)和Signature
(签名)。
1.Header(头) 作用:记录令牌类型、签名算法等 例如:“alg":"HS256","type","JWT
2.Payload(有效载荷)作用:携带一些用户信息 例如"userId":"1","username":"mayikt"
3.Signature(签名)作用:防止Token被篡改、确保安全性 例如 计算出来的签名,一个字符串
项目环境
- Shiro:1.4.1
- SpringBoot:2.5.6
- JDK:1.8
搭建项目
pom依赖
<dependencies>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.4.1</version>
</dependency>
<!-- shiro-chcache-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.1</version>
</dependency>
<!-- jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
<!-- fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.15</version>
</dependency>
</dependencies>
JWTUtil
public class JWTUtils
/**
* 过期时间
*/
private static final long EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000;
/**
* 校验
* @param token
* @param username
* @param password
* @return
*/
public static boolean verify(String token, String username, String password)
try
Algorithm algorithm = Algorithm.HMAC256(password);
JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
DecodedJWT jwt = verifier.verify(token);
return true;
catch (Exception e)
return false;
/**
* 颁发令牌
* @param username
* @param password
* @return
*/
public static String sign(String username, String password)
try
//设置过期时间:获取当前时间+过期时间(毫秒)
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
//设置签名的加密算法:HMAC256
Algorithm algorithm = Algorithm.HMAC256(password);
// 附带username信息
return JWT.create()
.withClaim("username", username)
.withExpiresAt(date)
.sign(algorithm);
catch (UnsupportedEncodingException e)
return null;
/**
* 获取用户名
* @param token
* @return
*/
public static String getUsername(String token)
if (token == null || "".equals(token))
return null;
try
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
catch (JWTDecodeException e)
return null;
JWTToken
JWTToken
是定义的一个Token
类,继承了AuthenticationToken
类,实现getPrincipal
和getCredentials
方法,(这两个方法本来是用于获取token中的信息,和识别token的,但JWTUtils
已经为我们提供了这样的方法,所以这两个方法对于JWTToken
没有意义)。用于将客户端传来的Token
进行封装,便于Realm
识别Token
类型,进行认证和授权。
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;
JWTFilter过滤器
因为 JWT
的整合,我们需要⾃定义⾃⼰的过滤器 JWTFilter
,JWTFilter
继承了 BasicHttpAuthenticationFilter
,并部分原⽅法进⾏了重写。
public class JWTFilter extends BasicHttpAuthenticationFilter
/**
* Header中的Token标志
*/
private static String LOGIN_SIGN = "Authorization";
/**
* 是否允许访问
* @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);
catch (Exception e)
if (e instanceof AuthorizationException)
throw new AuthorizationException("访问资源权限不足!");
else
//token 异常 认证失败
throw new AuthenticationException("token 异常 认证失败");
return true;
/**
* 是登录尝试
* @param request
* @param response
* @return
*/
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response)
HttpServletRequest req = (HttpServletRequest) request;
//判断是否是登录请求
String authorization = req.getHeader(LOGIN_SIGN);
return authorization != null;
/**
* 执行登录
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception
HttpServletRequest req = (HttpServletRequest) request;
String header = req.getHeader(LOGIN_SIGN);
JWTToken token = new JWTToken(header);
//提交给realm进⾏登⼊,如果错误他会抛出异常并被捕获
getSubject(request, response).login(token);
return true;
自定义ShiroRealm
自定义的Realm
对象,该对象继承于AuthorizingRealm
,实现了Shiro具体认证和授权的方法。
doGetAuthenticationInfo
方法用于->认证:校验帐号和密码
doGetAuthorizationInfo
方法用于->授权:授予角色和权限
另外需要注意:
必须要重写supports
方法,因为是自己定义的Token
,shiro
无法识别,需要修改Realm
中的supports
方法,使 shiro
支持自定义Token
。
public class ShiroRealm extends AuthorizingRealm
@Autowired
private RoleService roleService;
@Autowired
private MenuService menuService;
@Autowired
private UserService userService;
/**
* 因为是自己定义的Token,shiro无法识别,需要修改Realm中的supports方法,使 shiro 支持自定义token。
* @param token
* @return
*/
@Override
public boolean supports(AuthenticationToken token)
return token instanceof JWTToken;
/**
* 认证:校验帐号和密码
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException
String token = (String) authenticationToken.getCredentials();
//从token中获取用户名
String username = JWTUtils.getUsername(token);
//获取数据库中存取的用户,密码是加密后的
User user = userService.selectByUserName(username);
if (user != null)
// 密码验证
if (!JWTUtils.verify(token, username, user.getPassword()))
// 密码不正确
throw new IncorrectCredentialsException();
return new SimpleAuthenticationInfo(token, token, getName());
else
throw new UnknownAccountException();
/**
* 授权:授予角色和权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
//获取用户名
String userName = JWTUtils.getUsername(principals.toString());
//根据用户名查询用户
User user = userService.selectByUserName(userName);
//实例化一个授权信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
if (user != null)
//赋予角色
List<Role> roles = roleService.selectRoleByUserId(user.getId());
for (Role role : roles)
//将角色添加到授权信息中
info.addRole(role.getRoleKey());
//赋予资源
List<Menu> permissions = menuService.selectPermsByUserId(user.getId());
for (Menu permission : permissions)
//将权限添加授权信息中
info.addStringPermission(permission.getPerms());
return info;
ShiroConfig
ShiroConfig
用于进行Shiro
的相关配置,主要包括ShiroFilterFactoryBean
、DefaultWebSecurityManager
和Realm
的配置。
@Configuration
public class ShiroConfig
/**
* 生命周期处理器
* @return
*/
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor()
return new LifecycleBeanPostProcessor();
/**
* 加密方式
* @return
*/
@Bean(name = "hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher()
// 散列凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// 设置哈希算法名称,这里使用MD5算法
credentialsMatcher.setHashAlgorithmName("MD5");
// 设置哈希迭代,这里迭代2次,相当于 md5(md5(""))
credentialsMatcher.setHashIterations(2);
// 设置存储的凭据16进制编码,需要和生成密码时的一样,默认是 Base64
credentialsMatcher.setStoredCredentialsHexEncoded(true);
return credentialsMatcher;
/**
* 自定义Realm
* @param cacheManager
* @return
*/
@Bean(name = "shiroRealm")
@DependsOn("lifecycleBeanPostProcessor")
public ShiroRealm shiroRealm(EhCacheManager cacheManager)
ShiroRealm realm = new ShiroRealm();
realm.setCacheManager(cacheManager);
return realm;
/**
* 缓存管理器
* @return
*/
@Bean(name = "ehCacheManager")
@DependsOn("lifecycleBeanPostProcessor")
public EhCacheManager ehCacheManager()
EhCacheManager ehCacheManager = new EhCacheManager();
ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return ehCacheManager;
/**
* 安全管理器
* @param shiroRealm
* @return
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager securityManager(ShiroRealm shiroRealm)
// 实例化会话管理器
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置缓存管理器
securityManager.setCacheManager(ehCacheManager());
/**
* 关闭shiro自带的session
* 详情见文档: http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
*/
DefaultSessionStorageEvaluator evaluator = new DefaultSessionStorageEvaluator();
evaluator.setSessionStorageEnabled(false);
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
subjectDAO.setSessionStorageEvaluator(evaluator);
securityManager.setSubjectDAO(subjectDAO);
// 设置自定义Realm
securityManager.setRealm(shiroRealm);
return securityManager;
/**
* 过滤工厂
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager)
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
// 添加自己的过滤器并且取名为jwt
Map<String, Filter> filters = new LinkedHashMap<>();
filters.put("jwt", new JWTFilter());
factoryBean.setFilters(filters);
Map<String, String> filterChainDefinitionManager = new LinkedHashMap<>();
// 所有请求通过我们自己的JWT Filter
filterChainDefinitionManager.put("/**", "jwt");
factoryBean.setFilterChainDefinitionMap(filterChainDefinitionManager);
return factoryBean;
/**
* 自动代理配置
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator()
DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
// 强制使用cglib,防止重复代理和可能引起代理出错的问题
// https://zhuanlan.zhihu.com/p/29161098
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
/**
* 开启注解支持
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor 以上是关于SpringBoot整合Shiro+JWT实现认证及权限校验的主要内容,如果未能解决你的问题,请参考以下文章
SpringBoot技术专题「权限校验专区」Shiro整合JWT授权和认证实现
SpringBoot技术专题「实战开发系列」带你一同探索Shiro整合JWT授权和认证实战开发