集成shiro大概分这么一个步骤:
(一) pom.xml中添加Shiro依赖;
(二) 注入Shiro Factory和SecurityManager。
(三) 身份认证
(四) 权限控制
一: pom.xml中添加Shiro依赖
1.1: 要使用Shiro进行权限控制,那么很明显的就需要添加对Shiro的依赖包,在pom.xml中加入如下配置:
<!-- shiro权限控制框架 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
pom.xmlpom.xml完整配置
<?xml version="1.0" encoding="UTF-8"?> <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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.gt</groupId> <artifactId>commonService</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>springboot-mybatis</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.10.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- 热部署 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId> spring-boot-configuration-processor </artifactId> <optional>true</optional> </dependency> <!--jsp页面使用jstl标签--> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <!--用于编译jsp--> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency> <!-- jpa对象持久化 利用该jar包,通过bean直接生成数据库表--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- mysql支持 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--spring boot 整合 mybatis 依赖--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> <!-- 数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.5</version> </dependency> <!-- json支持 --> <dependency> <groupId>net.sf.json-lib</groupId> <artifactId>json-lib</artifactId> <version>2.4</version> <classifier>jdk15</classifier> </dependency> <!-- shiro权限控制框架 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.3.2</version> </dependency> <!-- 包含支持UI模版(Velocity,FreeMarker,JasperReports), 邮件服务, 脚本服务(JRuby), 缓存Cache(EHCache), 任务计划Scheduling(uartz)。 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> <!-- 单点登录 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-cas</artifactId> <version>1.2.4</version> </dependency> </dependencies> <build> <!-- 最终项目名,最终项目名 --> <finalName>commonService</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <!-- maven打包的时候告诉maven不需要web.xml,否刚会报找不到web.xml错误 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.4</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build> </project>
1.2:application.yml部分文件配置
spring: profiles: active: dev mvc: view: prefix: /WEB-INF/jsp/ suffix: .jsp datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/testDemo?characterEncoding=utf8&useSSL=true username: root password: root ######################################################## ### Java Persistence Api ######################################################## jpa: database: MYSQL show-sql: true hibernate: ddl-auto: update naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl #### ##org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy ##org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl ##对于PhysicalNamingStrategyStandardImpl有DefaultNamingStrategy的效果;对于SpringPhysicalNamingStrategy 有 ##ImprovedNamingStrategy的效果。 ##### properties: hibernate: dialect: org.hibernate.dialect.MySQL5Dialect mybatis: mapper-locations: classpath:mapper/*Mapper.xml type-aliases-package: com.gt.entity
1.3:application-dev.yml 开发配置文件
server: port: 8081 context-path: /commonService
1.4:ehcache-shiro.xml 缓存配置文件
<?xml version="1.0" encoding="UTF-8"?> <ehcache name="es"> <diskStore path="java.io.tmpdir"/> <!-- name:缓存名称。 maxElementsInMemory:缓存最大数目 maxElementsOnDisk:硬盘最大缓存个数。 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 overflowToDisk:是否保存到磁盘,当系统当机时 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 clearOnFlush:内存数量最大时是否清除。 memoryStoreEvictionPolicy: Ehcache的三种清空策略; FIFO,first in first out,这个是大家最熟的,先进先出。 LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。 LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" /> <!-- 登录记录缓存锁定10分钟 --> <cache name="passwordRetryCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"> </cache> </ehcache>
二:注入Shiro Factory和SecurityManager。
package com.gt.config; import java.util.LinkedHashMap; import java.util.Map; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.mgt.SecurityManager; 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.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.sun.org.apache.xerces.internal.impl.dv.util.Base64; /** * Shiro 配置 * Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。 既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。 * * @author gt * @version v.0.1 */ @Configuration public class ShiroConfiguration { /** * shiro缓存管理器; * 需要注入对应的其它的实体类中: * 1、安全管理器:securityManager * 可见securityManager是整个shiro的核心; * @return */ @Bean public EhCacheManager ehCacheManager(){ System.out.println("ShiroConfiguration.getEhCacheManager()"); EhCacheManager cacheManager = new EhCacheManager(); cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml"); return cacheManager; } @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //设置realm. securityManager.setRealm(myShiroRealm()); //注入缓存 securityManager.setCacheManager(ehCacheManager()); return securityManager; } /* @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); securityManager.setCacheManager(ehCacheManager()); return securityManager; }*/ /** * ShiroFilterFactoryBean 处理拦截资源文件问题。 * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在 * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager * Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过 3、部分过滤器可指定参数,如perms,roles * */ @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){ System.out.println("ShiroConfiguration.shirFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); //拦截器. Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>(); //配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了 filterChainDefinitionMap.put("/logout", "logout"); filterChainDefinitionMap.put("/system/login", "anon"); //<!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了; //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put("/**", "authc"); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/system/toLogin"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/system/index"); //未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl("/system/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * 开启shiro aop注解支持. * 使用代理方式;所以需要开启代码支持; * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * 身份认证realm; * (这个需要自己写,账号密码校验;权限等) * @return */ @Bean public MyShiroRealm myShiroRealm(){ MyShiroRealm myShiroRealm = new MyShiroRealm(); return myShiroRealm; } }
这里说下:ShiroFilterFactory中已经由Shiro官方实现的过滤器:
Shiro内置的FilterChain
Filter Name |
Class |
anon |
org.apache.shiro.web.filter.authc.AnonymousFilter |
authc |
org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic |
org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
perms |
org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port |
org.apache.shiro.web.filter.authz.PortFilter |
rest |
org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles |
org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
ssl |
org.apache.shiro.web.filter.authz.SslFilter |
user |
org.apache.shiro.web.filter.authc.UserFilter |
anon:所有url都都可以匿名访问;
authc: 需要认证才能进行访问;
user:配置记住我或认证通过可以访问;
这几个是我们会用到的,在这里说明下,其它的请自行查询文档进行学习。
这时候我们运行程序,访问/index页面我们会发现自动跳转到了login页面,当然这个时候输入账号和密码是无法进行访问的。下面这才是重点:任何身份认证,如何权限控制。
三:身份认证
在认证、授权内部实现机制中都有提到,最终处理都将交给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进行操作数据库。
3.1:那么我们先在pom.xml中引入mysql和JPA的依赖:
<!-- jpa对象持久化 利用该jar包,通过bean直接生成数据库表-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- mysql支持 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
3.2 准备工作准备好之后,那么就可以编写实体类了:
UserInfo.java
package com.gt.entity; 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 gt * @version v.0.1 */ @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;// 一个用户具有多个角色 public List<SysRole> getRoleList() { return roleList; } public void setRoleList(List<SysRole> roleList) { this.roleList = roleList; } public long getUid() { return uid; } public void setUid(long uid) { this.uid = uid; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getSalt() { return salt; } public void setSalt(String salt) { this.salt = salt; } public byte getState() { return state; } public void setState(byte state) { this.state = state; } /** * 密码盐. * @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 + "]"; } }
3.3 SysRole.java
package com.gt.entity; 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 gt * @version v.0.1 */ @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;// 一个角色对应多个用户 public List<UserInfo> getUserInfos() { return userInfos; } public void setUserInfos(List<UserInfo> userInfos) { this.userInfos = userInfos; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Boolean getAvailable() { return available; } public void setAvailable(Boolean available) { this.available = available; } public List<SysPermission> getPermissions() { return permissions; } public void setPermissions(List<SysPermission> permissions) { this.permissions = permissions; } @Override public String toString() { return "SysRole [id=" + id + ", role=" + role + ", description=" + description + ", available=" + available + ", permissions=" + permissions + "]"; } }
3.4:SysPermission.java
package com.gt.entity; import java.io.Serializable; import java.util.List; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; /** * 权限实体类; * @author gt * @version v.0.1 */ @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; @ManyToMany @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")}) private List<SysRole> roles; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getResourceType() { return resourceType; } public void setResourceType(String resourceType) { this.resourceType = resourceType; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getPermission() { return permission; } public void setPermission(String permission) { this.permission = permission; } public Long getParentId() { return parentId; } public void setParentId(Long parentId) { this.parentId = parentId; } public String getParentIds() { return parentIds; } public void setParentIds(String parentIds) { this.parentIds = parentIds; } public Boolean getAvailable() { return available; } public void setAvailable(Boolean available) { this.available = available; } public List<SysRole> getRoles() { return roles; } public void setRoles(List<SysRole> roles) { this.roles = roles; } @Override public String toString() { return "SysPermission [id=" + id + ", name=" + name + ", resourceType=" + resourceType + ", url=" + url + ", permission=" + permission + ", parentId=" + parentId + ", parentIds=" + parentIds + ", available=" + available + ", roles=" + roles + "]"; } }
ok,到这里实体类就编码完毕了,在这里我们看到的是3个实体类,UserInfo,SysRole,SysPermission,对应的是数据库的五张表:
1表UserInfo、2表SysUserRole、3表SysRole、4表SysRolePermission、5表SysPermission
这时候运行程序,就会自动建表,然后我们添加一些数据:
INSERT INTO `SysPermission` VALUES ('1', 1, '用户管理', '0', '0/', 'userInfo:view', 'menu', 'userInfo/userList'); INSERT INTO `SysPermission` VALUES ('2', 1, '用户添加', '1', '0/1', 'userInfo:add', 'button', 'userInfo/userAdd'); INSERT INTO `SysPermission` VALUES ('3', 1, '用户删除', '1', '0/1', 'userInfo:del', 'button', 'userInfo/userDel'); INSERT INTO `SysRole` VALUES ('1', 1, '管理员', 'admin'); INSERT INTO `SysRole` VALUES ('2', 1, 'VIP会员', 'vip'); INSERT INTO `SysRolePermission` VALUES ('1', 1); INSERT INTO `SysRolePermission` VALUES ('1', 2); INSERT INTO `SysUserRole` VALUES ('1', 1); INSERT INTO `SysUserRole` VALUES ('1', 2); INSERT INTO `UserInfo` VALUES ('1', '管理员', 'admin', 'e10adc3949ba59abbe56e057f20f883e', '8d78869f470951332959580424d4bf4f', '0');
这时候数据都准备完毕了,那么接下来就应该编写UserInfoMapper进行访问数据了
UserInfoMapper.java
package com.gt.mapper; import java.util.List; import org.apache.ibatis.annotations.Param; import com.gt.entity.SysRole; import com.gt.entity.UserInfo; public interface UserInfoMapper { /**通过username查找用户信息;*/ public UserInfo findByUsername(@Param("username") String username); /**通过username查找用户权限;*/ public List<SysRole> findRoleListByUsername(@Param("uid") Long uid); }
userInfoMapper.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.gt.mapper.UserInfoMapper" > <select id="findByUsername" resultType="com.gt.entity.UserInfo"> select * from UserInfo WHERE 1=1 <if test="username !=null and username !=''"> and username= #{username} </if> </select> <select id="findRoleListByUsername" resultMap="userrole"> <!-- SELECT ui.*,sur.*,sr.*,srp.*,sp.* --> SELECT sr.id,sr.available,sr.role,sr.description, sp.id spId,sp.url,sp.permission,sp.resourceType,sp.available, sp.name roleName,sp.parentId,sp.parentIds, sr.id srId,sr.role,sr.description,sr.available FROM UserInfo ui inner JOIN SysUserRole sur on sur.uid=ui.uid inner JOIN SysRole sr on sur.roleId=sr.id inner JOIN SysRolePermission srp on sur.roleId=srp.roleId inner JOIN SysPermission sp on srp.permissionId=sp.id WHERE 1=1 <if test="uid !=null and uid !=''"> and ui.uid= #{uid} </if> </select> <resultMap type="com.gt.entity.SysRole" id="userrole"> <id column="id" property="id"/> <result column="role" property="role"/> <result column="available" property="available"/> <result column="description" property="description"/> <collection property="permissions" ofType="com.gt.entity.SysPermission"> <id property="id" column="spId" /> <result property="name" column="roleName" /> <result property="resourceType" column="resourceType" /> <result property="url" column="url" /> <result property="permission" column="permission" /> <result property="parentId" column="parentId" /> <result property="parentIds" column="parentIds" /> <result property="available" column="available" /> <collection property="roles" ofType="com.gt.entity.SysRole"> <id property="id" column="srId" /> <result property="role" column="role" /> <result property="description" column="description" /> <result property="available" column="available" /> </collection> </collection> </resultMap> </mapper>
基本工作准备好之后,剩下的才是重点,shiro的认证最终是交给了Realm进行执行了,所以我们需要自己重新实现一个Realm,此Realm继承AuthorizingRealm。
MyShiroRealm.java
package com.gt.config; import java.util.ArrayList; import java.util.List; 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.authc.UsernamePasswordToken; 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.gt.entity.SysPermission; import com.gt.entity.SysRole; import com.gt.entity.UserInfo; import com.gt.mapper.UserInfoMapper; /** * 身份校验核心类; * @author gt * @version v.0.1 */ public class MyShiroRealm extends AuthorizingRealm{ @Resource private UserInfoMapper userInfoMapper; /** * 认证信息.(身份验证) * : * Authentication 是用来验证用户身份 * @param token * @return * @throws AuthenticationException */ @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 = userInfoMapper.findByUsername(username); System.out.println("----->>userInfo="+userInfo); if(userInfo == null){ return null; } /* * 获取权限信息:这里没有进行实现, * 请自行根据UserInfo,Role,Permission进行实现; * 获取之后可以在前端for循环显示所有链接; */ //userInfo.setPermissions(userService.findPermissions(user)); //账号判断; //加密方式; //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( userInfo, //用户名 userInfo.getPassword(), //密码 ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt getName() //realm name ); //明文: 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验 // SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( // userInfo, //用户名 // userInfo.getPassword(), //密码 // getName() //realm name // ); return authenticationInfo; } /** * 此方法调用 hasRole,hasPermission的时候才会进行回调. * * 权限信息.(授权): * 1、如果用户正常退出,缓存自动清空; * 2、如果用户非正常退出,缓存自动清空; * 3、如果我们修改了用户的权限,而用户不退出系统,修改的权限无法立即生效。 * (需要手动编程进行实现;放在service进行调用) * 在权限修改后调用realm中的方法,realm已经由spring管理,所以从spring中获取realm实例, * 调用clearCached方法; * :Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。 * @param principals * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { /* * 当没有使用缓存的时候,不断刷新页面的话,这个代码会不断执行, * 当其实没有必要每次都重新设置权限信息,所以我们需要放到缓存中进行管理; * 当放到缓存中时,这样的话,doGetAuthorizationInfo就只会执行一次了, * 缓存过期之后会再次执行。 */ System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()"); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); UserInfo userInfo = (UserInfo)principals.getPrimaryPrincipal(); List<SysRole> roleList=new ArrayList<>(); roleList= userInfoMapper.findRoleListByUsername(userInfo.getUid()); userInfo.setRoleList(roleList); //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 // UserInfo userInfo = userInfoService.findByUsername(username) //权限单个添加; // 或者按下面这样添加 //添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色 // authorizationInfo.addRole("admin"); //添加权限 // authorizationInfo.addStringPermission("userInfo:query"); ///在认证成功之后返回. //设置角色信息. //支持 Set集合, //用户的角色对应的所有权限,如果只使用角色定义访问权限,下面的四行可以不要 // List<Role> roleList=user.getRoleList(); // for (Role role : roleList) { // info.addStringPermissions(role.getPermissionsName()); // } for(SysRole role:userInfo.getRoleList()){ authorizationInfo.addRole(role.getRole()); for(SysPermission p:role.getPermissions()){ authorizationInfo.addStringPermission(p.getPermission()); } } //设置权限信息. // authorizationInfo.setStringPermissions(getStringPermissions(userInfo.getRoleList())); return authorizationInfo; } }
com.gt.config.ShiroConfiguration.java
package com.gt.config; import java.util.LinkedHashMap; import java.util.Map; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.mgt.SecurityManager; 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.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.sun.org.apache.xerces.internal.impl.dv.util.Base64; /** * Shiro 配置 * http://412887952-qq-com.iteye.com/blog/2299777 Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。 既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。 * * @author gt * @version v.0.1 */ @Configuration public class ShiroConfiguration { /** * shiro缓存管理器; * 需要注入对应的其它的实体类中: * 1、安全管理器:securityManager * 可见securityManager是整个shiro的核心; * @return */ @Bean public EhCacheManager ehCacheManager(){ System.out.println("ShiroConfiguration.getEhCacheManager()"); EhCacheManager cacheManager = new EhCacheManager(); cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml"); return cacheManager; } @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //设置realm. securityManager.setRealm(myShiroRealm()); //注入缓存 securityManager.setCacheManager(ehCacheManager()); return securityManager; } /* @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); securityManager.setCacheManager(ehCacheManager()); return securityManager; }*/ /** * ShiroFilterFactoryBean 处理拦截资源文件问题。 * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在 * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager * Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过 3、部分过滤器可指定参数,如perms,roles * */ @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){ System.out.println("ShiroConfiguration.shirFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); //拦截器. Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>(); //配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了 filterChainDefinitionMap.put("/logout", "logout"); filterChainDefinitionMap.put("/system/login", "anon"); //<!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了; //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put("/**", "authc"); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/system/toLogin"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/system/index"); //未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl("/system/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * 开启shiro aop注解支持. * 使用代理方式;所以需要开启代码支持; * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * 身份认证realm; * (这个需要自己写,账号密码校验;权限等) * @return */ @Bean public MyShiroRealm myShiroRealm(){ MyShiroRealm myShiroRealm = new MyShiroRealm(); return myShiroRealm; } }
SystemController.java
/** * */ package com.gt.controller; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; 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.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import com.gt.config.MyShiroRealm; import com.gt.entity.Girl; import com.gt.entity.UserInfo; import com.gt.mapper.GirlMapper; import com.gt.util.MD5Utils; import net.sf.json.JSONObject; /** * @author Administrator * */ @RequestMapping("system") @Controller public class SystemController { @RequestMapping(value="/toLogin") public String toLogin(HttpServletRequest request ) { return "login"; } @RequestMapping(value="/index") public String index(HttpServletRequest request ) { return "index"; } @RequestMapping(value="/403") public String er403(HttpServletRequest request ) { return "403"; } // 登录提交地址和applicationontext-shiro.xml配置的loginurl一致。 (配置文件方式的说法) @RequestMapping(value="/login",method=RequestMethod.POST) public String login(HttpServletRequest request, Map<String, Object> map , @RequestBody String json,HttpSession session) throws Exception { System.out.println("HomeController.login()"); // 登录失败从request中获取shiro处理的异常信息。 // shiroLoginFailure:就是shiro异常类的全类名. JSONObject jsparam = JSONObject.fromObject(json); String username=jsparam.optString("username"); String password=MD5Utils.string2MD5(jsparam.optString("password")); String rememberMe=jsparam.optString("rememberMe"); UsernamePasswordToken token = new UsernamePasswordToken(username, password,rememberMe); Subject subject = SecurityUtils.getSubject(); try { subject.login(token); //完成登录 UserInfo user=(UserInfo) subject.getPrincipal(); session.setAttribute("user", user); return "index"; } catch (UnknownAccountException ex) { request.setAttribute("msg", "用户不存在或者密码错误!"); return "login"; } catch (IncorrectCredentialsException ex) { request.setAttribute("msg", "用户不存在或者密码错误!"); return "login"; } catch (AuthenticationException ex) { request.setAttribute("msg",ex.getMessage()); return "login"; } catch (Exception ex) { ex.printStackTrace(); request.setAttribute("msg", "内部错误,请重试!"); return "login"; } // 此方法不处理登录成功,由shiro进行处理. } }
index.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <%@page import="java.util.List"%> <%@page import="java.util.ArrayList"%> <%@page import="java.util.HashMap"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> <%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Insert title here</title> </head> <body> <h3>index2</h3> <ul> <shiro:hasPermission name="userInfo:add" ><li>增加</li></shiro:hasPermission> <shiro:hasPermission name="userInfo:del"><li>删除</li></shiro:hasPermission> <shiro:hasPermission name="update"><li>修改</li></shiro:hasPermission> <shiro:hasPermission name="userInfo:view"><li>查询</li></shiro:hasPermission> </ul> </body> </html>
这时候我们启动应用程序,利用postman访问http://localhost:8081/commonService/system/login
参数:{"rememberMe":"false","username":"admin","password":"123456"}
成功登录,且权限正常