Shiro 核心功能案例讲解 基于SpringBoot 有源码
从实战中学习Shiro的用法。本章使用SpringBoot快速搭建项目。整合SiteMesh框架布局页面。整合Shiro框架实现用身份认证,授权,数据加密功能。通过本章内容,你将学会用户权限的分配规则,SpringBoot整合Shiro的配置,Shiro自定义Realm的创建,Shiro标签式授权和注解式授权的使用场景,等实战技能,还在等什么,快来学习吧!
技术:SpringBoot,Shiro,SiteMesh,Spring,SpringDataJpa,SpringMVC,Bootstrap-sb-admin-1.0.4 说明:前端使用的是Bootstrap-sb-admin模版。注意文章贴出的代码可能不完整,请以github上源码为主,谢谢! 源码:https://github.com/ITDragonBlog/daydayup/tree/master/Shiro 喜欢的朋友可以鼓励(star)下。 效果图:

Shiro 功能介绍
四个核心:登录认证,权限验证,会话管理,数据加密。 六个支持:支持WEB开发,支持缓存,支持线程并发验证,支持测试,支持用户切换,支持"记住我"功能。
• Authentication :身份认证,也可以理解为登录,验证用户身份。 • Authorization :权限验证,也可以理解为授权,验证用户是否拥有某个权限;即判断用户是否能进行什么操作。 • Session Manager :会话管理,用户登录后就是一次会话,在退出前,用户的所有信息都在会话中。 • Cryptography :数据加密,保护数据的安全性,常见的有密码的加盐加密。 • Web Support :支持Web开发。 • Caching :缓存,Shiro将用户信息、拥有的角色/权限数据缓存,以提高程序效率。 • Concurrency :支持多线程应用的并发验证,即在一个线程中开启另一个线程,Shiro能把权限自动传播过去。 • Testing :提供测试支持。 • Run As :允许一个用户以另一个用户的身份进行访问;前提是两个用户运行切换身份。 • Remember Me :记住我,常见的功能,即登录一次后,在指定时间内免登录。

