目录
一、Shiro
Shiro简介
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
Shiro功能
三个核心组件:Subject, SecurityManager 和 Realms.
Subject
主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;
SecurityManager
安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;
Realm
域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。
也就是说对于我们而言,最简单的一个 Shiro 应用:
- 应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
- 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
从以上也可以看出,Shiro 不提供维护用户 / 权限,而是通过 Realm 让开发人员自己注入。
二、前期工作
创建库表
- 用户表
CREATE TABLE `sys_user` (
`id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`account` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`nickname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`sex` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`telephone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
- 角色表
CREATE TABLE `sys_role` (
`id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`role` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
- 权限表
CREATE TABLE `sys_permission` (
`id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`permission` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限名称',
`desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限描述',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
- 用户角色表
CREATE TABLE `sys_user_role` (
`id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`user_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`role_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = FIXED;
- 角色权限表
CREATE TABLE `sys_role_permission` (
`id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`role_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`permission_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = FIXED;
引入依赖
<!-- shiro整合springboot所需相关依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- 兼容于thymeleaf的shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!--end.......-->
三、整合Shiro
自定义UserRealm
//TODO 自定义的UserRealm,继承AuthorizingRealm,
//TODO 覆写doGetAuthorizationInfo、doGetAuthenticationInfo方法
public class UserRealm extends AuthorizingRealm {
@Resource
private UserInfoDao userInfoDao;
@Resource
private PermissionDao permissionDao;
@Resource
private RoleDao roleDao;
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=>授权doGetAuthorizationInfo");
// 获取当前用户登录信息
SysUser user = (SysUser) principalCollection.getPrimaryPrincipal();
// 创建SimpleAuthorizationInfo对象
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 查询当前用户所拥有角色
List<SysRole> roleList = roleDao.findRoleByUserId(user.getId());
Set<String> roleSet = new HashSet<>();
List<String> roleIds = new ArrayList<>();
for (SysRole role : roleList) {
// 用户角色集合
roleSet.add(role.getRole());
// 用户角色id集合
roleIds.add(role.getId());
}
// 放入用户角色信息
authorizationInfo.setRoles(roleSet);
// 根据当前用户拥有的角色查询所拥有权限
List<String> permissionList = permissionDao.findByRoleId(roleIds);
// 放入用户权限信息
authorizationInfo.setStringPermissions(new HashSet<>(permissionList));
return authorizationInfo;
}
// 认证
@SneakyThrows
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了=>认证doGetAuthorizationInfo");
// 获取当前用户登录信息
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
// 根据用户名查询用户信息
SysUser sysUser = userInfoDao.selectByAccount(userToken.getUsername());
if (sysUser ==null) {//没有此用户
return null;//抛出异常 UnknownAccountException,账号不存在。
}
// AES加密密码
//String password = AESUtils.decrypt(userInfo.getPassword(), secret);
// 密码认证,shiro做
return new SimpleAuthenticationInfo(sysUser, sysUser.getPassword(),getName());
}
}
配置ShiroConfig
package com.example.animalhome.shiro;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import javax.servlet.Filter;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//1. 创建realm对象,需要自定义类
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
//2. 配置ShiroDialect,用于Shiro和thymeleaf标签配合使用
//若不配置,可能导致前端使用shiro标签后无效果
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
// 配置sessionDAO
@Bean(name = "sessionDAO")
public MemorySessionDAO getMemorySessionDAO() {
return new MemorySessionDAO();
}
//3. shiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
// 添加自己的过滤器并且取名为loginFilter
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
// 自定义loginFilter,解决账号不能异地登录,以及登录失效等问题
filterMap.put("loginFilter", new LoginFilter());
shiroFilterFactoryBean.setFilters(filterMap);
// 拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接 顺序判断,anon放开,不会拦截,authc会拦截
//登录用到的url
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/register", "anon");
filterChainDefinitionMap.put("/captchaImage", "anon");
filterChainDefinitionMap.put("/publish/options", "anon");
//静态资源不能被拦截
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
// 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
// <!-- loginFilter:所有url都必须登录系统才可以访问-->
filterChainDefinitionMap.put("/**", "loginFilter,authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
//访问的是后端url地址为 /login的接口,/未登录页面,会跳转到登录页面
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
//配置shiro session 的一个管理器
@Bean(name = "sessionManager")
public DefaultWebSessionManager getDefaultWebSessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// 设置session过期时间,30分钟=1800000毫秒
sessionManager.setGlobalSessionTimeout(60*60*1000);
// 将sessionDAO放进来
sessionManager.setSessionDAO(getMemorySessionDAO());
return sessionManager;
}
//4. DefaultWebSecurityManager
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联userRealm
securityManager.setRealm(userRealm);
// 将sessionManager放进来
securityManager.setSessionManager( getDefaultWebSessionManager() );
// 关闭shiro的记住我验证
securityManager.setRememberMeManager(null);
return securityManager;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @return org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("getDefaultWebSecurityManager") SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
自定义LoginFilter
自定义LoginFilter是为了解决用户登录session超时后跳转登录页,以及异地登录提醒问题;代码中已做具体注释。
public class LoginFilter extends FormAuthenticationFilter {
//TODO /druid为Druid监控页面,无需拦截直接访问。
private static final String[] filter = { "/druid" };
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest hsRequest = (HttpServletRequest) request;
HttpServletResponse hsResponse = (HttpServletResponse) response;
String url = hsRequest.getRequestURI();
// 不需要过滤的请求地址以及文件
for (String str : filter) {
if (url.contains(str) || url.equalsIgnoreCase("/")){
return true;
}
}
//这里是获取用户登录信息
Subject subject = getSubject(request, response);
String account = hsRequest.getHeader("account");//获取登录账号
// 如果没有获取到用户信息,将退出到登陆界面
if (null == subject.getPrincipal()) {
boolean isAjaxRequest = false;
if(!StringUtils.isBlank(hsRequest.getHeader("x-requested-with")) &&
hsRequest.getHeader("x-requested-with").equals("XMLHttpRequest")){
isAjaxRequest = true;
}
// 如果是Ajax返回指定数据
if (isAjaxRequest) {
//可以通过code403判断是否重定向,也可以自定义一个属性指定是session超时的重定向
hsResponse.setHeader("sessionstatus", "TIMEOUT"); //返回特定数据(头部信息)
//首先account是从前端cookie中获取,失效时间与shiro登录账号的session过期时间一致,
//若为空说明账号登录有效时间已过,或者用户关闭浏览器,亦或者用户清空缓存等情况,均返回到登录首页,提示用户登录信息已失效。
if (StringUtils.isNotEmpty(account)){
//若不为空,则从缓存中拿到是否异地登录,remoteLogin=2则是已异地登录,需返回登录界面。
String remoteLogin = RedisUtil.getCacheString(account);
if (StringUtils.isNotEmpty(remoteLogin) && StringUtils.equals(remoteLogin,"2")){
//首页登陆地址,并提示用户账号已在异地登录
hsResponse.setHeader("content_path", "/animalHome/login?strangeLand=1");
}
} else {
//首页登陆地址,并提示用户登录信息已失效
hsResponse.setHeader("content_path", "/animalHome/login?strangeLand=2");
}
hsResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
} else { // 不是Ajax进行重定向处理
hsResponse.sendRedirect("/animalHome/login?strangeLand=2"); //重定向到登陆界面
}
return false;
}
return true;
}
}
公共方法common.js
若用户登录session超时,异地登录时,点击页面会触发ajax请求,后端LoginFilter做出响应处理后传值给前端,由前端判断sessionstatus === "TIMEOUT"决定跳转登录首页。
//设置session超时跳转页面start
if(typeof jQuery != 'undefined') {
//重写ajax
$.ajaxSetup( {
beforeSend: function(xhr) {
xhr.setRequestHeader("account", getCookie("account"));
},
//设置ajax请求结束后的执行动作
complete :
function(XMLHttpRequest, textStatus) {
// 通过XMLHttpRequest取得响应头,sessionstatus
var sessionstatus = XMLHttpRequest.getResponseHeader("sessionstatus");
if (sessionstatus === "TIMEOUT") {
var win = window;
while (win != win.top) {
win = win.top;
}
win.location.href = XMLHttpRequest.getResponseHeader("content_path");
}
}
});
}
//设置session超时跳转页面end
用户登录login.js
login(){
const _this =this;
$.ajax({
url: getRootPath()+"/login",
contentType:'application/json',
type: "POST",
data: JSON.stringify({
"account": this.loginForm.account,
"pass": this.loginForm.pass,
"uuid": this.loginForm.uuid,
"code": this.loginForm.code,
}),
success: function (data) {
//重置loading登录状态。
_this.loading = false;
if (data.code === "0000"){
_this.loginForm.uuid = "";
//重点:与shiro用户登录后的session失效时间需保持一致。
setCookie("account", _this.loginForm.account,30 * 60 * 1000);
window.location.href = getRootPath()+"/homePage?account="+ _this.loginForm.account;
//登录成功后重置表单字段
_this.$refs["loginForm"].resetFields();
} else {
_this.loginForm.code = "";
_this.resetImg();
_this.$message.error(data.message);
}
},
});
},
created(){
const _this = this;
_this.resetImg();
const strangeLand = getUrlParam("strangeLand");
if (strangeLand != null) {
const message = strangeLand === "1" ? "此账号已在异地登录,若不是本人登录请及时修改密码!" : "您的登录信息已失效,请重新登录!";
// 解决用户刷新页面不再重复弹窗问题。
if (window.name == ""){
window.name = "isReload";
this.$message({
showClose: true,
message: message,
type: 'warning',
duration: 0
});
}
}
},
用户登录逻辑层
/**
* 用户登录
* @param jsonObject 用户信息
* @return Map
*/
@Override
public Map<String, Object> login(JSONObject jsonObject) throws Exception {
Map<String, Object> loginMap = new HashMap<>();
String code = RedisUtil.getCacheString(jsonObject.getString("uuid"));
if(StringUtils.isEmpty(code)){
// 验证码失效
throw new ServiceException(RetResult.setResponse(RetCode.CODE_INVALID.getCode()));
}
String account = jsonObject.getString("account");
String pass = jsonObject.getString("pass");
String inputCode = jsonObject.getString("code");
try {
if(StringUtils.equals(inputCode,code)){
//获取一个用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(account, pass);
//登录验证
subject.login(token);
Collection<Session> sessions = sessionDAO.getActiveSessions();
if (subject.isAuthenticated()) {
Optional<Session> loginedUser = sessions.stream().filter(session -> account.equals(session.getAttribute("loginedUser"))).findFirst();
if (loginedUser.isPresent()){
//TODO 方法一、当第二次登录时,给出提示“用户已登录”,停留在登录页面
//TODO 弊端:如果操作人员不是常规退出,而是通过浏览器清除全部的浏览数据,刷新页面虽然返回登录页面,
//TODO 但是再用刚才的账号登录时会提示已经登录,因为之前的账号shiro还是一直认证通过状态,
//TODO 只能等session超时了才能登录(如果设置session超时时间为2小时或更久那就太悲剧了)
// subject.logout();
// throw new RuntimeException("用户已登录");
//TODO 方法二、当第二次登录时,把第一个session剔除
//TODO 弊端:虽然不会产生方法一的问题,但是两个人AB公用一个账户,AB同时登录,AB同时处在下图的页面,
//TODO A修改了demo的状态为锁定,而B用户的页面不会改变仍是正常的状态。
Session session = loginedUser.get();
session.setTimeout(0);
}
}
//若当前只有一个用户登录,则在redis缓存存入account=1,并设置过期时间,
//当另一个用户再登录账号时,缓存会取到数据,此时将account更改为2(代表
//此账号已存在异地登录);这时若前者再操作系统,会结合上方已将前者登录session
//剔除,综合两种情况跳转至登录首页,并提示用户密码需修改。
String remoteLogin = StringUtils.isNotEmpty(RedisUtil.getCacheString(account)) ? "2" : "1";
//重点:与前端login.js中用户登录后,存入cookie中的account失效时间需保持一致。
RedisUtil.setCacheObject(account,remoteLogin,60*30,TimeUnit.SECONDS);
//设置过期时间为30分钟,单位毫秒,30分钟=1800000毫秒
subject.getSession().setTimeout(1000*60*30);
shiroUtils.getSession().setAttribute("loginedUser",account);
}else{
//验证码错误
RetResult.setResponse(RetCode.CODE_FAILED.getCode(),loginMap);
}
} catch (UnknownAccountException e) {
//账号不存在
RetResult.setResponse(RetCode.ACCOUNT_NOT_EXIST.getCode(),loginMap);
} catch (DisabledAccountException e) {
//账号被禁用
RetResult.setResponse(RetCode.ACCOUNT_IS_DISABLED.getCode(),loginMap);
} catch (IncorrectCredentialsException e) {
//账号或密码错误
RetResult.setResponse(RetCode.INCORRECT_CREDENTIALS.getCode(),loginMap);
} catch (Throwable e) {
//系统异常
RetResult.setResponse(RetCode.SYS_ERROR.getCode(),loginMap);
}
return loginMap;
}
shiro标签说明
标签 | 含义 |
shiro:principal | 当前用户的登录信息,用户名之类 |
shiro:guest="" | 验证是否是游客,即未认证的用户 |
shiro:user="" | 验证是否是已认证或已记住用户 |
shiro:authenticated="" | 验证是否是已认证用户,不包括已记住用户 |
shiro:notAuthenticated= “” | 未认证用户,但是 已记住用户 |
shiro:lacksRole=“admin” | 表示没有 admin 角色的用户 |
shiro:hasRole=“admin” | 表示拥有 admin 角色的用户 |
shiro:hasAllRoles=“admin, user1” | 表示需要同时拥有两种角色 |
shiro:hasAnyRoles=“admin, user1” | 表示 拥有其中一个角色即可 |
shiro:lacksPermission=“admin:delete” | 类似于 shiro:lacksRole |
shiro:hasPermission=“admin:delete” | 类似于 shiro:hasRole |
shiro:hasAllPermissions=“admin:delete, admin:edit” | 类似于 shiro:hasAllRoles |
shiro:hasAnyPermission=“admin:delete, admin:edit” | 类似于 hasAnyRoles |
shiro标签使用
html引入shiro 的命名空间:xmlns:shiro=http://www.pollix.at/thymeleaf/shiro
根据自己需求使用相应的shiro标签
principal标签:输出当前用户信息,通常为登录帐号信息。
Hello, <shiro:principal/>, how are you today?
guest标签:验证当前用户是否为“访客”,即未认证(包含未记住)的用户。
<shiro:guest>
Hi there! Please <a href="login.jsp">Login</a> or <a href="signup.jsp">Signup</a> today!
</shiro:guest>
user标签:认证通过或已记住的用户。
<shiro:user>
Welcome back John! Not John? Click <a href="login.jsp">here<a> to login.
</shiro:user>
authenticated标签:已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在。
<shiro:authenticated>
<a href="updateAccount.jsp">Update your contact information</a>.
</shiro:authenticated>
notAuthenticated标签:未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。
<shiro:notAuthenticated>
Please <a href="login.jsp">login</a> in order to update your credit card information.
</shiro:notAuthenticated>
lacksRole标签:与hasRole标签逻辑相反,当用户不属于该角色时验证通过。
<shiro:lacksRole name="administrator">
Sorry, you are not allowed to administer the system.
</shiro:lacksRole>
<!-- 第二种写法,嵌入Element前端框架 -->
<el-submenu index="2" shiro:lacksRole="admin"></el-submenu>
hasRole标签:验证当前用户是否属于该角色。
<shiro:hasRole name="administrator">
<a href="admin.jsp">Administer the system</a>
</shiro:hasRole>
<!-- 第二种写法,嵌入Element前端框架 -->
<el-submenu index="2" shiro:hasRole="admin"></el-submenu>
hasAnyRole标签 :验证当前用户是否属于以下任意一个角色。
<shiro:hasAnyRoles name="developer, project manager, administrator">
You are either a developer, project manager, or administrator.
</shiro:hasAnyRoles>
<!-- 第二种写法,嵌入Element前端框架 -->
<el-submenu index="2" shiro:hasAnyRoles="admin,user"></el-submenu>
hasPermission标签 :验证当前用户是否拥有指定权限。
<shiro:hasPermission name="user:create">
<a href="createUser.jsp">Create a new User</a>
</shiro:hasPermission>
<!-- 第二种写法,嵌入Element前端框架 -->
<el-submenu index="2" shiro:hasPermission="'admin:add'"></el-submenu>
lacksPermission标签 :与hasPermission标签逻辑相反,当前用户没有指定权限时,验证通过。
<shiro:lacksPermission name="user:create">
<a href="createUser.jsp">Create a new User</a>
</shiro:lacksPermission>
hasAllRoles标签:当前用户同时拥有多个角色权限时,验证通过。
<shiro:hasAllRoles name="admin,user">
<a href="createUser.jsp">Create a new User</a>
</shiro:hasAllRoles>
<!-- 第二种写法,嵌入Element前端框架 -->
<el-submenu index="2" shiro:hasAllRoles="admin,user"></el-submenu>
hasAllPermissions标签:当前用户同时拥有多个指定权限时,验证通过。
<shiro:hasAllPermissions name="admin:add,user:add">
<a href="createUser.jsp">Create a new User</a>
</shiro:hasAllPermissions>
<!-- 第二种写法,嵌入Element前端框架 -->
<el-submenu index="2" shiro:hasAllPermissions="'admin:add,user:add'"></el-submenu>
hasAnyPermission标签:验证用户是否拥有属于以下任意一个权限。
<shiro:hasAnyPermissions name="admin:add,user:add">
<a href="createUser.jsp">Create a new User</a>
</shiro:hasAnyPermissions>
<!-- 第二种写法,嵌入Element前端框架 -->
<el-submenu index="2" shiro:hasAnyPermissions="'admin:add,user:add'"></el-submenu>