大家好,我是架构君,一个会写代码吟诗的架构师。今天说一说springboot shiro权限管理「建议收藏」,希望能够帮助大家进步!!!
集成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>
只听到从架构师办公室传来架构君的声音:
陆机雄才岂自保?李斯税驾苦不早。有谁来对上联或下联?
此代码由Java架构师必看网-架构君整理
pom.xml
pom.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部分文件配置
此代码由Java架构师必看网-架构君整理
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"}
成功登录,且权限正常