一、pom.xml
关于shiro使用全部依赖,因为我使用thymeleaf模块,所以才有thymeleaf+shiro的依赖,如果是其它前端模板,需要导入其它依赖
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<commons-pool2.version>2.6.1</commons-pool2.version>
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.0.0</thymeleaf-layout-dialect.version>
</properties>
<!-- shiro+redis缓存插件 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.4.2.1-RELEASE</version>
<exclusions>
<exclusion>
<artifactId>shiro-core</artifactId>
<groupId>org.apache.shiro</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- shiro 依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!--thymeleaf- shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>${thymeleaf-layout-dialect.version}</version>
</dependency>
二、权限控制数据库结构设置
我这里采用的权限设计经典5表
用户表
角色表
用户-角色关系表
权限资源表
角色-权限关系表
5张表基本上可以完成权限的功能,当然主要是根据自己项目需要来设计,但是其中关系还是那样!
用户表t_sys_user
没有什么介绍的,主要是用户基本信息,user_state 用户状态,比如启用、锁定、禁用等状态
角色表t_sys_role
role_state 角色状态,role_sort 排序
用户-角色关系表(t_sys_user_role)中间表
user_id和role_id分别为用户id和角色id,保存用户具备角色信息
权限资源关系表(t_sys_permission)
这里主要保存我们所说的权限了,一般会为权限分为几个类型,比如菜单权限、按钮权限等
我这里主要控制用户访问菜单url权限已经一些按钮比如(查看、编辑、删除)等权限
角色-权限资源关系表(t_sys_role_permission)
我这里主要是通过角色来控制权限,给用户分配不同的角色,给角色分配具有的权限,那么相当于给用户分配了权限,而且管理起来也比较方便
三、将shiro 整合正springboot中
业务介绍清楚后,需要完成相应的功能代码
部分代码我这里就不介绍了,比如实体类的建立
springboot整合shiro
相对应spring整合shiro来说,轻松了许多,不需要过多复杂的配置,而且springboot整合shiro,模式也比较统一规范,按照规范配置即可。
一些application.yml中配置
spring:
#thymeleaf模块配置
thymeleaf:
prefix: classpath:/templates/
suffix: .html
##非严格检查-HTML
##严格检查-HTML5
mode: HTML
encoding: UTF-8
#清除缓存,实现热部署。也就是修改了html后不用重启,刷新页面就能看到效果。再回到浏览器刷新,就能看到效果了
cache: false
#数据库配置
datasource:
url: jdbc:mysql://localhost:3306/spring?serverTimezone=UTC&characterEncoding=utf8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
#禁用视图
open-in-view: false
#单个redis配置
redis:
#Redis服务器地址
host: 127.0.0.1
#Redis服务器连接端口
port: 6379
#Redis服务器连接密码(默认为空)
password:
# Redis数据库索引(默认为0)
database: 0
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制) 默认 8
max-active: 32
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
max-wait: -1
# 连接池中的最大空闲连接 默认 8
max-idle: 8
# 连接池中的最小空闲连接 默认 0
min-idle: 0
# 连接超时时间(毫秒)
timeout: 0
ShiroConfig是shiro的核心过滤器
是不可缺少的,提供了许多shiro的功能,这也是shiro优点,不需要过多关系业务功能,只要按照shiro提供的框架规范即可。
package com.springboot.main.core.config.shiro;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.crazycake.shiro.RedisCacheManager;
import com.springboot.main.eimm.permission.entity.Permission;
import com.springboot.main.eimm.permission.service.PermissionService;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import lombok.extern.slf4j.Slf4j;
/**
* @method shiro核心配置
* @author Mr yi
* @time 2019年5月6日
*/
@Configuration
@Slf4j
public class ShiroConfig {
@Autowired(required = false)
private PermissionService permissionService;
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.lettuce.pool.timeout}")
private int timeout;
@Value("${spring.redis.password}")
private String password;
@Bean
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* @method ShiroDialect,为了在thymeleaf里使用shiro的标签的bean
* @author Mr yi
* @time 2019年5月6日
* @return
*/
@Bean("shiroDialect")
public ShiroDialect shiroDialect() {
log.info("配置thymeleaf方言shiroDialect");
return new ShiroDialect();
}
/**
*
* @param
* @return org.springframework.beans.factory.config.MethodInvokingFactoryBean
* @author Zain
* @description Spring静态注入:让某个方法的返回值注入bean实例
* @date 2019/1/9 17:18
*/
@Bean
public MethodInvokingFactoryBean methodInvokingFactoryBean(SecurityManager securityManager){
MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();
bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
bean.setArguments(securityManager);
return bean;
}
@SuppressWarnings("rawtypes")
@Bean
public FilterRegistrationBean delegatingFilterProxy(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
DelegatingFilterProxy proxy = new DelegatingFilterProxy();
proxy.setTargetFilterLifecycle(true);
proxy.setTargetBeanName("shiroFilter");
filterRegistrationBean.setFilter(proxy);
return filterRegistrationBean;
}
/**
*
* @method ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在
* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager Filter Chain定义说明
* 1、一个URL可以配置多个Filter,使用逗号分隔
* 2、当设置多个过滤器时,全部验证通过,才视为通过3、部分过滤器可指定参数,如perms,roles
* @author Mr yi
* @time 2019年5月6日
* @param securityManager
* @return
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
log.info("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/main/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/main/main/index");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/main/error/error-404");
// 拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
filterChainDefinitionMap.put("/", "user");
filterChainDefinitionMap.put("/main/main/index", "user");
// 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,这里我们在MainController自定义退出方法
filterChainDefinitionMap.put("/**","anon");
/* filterChainDefinitionMap.put("/main/logout", "logout"); */
/*
* filterChainDefinitionMap.put("/css/**","anon");
* filterChainDefinitionMap.put("/js/**","anon");
* filterChainDefinitionMap.put("/img/**","anon");
* filterChainDefinitionMap.put("/font-awesome/**","anon");
*/
// <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
// 自定义加载权限资源关系
List<Permission> permissionList = permissionService.selectByMap(new HashMap<String, Object>());
for (Permission permission : permissionList) {
if (StringUtils.isNotEmpty(permission.getPermission_url())) {
String url = "perms[" + permission.getPermission_url() + "]";
filterChainDefinitionMap.put(permission.getPermission_url(), url);
}
}
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
//配置核心安全事务管理器
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(myShiroRealm());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
//注入记住我管理器;
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
/**
* @method MyShiroRealm自定义Realm-配置自定义的权限登录器
* @author Mr yi
* @time 2019年5月6日
* @return
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
/**
* @method 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了,所以我们需要修改下doGetAuthenticationInfo中的代码;
* )
* @author Mr yi
* @time 2019年5月6日
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");// 散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);// 散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
/**
* @method 开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持;
* @author Mr yi
* @time 2019年5月6日
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* @method 配置shiro redisManager 使用的是shiro-redis开源插件
* @author Mr yi
* @time 2019年5月6日
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setExpire(1800);// 配置缓存过期时间
redisManager.setTimeout(timeout);
redisManager.setPassword(password);
return redisManager;
}
/**
* @method cacheManager 缓存 redis实现 使用的是shiro-redis开源插件
* @author Mr yi
* @time 2019年5月6日
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
/**
* @method RedisSessionDAO shiro sessionDao层的实现 通过redis 使用的是shiro-redis开源插件
* @author Mr yi
* @time 2019年5月6日
* @return
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
/**
* @method shiro session的管理-使用的是shiro-redis开源插件
* @author Mr yi
* @time 2019年5月6日
* @return
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
/**
* cookie对象;
*
* @return
*/
public SimpleCookie rememberMeCookie() {
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//<!-- 记住我cookie生效时间7天 ,单位秒;-->
simpleCookie.setMaxAge(604800);
return simpleCookie;
}
/**
* cookie管理对象;记住我功能
*
* @return
*/
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
//rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
return cookieRememberMeManager;
}
}
MyShiroRealm shiro自定义Realm,shiro比较核心两点:认证、授权。
用户登录时会执行认证方法,只有认证通过的用户才可以访问系统;
那么授权便是为了获取用户权限数据,依次来判断用户是否具备访问此资源的权限!
缓存我使用redis来实现的,官方推荐使用EhCache
package com.springboot.main.core.config.shiro;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.mgt.RealmSecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.apache.shiro.util.ByteSource;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import com.springboot.main.eimm.permission.entity.Permission;
import com.springboot.main.eimm.permission.service.PermissionService;
import com.springboot.main.eimm.user.entity.User;
import com.springboot.main.eimm.user.service.UserService;
import javax.annotation.Resource;
import java.util.*;
/**
* @method shiro自定义Realm
* @author Mr yi
* @time 2019年5月6日
*/
public class MyShiroRealm extends AuthorizingRealm {
@Resource
private UserService userService;
@Resource
private PermissionService permissionService;
@Autowired
private RedisSessionDAO redisSessionDAO;
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
User user = (User) SecurityUtils.getSubject().getPrincipal();
Map<String, Object> map = new HashMap<String, Object>();
map.put("role_state", "001");//角色状态:启用
map.put("user_id", user.getId());//用户id
map.put("permission_state", "001");//权限状态:启用
map.put("permission_type", "001"); //权限类型:菜单
List<Permission> permissionList = permissionService.getPermissionListByUserId(map);
// 获取该用户具有的全部权限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
for (Permission permission : permissionList) {
String url = permission.getPermission_url();
//权限不能有空数据
if(StringUtils.isNotBlank(url))
info.addStringPermission(permission.getPermission_url());
}
return info;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取用户的输入的账号.
String userName = (String) token.getPrincipal();
Map<String, Object> map = new HashMap<String, Object>();
map.put("user_name", userName);
User user = userService.selectUserByUserName(map);
if (user == null || StringUtils.isBlank(userName)) {
throw new UnknownAccountException();
}
if (StringUtils.equals("002", user.getUser_state())) { // 锁定
throw new LockedAccountException(); // 帐号锁定
}
if (StringUtils.equals("003", user.getUser_state())) { // 禁用
throw new DisabledAccountException("该账户已被禁用!");
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, // 用户
user.getUser_pwd(), // 密码
ByteSource.Util.bytes(userName), getName() // realm name
);
//当验证都通过后,把用户信息放在session里
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("userSession", user);
session.setAttribute("userSessionId", user.getId());
return authenticationInfo;
}
/**
* @method 根据userId 清除当前session存在的用户的权限缓存(在shiro中权限清空后会重新调用授权方法)
* @author Mr yi
* @time 2019年5月6日
* @param userIds
*/
public void clearUserAuthByUserId(List<String> userIds) {
if (null == userIds || userIds.size() == 0)
return;
// 获取所有session
Collection<Session> sessions = redisSessionDAO.getActiveSessions();
// 定义返回
List<SimplePrincipalCollection> list = new ArrayList<SimplePrincipalCollection>();
for (Session session : sessions) {
// 获取session登录信息。
Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (null != obj && obj instanceof SimplePrincipalCollection) {
// 强转
SimplePrincipalCollection spc = (SimplePrincipalCollection) obj;
// 判断用户,匹配用户ID。
obj = spc.getPrimaryPrincipal();
if (null != obj && obj instanceof User) {
User user = (User) obj;
// 比较用户ID,符合即加入集合
if (null != user && userIds.contains(user.getId())) {
list.add(spc);
}
}
}
}
RealmSecurityManager securityManager = (RealmSecurityManager) SecurityUtils.getSecurityManager();
MyShiroRealm realm = (MyShiroRealm) securityManager.getRealms().iterator().next();
for (SimplePrincipalCollection simplePrincipalCollection : list) {
realm.clearCachedAuthorizationInfo(simplePrincipalCollection);
}
}
}
为认证的用户,访问系统会自动拦截,跳转到login登录界面
提交登录信息进入login方法进行登录
package com.springboot.main.eimm.main.controller;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.springboot.main.eimm.user.entity.User;
import lombok.extern.slf4j.Slf4j;
/**
* @method 程序主程序控制层方法(登录注销)
* @author Mr yi
* @time 2019年5月6日
*/
@Controller
@Slf4j
@RequestMapping("main")
public class MainController {
/**
*
* @method 通用访问页面方法(访问方式:http://127.0.0.1:8080/spring-main/main/main/content) 访问main文件夹下的content。html页面
* @author Mr yi
* @time 2019年5月9日
* @param module
* @param usecase
* @return
*/
@RequestMapping("/{module}/{usecase}")
public String index(@PathVariable("module")String module,@PathVariable("usecase")String usecase){
return module+"/"+usecase;
}
/**
* @method 用户登录(get)
* @author Mr yi
* @time 2019年5月6日
* @return
*/
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login() {
return "login";
}
/**
* @method 用户登录(post)
* @author Mr yi
* @time 2019年5月6日
* @param request
* @param user
* @param model
* @return
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(HttpServletRequest request, User user, Model model,boolean rememberMe) {
if (StringUtils.isEmpty(user.getUser_name()) || StringUtils.isEmpty(user.getUser_pwd())) {
request.setAttribute("msg", "账号或密码不能为空!");
return "login";
}
UsernamePasswordToken token = new UsernamePasswordToken(user.getUser_name(), user.getUser_pwd(),rememberMe);
//获取当前的Subject
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return "redirect:main/index";
} catch (LockedAccountException lae) {
token.clear();
request.setAttribute("msg", "该账号已被锁定!");
return "login";
} catch (DisabledAccountException e) {
token.clear();
request.setAttribute("msg", "该账号已被禁用!");
return "login";
} catch (AuthenticationException e) {
token.clear();
request.setAttribute("msg", "账号或密码不正确!");
return "login";
}
}
/**
* @method 注销(logout方法自动清空shiro相关用户缓存)
* @author Mr yi
* @time 2019年5月6日
* @return
*/
@RequestMapping("/logout")
public String logout1() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
log.info(" 用户注销成功 !");
return "login";
}
}
dao成方法主要是getPermissionListByUserId(),获取用户具有的权限权限,这里要注意一点,permission_url权限链接不能为空,在前面shiro的授权中,需要获取用户具备的权限。必须要求给的权限不能为空,如果权限为空,会抛出错误。
/**
* @method 查询用户具有的全部权限链接( permission_url 不为空的)
* @author Mr yi
* @time 2019年5月6日
* @param user_id
* @return
*/
@Select({"<script>",
"SELECT DISTINCT t1.* from t_sys_permission t1 ",
"INNER JOIN t_sys_role_permission t2 on t1.id=t2.permission_id",
"INNER JOIN t_sys_role t3 on t2.role_id=t3.id",
//角色状态判断
"<when test='role_state!=null'>",
"and t3.role_state= #{role_state}",
"</when>",
//用户id
"INNER JOIN t_sys_user_role t4 on t3.id=t4.role_id and t4.user_id=#{user_id} where 1=1 ",
//权限状态判断
"<when test='permission_state!=null'>",
"and t1.permission_state= #{permission_state}",
"</when>",
//权限类型判断
"<when test='permission_type!=null'>",
"and t1.permission_type= #{permission_type}",
"</when>",
"ORDER BY t1.permission_sort asc ",
" </script>"})
public List<Permission> getPermissionListByUserId(Map<String, Object> columnMap);
四、thymeleaf使用shiro标签
shiro提供一套标签库,具体语法不介绍了
1、到如pom.xml,前面已经提过了
springboot4以上版本目前要求2.0.0以上
<!--thymeleaf- shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>${thymeleaf-layout-dialect.version}</version>
</dependency>
2、在ShiroConfig中配置ShiroDialect
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
3、html中导入shiro标签
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
4、使用shiro标签
<shiro:principal property=“user_name”/>
五、使用thymeleaf使用shiro页面抛错问题
1、SpringBoot热部署导致同一个类型转换出现类型转换异常ClassCastException
这是因为springboot热部署插件原因导致的,应该springboot及热部署插件使用不是同一个统一classloader ,类加载器导致
比如:认证成功,将查询出来的对象存到session中,
再从session取出对象是object,将object进行转换,报java.lang.ClassCastException 不是同一个对象。
解决方案就是在resources目录下面创建META_INF文件夹,然后创建spring-devtools.properties文件,文件加上类似下面的配置
restart.include.shiro-redis=/shiro-[\\w-\\.]+jar
或者不适用热部署插件
2、springboot导致thymleaf 中shiro标签解析出错
原因:SpringBoot热部署导致
不适用spring-boot-devtools后即可正常使用shiro标签
如果你觉得本篇文章对你有所帮助的话,麻烦请点击头像右边的关注按钮,谢谢!
技术在交流中进步,知识在分享中传播