Shiro 架构介绍
三个角色:当前用户 Subject,安全管理器 SecurityManager,权限配置域 Realm。
• Subject :代表当前用户,提供了很多方法,如login和logout。Subject 只是一个门面,与Subject的所有交互都会委托给SecurityManager,SecurityManager才是真正的执行者; • SecurityManager :安全管理器;Shiro的核心,它负责与Shiro的其他组件进行交互,即所有与安全有关的操作都会与SecurityManager 交互;且管理着所有的 Subject; • Realm :Shiro 从 Realm 获取安全数据(如用户、角色、权限),SecurityManager 要验证用户身份,必需要从 Realm 获取相应的用户信息,判断用户身份是否合法,判断用户角色或权限是否授权。
SpringBoot 整合SiteMesh
SiteMesh 是一个网页布局和修饰的框架,利用它可以将网页的内容和页面结构分离,以达到页面结构共享的目的。
SiteMesh 统一了页面的风格,减少了重复代码,提高了页面的复用率,是一款值得我们去学习的框架(也有很多坑)。当然,今天的主角是Shiro,这里只介绍它的基本用法。 SpringBoot 整合SiteMesh只需二个步骤: 第一步:配置拦截器FIlter,并在web中注册bean。 第二步:创建装饰页面,引入常用的css和js文件,统一系统样式。
配置拦截器FIlter
指定拦截的URL请求路径,指定装饰页面的文件全路径,指定不需要拦截的URL请求路径。这里拦截所有请求到装饰页面,只有登录页面和静态资源不拦截。
1import org.sitemesh.builder.SiteMeshFilterBuilder; 2import org.sitemesh.config.ConfigurableSiteMeshFilter; 3/** 4 * 配置SiteMesh拦截器FIlter,指定装饰页面和不需要拦截的路径 5 * @author itdragon 6 */ 7public class WebSiteMeshFilter extends ConfigurableSiteMeshFilter{ 8 9 @Override 10 protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) { 11 builder.addDecoratorPath("/*", "/WEB-INF/layouts/default.jsp") // 配置装饰页面 12 .addExcludedPath("/static/*") // 静态资源不拦截 13 .addExcludedPath("/login**"); // 登录页面不拦截 14 } 15 16} 17 18 19import org.springframework.boot.web.servlet.FilterRegistrationBean; 20import org.springframework.context.annotation.Bean; 21import org.springframework.context.annotation.Configuration; 22/** 23 * web.xml 配置 24 * @author itdragon 25 */ 26@Configuration 27public class WebConfig { 28 29 @Bean // 配置siteMesh3 30 public FilterRegistrationBean siteMeshFilter(){ 31 FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); 32 WebSiteMeshFilter siteMeshFilter = new WebSiteMeshFilter(); 33 filterRegistrationBean.setFilter(siteMeshFilter); 34 return filterRegistrationBean; 35 } 36 37}
创建装饰页面
SiteMesh语法 <sitemesh:write property='title'/> : 被修饰页面title的内容会在这里显示。 <sitemesh:write property='head'/> : 被修饰页面head的内容会在这里显示,除了title。 <sitemesh:write property='body'/> : 被修饰页面body的内容会在这里显示。 需要注意的是:SiteMesh的jar有OpenSymphony(最新版是2009年)和Apache(最新版是2015年),两者用法是有差异的。笔者选择的是Apache版本的jar。
1<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 2<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 3<!DOCTYPE html> 4<html> 5 <head> 6 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 7 <meta name="viewport" content="initial-scale=1.0, width=device-width, user-scalable=no" /> 8 <title>ITDragon系统-<sitemesh:write property='title'/></title> 9 <link type="image/x-icon" href="images/favicon.ico" rel="shortcut icon"> 10 <c:set var="ctx" value="${pageContext.request.contextPath}" /> 11 <link href="${ctx}/static/sb-admin-1.0.4/css/bootstrap.min.css" rel="stylesheet"> 12 <link href="${ctx}/static/sb-admin-1.0.4/css/sb-admin.css" rel="stylesheet"> 13 <sitemesh:write property='head'/> 14 </head> 15 <body> 16 <div id="wrapper"> 17 <%@ include file="/WEB-INF/layouts/header.jsp"%> 18 <div class='mainBody'> 19 <sitemesh:write property='body'/> 20 </div> 21 </div> 22 <script src="${ctx}/static/sb-admin-1.0.4/js/jquery.js"></script> 23 <script src="${ctx}/static/sb-admin-1.0.4/js/bootstrap.min.js"></script> 24 </body> 25</html>
SpringBoot 整合Shiro
这是本章的核心知识点,SpringBoot 整合Shiro 有三个步骤: 第一步:创建实体类:用户,角色,权限。确定三者关系,以方便Realm的授权工作。 第二步:创建自定义安全数据源Realm:负责用户登录认证,用户操作授权。 第三步:创建Spring整合Shiro配置类:配置拦截规则,生命周期,安全管理器,安全数据源,等。
创建实体类
实体类:User,SysRole,SysPermission。 权限设计思路: 1). 角色表确定系统菜单资源,权限表确定菜单操作资源。 2). 用户主要通过角色来获取权限,且一个用户可以拥有多个角色(不推荐,但必须支持该功能)。 3). 一个角色可以拥有多个权限,同时也可以有用多个用户。 4). 一个权限可以被多个角色使用。 5). 工作都是从易到难,我们可以先从“一个用户拥有一个角色,一个角色拥有多个权限”开始。 有了上面的分析,三个实体类代码如下,省略了get/set方法。
1import java.util.List; 2import javax.persistence.Entity; 3import javax.persistence.FetchType; 4import javax.persistence.GeneratedValue; 5import javax.persistence.GenerationType; 6import javax.persistence.Id; 7import javax.persistence.JoinColumn; 8import javax.persistence.JoinTable; 9import javax.persistence.ManyToMany; 10import javax.persistence.Table; 11import javax.persistence.Transient; 12/** 13 * 用户实体类 14 * @author itdragon 15 */ 16@Table(name="itdragon_user_shiro") 17@Entity 18public class User { 19 @Id 20 @GeneratedValue(strategy=GenerationType.AUTO) 21 private Long id; // 自增长主键,默认ID为1的账号为超级管理员 22 private String account; // 登录的账号 23 private String userName; // 注册的昵称 24 @Transient 25 private String plainPassword; // 登录时的密码,不持久化到数据库 26 private String password; // 加密后的密码 27 private String salt; // 用于加密的盐 28 private String iphone; // 手机号 29 private String email; // 邮箱 30 private String platform; // 用户来自的平台 31 private String createdDate; // 用户注册时间 32 private String updatedDate; // 用户最后一次登录时间 33 @ManyToMany(fetch=FetchType.EAGER) 34 @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") }) 35 private List<SysRole> roleList; // 一个用户拥有多个角色 36 private Integer status; // 用户状态,0表示用户已删除 37} 38 39 40import java.util.List; 41import javax.persistence.Entity; 42import javax.persistence.FetchType; 43import javax.persistence.GeneratedValue; 44import javax.persistence.Id; 45import javax.persistence.JoinColumn; 46import javax.persistence.JoinTable; 47import javax.persistence.ManyToMany; 48import javax.persistence.Table; 49/** 50 * 角色表,决定用户可以访问的页面 51 * @author itdragon 52 */ 53@Table(name="itdragon_sysrole") 54@Entity 55public class SysRole { 56 @Id 57 @GeneratedValue 58 private Integer id; 59 private String role; // 角色 60 private String description; // 角色描述 61 private Boolean available = Boolean.FALSE; // 默认不可用 62 //角色 -- 权限关系:多对多关系; 取出这条数据时,把它关联的数据也同时取出放入内存中 63 @ManyToMany(fetch=FetchType.EAGER) 64 @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")}) 65 private List<SysPermission> permissions; 66 // 用户 - 角色关系:多对多关系; 67 @ManyToMany 68 @JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")}) 69 private List<User> users; 70} 71 72 73import java.util.List; 74import javax.persistence.Entity; 75import javax.persistence.GeneratedValue; 76import javax.persistence.Id; 77import javax.persistence.JoinColumn; 78import javax.persistence.JoinTable; 79import javax.persistence.ManyToMany; 80import javax.persistence.Table; 81/** 82 * 权限表,决定用户的具体操作 83 * @author itdragon 84 */ 85@Table(name = "itdragon_syspermission") 86@Entity 87public class SysPermission { 88 @Id 89 @GeneratedValue 90 private Integer id; 91 private String name; // 名称 92 private String url; // 资源路径 93 private String permission; // 权限字符串 如:employees:create,employees:update,employees:delete 94 private Boolean available = Boolean.FALSE; // 默认不可用 95 @ManyToMany 96 @JoinTable(name = "SysRolePermission", joinColumns = { @JoinColumn(name = "permissionId") }, inverseJoinColumns = {@JoinColumn(name = "roleId") }) 97 private List<SysRole> roles; 98}
创建自定义安全数据源Realm
Shiro 从 Realm 获取安全数据(如用户、角色、权限),SecurityManager 身份认证和权限认证都是从Realm中获取相应的用户信息,然后做比较判断是否有身份登录,是否有权限操作。 Shiro 支持多个Realm。同时也有不同的认证策略: • FirstSuccessfulStrategy : 只要有一个Realm成功就返回,后面的忽略; • AtLeastOneSuccessfulStrategy : 只要有一个Realm成功就通过,返回所有认证成功的信息,默认; • AllSuccessfulStrategy : 必须所有Realm都成功才算通过
1import org.apache.shiro.authc.AuthenticationException; 2import org.apache.shiro.authc.AuthenticationInfo; 3import org.apache.shiro.authc.AuthenticationToken; 4import org.apache.shiro.authc.SimpleAuthenticationInfo; 5import org.apache.shiro.authz.AuthorizationInfo; 6import org.apache.shiro.authz.SimpleAuthorizationInfo; 7import org.apache.shiro.realm.AuthorizingRealm; 8import org.apache.shiro.subject.PrincipalCollection; 9import org.apache.shiro.util.ByteSource; 10import org.slf4j.Logger; 11import org.slf4j.LoggerFactory; 12import org.springframework.beans.factory.annotation.Autowired; 13import com.itdragon.pojo.SysPermission; 14import com.itdragon.pojo.SysRole; 15import com.itdragon.pojo.User; 16import com.itdragon.service.UserService; 17 18/** 19 * 自定义安全数据Realm,重点 20 * @author itdragon 21 */ 22public class ITDragonShiroRealm extends AuthorizingRealm { 23 24 private static final transient Logger log = LoggerFactory.getLogger(ITDragonShiroRealm.class); 25 26 @Autowired 27 private UserService userService; 28 29 /** 30 * 授权 31 */ 32 @Override 33 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 34 log.info("^^^^^^^^^^^^^^^^^^^^ ITDragon 配置当前用户权限"); 35 String username = (String) principals.getPrimaryPrincipal(); 36 User user = userService.findByAccount(username); 37 if(null == user){ 38 return null; 39 } 40 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); 41 for (SysRole role : user.getRoleList()) { 42 authorizationInfo.addRole(role.getRole()); // 添加角色 43 for (SysPermission permission : role.getPermissions()) { 44 authorizationInfo.addStringPermission(permission.getPermission()); // 添加具体权限 45 } 46 } 47 return authorizationInfo; 48 } 49 50 /** 51 * 身份认证 52 */ 53 @Override 54 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) 55 throws AuthenticationException { 56 log.info("^^^^^^^^^^^^^^^^^^^^ ITDragon 认证用户身份信息"); 57 String username = (String) token.getPrincipal(); // 获取用户登录账号 58 User userInfo = userService.findByAccount(username); // 通过账号查加密后的密码和盐,这里一般从缓存读取 59 if(null == userInfo){ 60 return null; 61 } 62 // 1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象. 63 Object principal = username; 64 // 2). credentials: 加密后的密码. 65 Object credentials = userInfo.getPassword(); 66 // 3). realmName: 当前 realm 对象的唯一名字. 调用父类的 getName() 方法 67 String realmName = getName(); 68 // 4). credentialsSalt: 盐值. 注意类型是ByteSource 69 ByteSource credentialsSalt = ByteSource.Util.bytes(userInfo.getSalt()); 70 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName); 71 return info; 72 } 73}
创建Spring整合Shiro配置类
第一步:配置Shiro拦截器,指定URL请求的权限。首先静态资源和登录请求匿名访问,然后是用户登出操作,最后是所有请求都需身份认证。Shiro拦截器优先级是从上到下,切勿将/**=authc,放在前面。 第二步:配置Shiro生命周期处理器, 第三步:配置自定义Realm,负责身份认证和授权。 第四步:配置安全管理器SecurityManager,Shiro的核心。
1import java.util.LinkedHashMap; 2import java.util.Map; 3import org.apache.shiro.authc.credential.HashedCredentialsMatcher; 4import org.apache.shiro.spring.LifecycleBeanPostProcessor; 5import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; 6import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 7import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 8import org.slf4j.Logger; 9import org.slf4j.LoggerFactory; 10import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; 11import org.springframework.context.annotation.Bean; 12import org.springframework.context.annotation.Configuration; 13import org.springframework.context.annotation.DependsOn; 14/** 15 * Shiro 配置,重点 16 * @author itdragon 17 */ 18@Configuration 19public class ShiroSpringConfig { 20 21 private static final transient Logger log = LoggerFactory.getLogger(ShiroSpringConfig.class); 22 23 /** 24 * 配置拦截器 25 * 26 * 定义拦截URL权限,优先级从上到下 27 * 1). anon : 匿名访问,无需登录 28 * 2). authc : 登录后才能访问 29 * 3). logout: 登出 30 * 4). roles : 角色过滤器 31 * 32 * URL 匹配风格 33 * 1). ?:匹配一个字符,如 /admin? 将匹配 /admin1,但不匹配 /admin 或 /admin/; 34 * 2). *:匹配零个或多个字符串,如 /admin* 将匹配 /admin 或/admin123,但不匹配 /admin/1; 35 * 2). **:匹配路径中的零个或多个路径,如 /admin/** 将匹配 /admin/a 或 /admin/a/b 36 * 37 * 配置身份验证成功,失败的跳转路径 38 */ 39 @Bean 40 public ShiroFilterFactoryBean shirFilter(DefaultWebSecurityManager securityManager) { 41 log.info("^^^^^^^^^^^^^^^^^^^^ ITDragon 配置Shiro拦截工厂"); 42 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 43 shiroFilterFactoryBean.setSecurityManager(securityManager); 44 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); 45 filterChainDefinitionMap.put("/static/**", "anon"); // 静态资源匿名访问 46 filterChainDefinitionMap.put("/employees/login", "anon");// 登录匿名访问 47 filterChainDefinitionMap.put("/logout", "logout"); // 用户退出,只需配置logout即可实现该功能 48 filterChainDefinitionMap.put("/**", "authc"); // 其他路径均需要身份认证,一般位于最下面,优先级最低 49 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); 50 shiroFilterFactoryBean.setLoginUrl("/login"); // 登录的路径 51 shiroFilterFactoryBean.setSuccessUrl("/dashboard"); // 登录成功后跳转的路径 52 shiroFilterFactoryBean.setUnauthorizedUrl("/403"); // 验证失败后跳转的路径 53 return shiroFilterFactoryBean; 54 } 55 56 /** 57 * 配置Shiro生命周期处理器 58 */ 59 @Bean 60 public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){ 61 return new LifecycleBeanPostProcessor(); 62 } 63 64 /** 65 * 自动创建代理类,若不添加,Shiro的注解可能不会生效。 66 */ 67 @Bean 68 @DependsOn({"lifecycleBeanPostProcessor"}) 69 public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){ 70 DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); 71 advisorAutoProxyCreator.setProxyTargetClass(true); 72 return advisorAutoProxyCreator; 73 } 74 75 /** 76 * 开启Shiro的注解 77 */ 78 @Bean 79 public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){ 80 AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); 81 authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); 82 return authorizationAttributeSourceAdvisor; 83 } 84 85 /** 86 * 配置加密匹配,使用MD5的方式,进行1024次加密 87 */ 88 @Bean 89 public HashedCredentialsMatcher hashedCredentialsMatcher() { 90 HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); 91 hashedCredentialsMatcher.setHashAlgorithmName("MD5"); 92 hashedCredentialsMatcher.setHashIterations(1024); 93 return hashedCredentialsMatcher; 94 } 95 96 /** 97 * 自定义Realm,可以多个 98 */ 99 @Bean 100 public ITDragonShiroRealm itDragonShiroRealm() { 101 ITDragonShiroRealm itDragonShiroRealm = new ITDragonShiroRealm(); 102 itDragonShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); 103 return itDragonShiroRealm; 104 } 105 106 /** 107 * SecurityManager 安全管理器;Shiro的核心 108 */ 109 @Bean 110 public DefaultWebSecurityManager securityManager() { 111 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 112 securityManager.setRealm(itDragonShiroRealm()); 113 return securityManager; 114 } 115 116}
实现业务逻辑
系统有四个菜单:控制面板 Dashboard,员工管理 Employees,权限管理 Permissions,角色管理 Roles 。 系统有三个角色:超级管理员 admin, 经理 manager, 普通员工 staff 。 业务的逻辑要求:
- admin角色可以访问所有菜单,manager角色除了Roles菜单外都可以访问,staff角色只能访问Dashboard和Employees菜单 。
- admin角色拥有删除用户信息的权限,其他两个角色没有权限。
实现业务逻辑步骤: 第一步:模拟数据,创建用户,角色,权限数据。 第二步:左侧菜单权限配置,需要用到Shiro的标签式授权。 第三步:在删除用户的Controller层方法上配置操作权限,需要用到Shiro的注解式授权。 第四步:权限验证失败统一处理。
配置数据
sql文件路径:https://github.com/ITDragonBlog/daydayup/tree/master/Shiro/springboot-shiro/sql 建议先执行sql文件,再启动项目。 用户密码通常采用加盐加密的方式,笔者采用MD5的加密方式,以UUID作为盐,进行1024次加密。代码如下:
1import java.util.UUID; 2import org.apache.shiro.crypto.hash.SimpleHash; 3import org.apache.shiro.util.ByteSource; 4import com.itdragon.pojo.User; 5/** 6 * 工具类 7 * @author itdragon 8 */ 9public class ItdragonUtils { 10 11 private static final String ALGORITHM_NAME = "MD5"; 12 private static final Integer HASH_ITERATIONS = 1024; 13 14 public static void entryptPassword(User user) { 15 String salt = UUID.randomUUID().toString(); 16 String temPassword = user.getPlainPassword(); 17 Object md5Password = new SimpleHash(ALGORITHM_NAME, temPassword, ByteSource.Util.bytes(salt), HASH_ITERATIONS); 18 user.setSalt(salt); 19 user.setPassword(md5Password.toString()); 20 } 21 22}
左侧菜单权限配置
系统使用了SiteMesh框架,左侧菜单页面属于修饰页面的一部分。只需要在一个文件中添加shiro的标签,就可以在整个系统生效,耦合性很低。 <shiro:guest> : 允许游客访问的代码块 <shiro:user> : 允许已经验证或者通过"记住我"登录的用户才能访问的代码块。 <shiro:authenticated> : 只有通过登录操作认证身份,而并非通过"记住我"登录的用户才能访问的代码块。 <shiro:notAuthenticated> : 未登录的用户显示的代码块。 <shiro:principal> : 显示当前登录的用户信息。 <shiro:hasRole name="admin"> : 只有拥有admin角色的用户才能访问的代码块。 <shiro:hasAnyRoles name="admin,manager"> : 只有拥有admin或者manager角色的用户才能访问的代码块。 <shiro:lacksRole name="admin"> : 没有admin角色的用户显示的代码块 <shiro:hasPermission name="admin:delete"> : 只有拥有"admin:delete"权限的用户才能访问的代码块。 <shiro:lacksPermission name="admin:delete"> : 没有"admin:delete"权限的用户显示的代码块。
1<div class="collapse navbar-collapse navbar-ex1-collapse"> 2 <ul class="nav navbar-nav side-nav itdragon-nav"> 3 <li class="active"> 4 <a href="https://my.oschina.net/dashboard"><i class="fa fa-fw fa-dashboard"></i> Dashboard</a> 5 </li> 6 <li> 7 <a href="https://my.oschina.net/employees"><i class="fa fa-fw fa-bar-chart-o"></i> Employees</a> 8 </li> 9 <!-- 只有角色为admin或manager的用户才有权限访问 --> 10 <shiro:hasAnyRoles name="admin,manager"> 11 <li> 12 <a href="https://my.oschina.net/permission"><i class="fa fa-fw fa-table"></i> Permissions</a> 13 </li> 14 </shiro:hasAnyRoles> 15 <!-- 只有角色为admin的用户才有权限访问 --> 16 <shiro:hasRole name="admin"> 17 <li> 18 <a href="https://my.oschina.net/roles"><i class="fa fa-fw fa-file"></i> Roles</a> 19 </li> 20 </shiro:hasRole> 21 </ul> 22</div>
在操作上添加权限
Shiro常见的权限注解有: @RequiresAuthentication : 表示当前 Subject 已经认证登录的用户才能调用的代码块。 @RequiresUser : 表示当前 Subject 已经身份验证或通过记住我登录的。 @RequiresGuest : 表示当前 Subject 没有身份验证,即是游客身份。 @RequiresRoles(value={"admin", "user"}, logical=Logical.AND) : 表示当前 Subject 需要角色 admin和user @RequiresPermissions (value={"user:update", "user:delete"}, logical= Logical.OR) : 表示当前 Subject 需要权限 user:update或user:delete。 这里值得注意的是:如果你的注解没有生效,很可能没有配置Shiro注解开启的问题。
1@RequestMapping(value = "delete/{id}") 2@RequiresPermissions(value={"employees:delete"}) 3public String delete(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) { 4 userService.deleteUser(id); 5 redirectAttributes.addFlashAttribute("message", "删除用户成功"); 6 return "redirect:/employees"; 7}
权限验证失败统一处理
Shiro提供权限验证失败跳转页面的功能,但这个逻辑是不友好的。我们需要统一处理权限验证失败,并返回执行失败的页面。
1import org.apache.shiro.web.util.WebUtils; 2import org.springframework.web.bind.annotation.ControllerAdvice; 3import org.springframework.web.bind.annotation.ExceptionHandler; 4import org.springframework.web.servlet.mvc.support.RedirectAttributes; 5/** 6 * 异常统一处理 7 * @author itdragon 8 */ 9@ControllerAdvice 10public class ExceptionController { 11 12 @ExceptionHandler(org.apache.shiro.authz.AuthorizationException.class) 13 public String handleException(RedirectAttributes redirectAttributes, Exception exception, HttpServletRequest request) { 14 redirectAttributes.addFlashAttribute("message", "抱歉!您没有权限执行这个操作,请联系管理员!"); 15 String url = WebUtils.getRequestUri(request); 16 return "redirect:/" + url.split("/")[1]; // 请求的规则 : /page/operate 17 } 18 19}
Shiro和SpringSecurity
- Shiro使用更简单,更容易上手。
- Spring Security功能更强大,和Spring无缝整合,但学习门槛比Shiro高。
- 我的建议是两个都可以学习,谁知道公司下一秒会选择什么框架。。。
总结
- Shiro 四个核心功能:身份认证,授权,数据加密,Seesion管理。
- Shiro 三个重要角色:Subject,SecurityManager,Realm。
- Shiro 五个常见开发:自定义Realm,配置拦截器,标签式授权控制菜单,注解式授权控制操作,权限不够异常统一处理。
- 项目搭建推荐从拦截器开始,然后再是身份认证,角色权限认证,操作权限认证。
- Shiro 其他知识后续介绍。
到这里Shiro 核心功能案例讲解 基于SpringBoot 的文章就写完了,一个基本的系统也搭完了。还有很多缺陷和建议,不吝赐教!如果文章对你有帮助,可以点个"推荐",也可以"关注"我,获得更多丰富的知识。
其他知识查考文献
Shiro 权限注解 :http://blog.csdn.net/w_stronger/article/details/73109248
Spring @ControllerAdvice注解 : http://blog.csdn.net/jackfrued/article/details/76710885
bootstrap 模块页面 : https://startbootstrap.com/template-categories/all/