前面已经介绍过springBoot和mybatis、JPA的整合,本篇主要是在上一篇的基础上整合Shiro进行权限的管理。
源码链接
1、简介
先简单介绍下shiro吧,其实它就是一个安全框架,相比spring Security使用起来更简单易懂。引用一张架构图(图片来自官网),从图中大家可以看到他的三大核心:
-Subject 当前用户操作
- SecurityManager 用于管理所有的Subject
- Realms 用于进行权限信息的验证,也是我们需要自己实现的。
我们需要实现Realms的Authentication 和 Authorization。其中 Authentication 是用来验证用户身份,Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。
既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。
另外我们可以通过Shiro 提供的会话管理来获取Session中的信息。Shiro 也提供了缓存支持,使用 CacheManager 来管理。

要集成shiro我们必须知道他的几个核心对象,分别是:
第一:ShiroFilterFactory,Shiro过滤器工厂类,具体的实现类是:ShiroFilterFactoryBean,此实现类是依赖于SecurityManager安全管理器。
第二:SecurityManager,Shiro的安全管理,主要是身份认证的管理,缓存管理,cookie管理,所以在实际开发中我们主要是和SecurityManager进行打交道的,ShiroFilterFactory主要配置好了Filter就可以了。当然SecurityManager并进行身份认证缓存的实现,我们需要进行对应的编码然后进行注入到安全管理器中。
第三:Realm,用于身份信息权限信息的验证。
第四:其它的就是缓存管理,记住登录之类的,这些大部分都是需要自己进行简单的实现,然后注入到SecurityManager让Shiro的安全管理器进行管理就好了。
2、整合完成demo
概念介绍完了 ,我们开始动手,完成我们的demo,具体步骤如下:
(a) pom.xml中添加Shiro依赖;
(b) 注入Shiro Factory和SecurityManager。
(c) 身份认证
(d) 权限控制
(a) 添加Shiro依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.pxk</groupId> <artifactId>SpringBootDemo_JPA</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>SpringBootDemo_JPA Maven Webapp</name> <url>http://maven.apache.org</url> <build> <finalName>SpringBootDemo_JPA</finalName> </build> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.6.RELEASE</version> <relativePath /> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- web容器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>1.5.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.10.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency> <!--日志 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!-- druid连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.18</version> </dependency> <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</artifactId> <version>1.2.3</version> </dependency> </dependencies> </project>
(b) 注入Shiro Factory和SecurityManager
使用springBoot的配置方式,新建config类,主要配置两个类ShiroFilterFactory和SecurityManager
package com.pxk.springboot.config; import java.util.LinkedHashMap; import java.util.Map; import org.apache.shiro.mgt.SecurityManager; 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; @Configuration public class ShiroConfiguration { @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置SecuritManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 拦截器 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 配置退出过滤器,其中的具体代码Shiro已经替我们实现了 filterChainDefinitionMap.put("/logout", "logout"); // <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了; // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put("/**", "authc"); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/login"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/index"); // 未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); return securityManager; } }
(c) 身份认证
在认证、授权内部实现机制中都有提到,最终处理都将交给Real进行处理。因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO.
认证实现
Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。
该方法主要执行以下操作:
1、检查提交的进行认证的令牌信息
2、根据令牌信息从数据源(通常为数据库)中获取用户信息
3、对用户信息进行匹配验证。
4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
5、验证失败则抛出AuthenticationException异常信息。
而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo (),重写获取用户信息的方法。
既然需要进行身份权限控制,那么少不了创建用户实体类,权限实体类。
在权限管理系统中,有这么几个角色很重要,这个要是不清楚的话,那么就很难理解,我们为什么这么编码了。第一是用户表:在用户表中保存了用户的基本信息,账号、密码、姓名,性别等;第二是:权限表(资源+控制权限):这个表中主要是保存了用户的URL地址,权限信息;第三就是角色表:在这个表重要保存了系统存在的角色;第四就是关联表:用户-角色管理表(用户在系统中都有什么角色,比如admin,vip等),角色-权限关联表(每个角色都有什么权限可以进行操作)。依据这个理论,我们进行来进行编码,很明显的我们第一步就是要进行实体类的创建。在这里我们使用Mysql和JPA进行操作数据库。
新建实体类UserInfo、SysRole、SysPermission(采用逆向工程生成数据库表,然后导入数据)--非关键的get、set方法省略
UserInfo.java
package com.pxk.springboot.domain; import java.io.Serializable; import java.util.List; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; /** * 用户信息. * * @author Administrator * */ @Entity public class UserInfo implements Serializable { /** * */ private static final long serialVersionUID = 1L; @Id @GeneratedValue private long uid;// 用户id @Column(unique = true) private String username;// 帐号 private String name;// 名称(昵称或者真实姓名,不同系统不同定义) private String password; // 密码; private String salt;// 加密密码的盐 private byte state;// 用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , // 1:正常状态,2:用户被锁定. @ManyToMany(fetch = FetchType.EAGER) // 立即从数据库中进行加载数据 @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns = { @JoinColumn(name = "roleId") }) private List<SysRole> roleList;// 一个用户具有多个角色 /** * 密码盐. * * @return */ public String getCredentialsSalt() { return this.username + this.salt; } @Override public String toString() { return "UserInfo [uid=" + uid + ", username=" + username + ", name=" + name + ", password=" + password + ", salt=" + salt + ", state=" + state + "]"; } }
SysRole.java
package com.pxk.springboot.domain; import java.io.Serializable; import java.util.List; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; /** * 系统角色实体类; * * @author Administrator * */ @Entity public class SysRole implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Long id; // 编号 private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的: private String description; // 角色描述,UI界面显示使用 private Boolean available = Boolean.FALSE; // 是否可用,如果不可用将不会添加给用户 // 角色 -- 权限关系:多对多关系; @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "SysRolePermission", joinColumns = { @JoinColumn(name = "roleId") }, inverseJoinColumns = { @JoinColumn(name = "permissionId") }) private List<SysPermission> permissions; // 用户 - 角色关系定义; @ManyToMany @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "roleId") }, inverseJoinColumns = { @JoinColumn(name = "uid") }) private List<UserInfo> userInfos;// 一个角色对应多个用户 @Override public String toString() { return "SysRole [id=" + id + ", role=" + role + ", description=" + description + ", available=" + available + ", permissions=" + permissions + "]"; } }
SysPermission.java
package com.pxk.springboot.domain; import java.io.Serializable; import java.util.List; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; /** * 权限实体类; * */ @Entity public class SysPermission implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private long id;// 主键. private String name;// 名称. @Column(columnDefinition = "enum('menu','button')") private String resourceType;// 资源类型,[menu|button] private String url;// 资源路径. private String permission; // 权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view private Long parentId; // 父编号 private String parentIds; // 父编号列表 private Boolean available = Boolean.FALSE; @Override public String toString() { return "SysPermission [id=" + id + ", name=" + name + ", resourceType=" + resourceType + ", url=" + url + ", permission=" + permission + ", parentId=" + parentId + ", parentIds=" + parentIds + ", available=" + available + "]"; } }
MyShiroRealm.java
该类是实现权限认证的核心,需要我们手动实现
package com.pxk.springboot.config; import javax.annotation.Resource; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; 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 com.pxk.springboot.domain.SysPermission; import com.pxk.springboot.domain.SysRole; import com.pxk.springboot.domain.UserInfo; import com.pxk.springboot.serivce.UserInfoService; /** * 身份校验核心类 * * @author Administrator * */ public class MyShiroRealm extends AuthorizingRealm { @Resource private UserInfoService userInfoService; /** * 认证信息(身份验证) Authentication 是用来验证用户身份 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("MyShiroRealm.doGetAuthenticationInfo()"); // 获取用户的输入帐号 String username = (String) token.getPrincipal(); System.out.println(token.getCredentials()); // 通过username从数据库中查找 User对象,如果找到,没找到. // 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 UserInfo userInfo = userInfoService.findByUsername(username); System.out.println("----->>userInfo=" + userInfo); if (userInfo == null) { return null; } SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo, // 用户名 userInfo.getPassword(), // 密码 ByteSource.Util.bytes(userInfo.getCredentialsSalt()), // salt=username+salt getName() // realm name ); return authenticationInfo; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // TODO Auto-generated method stub System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()"); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); UserInfo userInfo = (UserInfo) principals.getPrimaryPrincipal(); for (SysRole role : userInfo.getRoleList()) { authorizationInfo.addRole(role.getRole()); System.out.println(role.getPermissions()); for (SysPermission p : role.getPermissions()) { System.out.println(p); authorizationInfo.addStringPermission(p.getPermission()); } } return authorizationInfo; } }
核心功能基本完成,接下来就是编写controller和页面了
页面代码我就不详细贴出了,直接给大家源码地址进行下载。