springboot shiro权限管理「建议收藏」

2022-08-14 10:32:32 浏览数 (1)

大家好,我是架构君,一个会写代码吟诗的架构师。今天说一说springboot shiro权限管理「建议收藏」,希望能够帮助大家进步!!!

集成shiro大概分这么一个步骤:

(一) pom.xml中添加Shiro依赖;

(二) 注入Shiro Factory和SecurityManager。

(三) 身份认证

(四) 权限控制

一: pom.xml中添加Shiro依赖

1.1: 要使用Shiro进行权限控制,那么很明显的就需要添加对Shiro的依赖包,在pom.xml中加入如下配置:

代码语言:javascript复制
 <!-- shiro权限控制框架 -->
	 <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

只听到从架构师办公室传来架构君的声音:

陆机雄才岂自保?李斯税驾苦不早。有谁来对上联或下联?

代码语言:javascript复制
此代码由Java架构师必看网-架构君整理
pom.xml

pom.xml完整配置

代码语言:javascript复制
<?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部分文件配置

代码语言:javascript复制
此代码由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 开发配置文件

代码语言:javascript复制
server: 
  port: 8081
  context-path: /commonService

1.4:ehcache-shiro.xml 缓存配置文件

代码语言:javascript复制
<?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。

代码语言:javascript复制
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的依赖:

代码语言:javascript复制
 <!-- 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

代码语言:javascript复制
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

代码语言:javascript复制
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

代码语言:javascript复制
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

这时候运行程序,就会自动建表,然后我们添加一些数据:

代码语言:javascript复制
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

代码语言:javascript复制
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

代码语言:javascript复制
<?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

代码语言:javascript复制
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

代码语言:javascript复制
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;  
  }  

 
}  
代码语言:javascript复制
SystemController.java
代码语言:javascript复制
代码语言:javascript复制
/**
 * 
 */
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

代码语言:javascript复制
<%@ 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"}

成功登录,且权限正常

0 人点赞