Shrio安全框架

一、权限管理

1.1 什么是权限管理?

不同身份的用户进入到系统所能够完成的操作是不相同的,我们对不同用户进行的可执行的操作的管理称之为权限管理。

在这里插入图片描述
1.2 如何实现权限管理?

权限管理设计

  • 基于主页的权限管理(不同用户使用不同的主页,权限通过主页功能菜单进行限制)

    • 适用于权限管理比较单一、用户少、每类用户权限固定
  • 基于用户和权限的权限管理

    • 可以实现权限的动态分配,但是不够灵活
  • 基于角色的访问控制

    • RBAC 基于角色的访问控制

二、安全框架简介

2.1 认证授权流程
  • 认证:对用户的身份进行检查(登录验证)

  • 授权:对用户的权限进行检查(是否有对应的操作权限)

  • 流程示意图:

在这里插入图片描述

2.2 安全框架
  • 帮助我们完成用户身份认证及权限检查功能框架
  • 常用的安全框架:
    • Shiro:Apache Shiro是一个功能强大并且易用的Java安全框架 (小而简单)
    • Spring Security:基于Spring的一个安全框架,依赖Spring
    • OAuth2:第三方授权登录
    • 自定义安全认证中心
2.3 Shiro
  • Apache Shiro是一个功能强大并且易用的Java安全框架
  • 可以完成用户认证、授权、密码及会话管理
  • 可以在任何的应用系统中使用(主要针对单体项目的权限管理)

三、Shiro的工作原理

3.1 Shiro的核心功能

在这里插入图片描述

  • Anthentication 认证,验证用户是否有相应的身份—登录认证;

  • Authorization 授权,即权限验证;对已经通过认证的用户检查是否具有某个权限或者角色,从而控制是否能够进行某种操作;

  • Session Managment 会话管理,用户在认证成功之后创建会话,在没有退出之前,当前用户的所有信息都会保存在这个会话中;可以是普通的JavaSE应用,也可以是web应用;

  • Cryptography 加密,对敏感信息进行加密处理,shiro就提供这种加密机制;

  • 支持的特性:

    • Web Support — Shiro提供了过滤器,可以通过过滤器拦截web请求来处理web应用的访问控制
    • Caching 缓存支持,shiro可以缓存用户信息以及用户的角色权限信息,可以提高执行效率
    • Concurrency, shiro支持多线程应用
    • Testing 提供测试支持
    • Run As 允许一个用户以另一种身份去访问
    • Remeber Me
  • 说明:Shiro是一个安全框架,不提供用户、权限的维护(用户的权限管理需要我们自己去设计)

3.2 Shiro核心组件
  • Shiro三大核心组件:SubjectSecurity ManagerRealms
    • Subject,表示待认证和授权的用户
    • Security Manager,它是Shiro框架的核心,Shiro就是通过Security Manager来进行内部实例的管理,并通过它来提供安全管理的各种服务。
      • Authenticator,认证器
      • Anthorizer,授权器
      • SessionManager,会话管理器
      • CacheManager,缓存管理器
    • Realm,相当于Shiro进行认证和授权的数据源,充当了Shiro与安全数据之间的“桥梁”或者“连接器”。也就是说,当对用户进行认证(登录)和授权(访问控制)验证时,Shiro会用应用配置的Realm中查找用户及其权限信息。
在这里插入图片描述

四、基于JavaSE应用—Shiro的基本使用

4.1 创建Maven项目
4.2 导入Shiro依赖库
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.1</version>
</dependency>
4.3 创建Shiro配置文件
  • 在resource目录下创建名为shiro.ini的文件
  • 在文件中完成用户、角色及权限的配置
[users]
zhangsan=123456,seller
lisi=666666,ckmgr
admin=222222,admin

[roles]
admin=*
seller=order-add,order-del,order-list
ckmgr=ck-add,ck-del,ck-list
4.4 Shiro的基本使用
package com.qfedu.shiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;

import java.util.Scanner;

public class TestShiro {

    public static void main(String[] args) {

        Scanner scan = new Scanner(System.in);
        System.out.println("请输入帐号:");
        String username = scan.nextLine();
        System.out.println("请输入密码:");
        String password = scan.nextLine();

        //1.创建安全管理器
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        //2.创建realm
        IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
        //3.将realm设置给安全管理器
        securityManager.setRealm(iniRealm);
        //4.将Realm设置给SecurityUtils工具
        SecurityUtils.setSecurityManager(securityManager);
        //5.通过SecurityUtils工具类获取subject对象
        Subject subject = SecurityUtils.getSubject();

        //【认证流程】
        //a.将认证帐号和密码封装到token对象中
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        //b.通过subject对象调用login方法进行认证申请:
        boolean b = false;
        try{
            subject.login(token);
            b = true;
        }catch(IncorrectCredentialsException e){
            b = false;
        }
        System.out.println(b?"登录成功":"登录失败");


        //【授权】
        //判断是否有某个角色
        System.out.println(subject.hasRole("seller"));

        //判断是否有某个权限
        boolean permitted = subject.isPermitted("order-del");
        System.out.println(permitted);

    }


}

五、整合Shrio

5.1 SpringMVC整合Shrio
  • 1.导入依赖

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro‐core</artifactId>
        <version>1.4.1</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro‐web</artifactId>
        <version>1.4.1</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro‐spring</artifactId>
        <version>1.4.1</version>
    </dependency>
    
  • 2.在web.xml中添加shrio对应的过滤器配置

    • 拦截所有请求

      <?xml version="1.0" encoding="UTF‐8"?>
      <web‐app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
      		 xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance"
      		 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                            http://xmlns.jcp.org/xml/ns/javaee/web‐app_3_1.xsd"
      		 version="3.1">
      	<context‐param>
              <param‐name>contextConfigLocation</param‐name>
              <param‐value>classpath:spring‐*.xml</param‐value>
      	</context‐param>
          <listener>
          	<listener‐class>org.springframework.web.context.ContextLoaderListener</listener‐class>
          </listener>
          
          <servlet>
              <servlet‐name>springMVC</servlet‐name>
              
              <servlet‐class>org.springframework.web.servlet.DispatcherServlet</servlet‐class>
          <init‐param>
              <param‐name>contextConfigLocation</param‐name>
              <param‐value>classpath:spring‐context*.xml</param‐value>
          </init‐param>
      	</servlet>
          
          <servlet‐mapping>
              <servlet‐name>springMVC</servlet‐name>
              <url‐pattern>*.do</url‐pattern>
          </servlet‐mapping>
          <filter>
              <filter‐name>shiroFilter</filter‐name>
              <filter‐class>org.springframework.web.filter.DelegatingFilterProxy</filter‐class>
              <init‐param>
                  <param‐name>targetFilterLifecycle</param‐name>
                  <param‐value>true</param‐value>
              </init‐param>
          </filter>
          
          <filter‐mapping>
              <filter‐name>shiroFilter</filter‐name>
              <url‐pattern>/*</url‐pattern>
          </filter‐mapping>
      </web‐app>
      
  • 3.在spring配置文件中,配置shrio框架所需的内容

    • 创建spring-context-shiro.xml
    • 完成shiro配置
    <?xml version="1.0" encoding="UTF‐8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    		xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance"
    		xsi:schemaLocation="http://www.springframework.org/schema/beans
    		http://www.springframework.org/schema/beans/spring‐beans.xsd">
        
        <!‐‐shiro过滤器‐‐>
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"></property>
        
        <!‐‐配置登录页面地址,非必须,默认寻找web项目根路径下的/login.jsp‐‐>
        <property name="loginUrl" value="/login.jsp"></property>
            
        <!‐‐配置登录成功之后跳转的页面路径,此配置一般不使用,一般在LoginController中处理
        逻辑
        <property name="successUrl" value="/index.jsp"></property>
        ‐‐>
        <property name="unauthorizedUrl" value="/"></property>
        <property name="filterChainDefinitions">
            <value>/**=anon</value>
        </property>
        </bean>
        <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean>
        <!‐‐证书匹配器‐‐>
        <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.Md5CredentialsMatcher"></bean>
       
        <!‐‐使用自带的Realm‐‐>
        <bean id="jdbcRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
            <property name="credentialsMatcher" ref="credentialsMatcher"></property>
            <property name="permissionsLookupEnabled" value="true"></property>
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!‐‐缓存管理‐‐>
        <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"></bean>
        <!‐‐shiro安全管理器‐‐>
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="realm" ref="jdbcRealm"></property>
            <property name="cacheManager" ref="cacheManager"></property>
        </bean>
    </beans>
    
  • 4.配置权限验证使用注解

    <!‐‐配置权限验证使用注解‐‐>
    <aop:config proxy‐target‐class="true"></aop:config>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    	<property name="securityManager" ref="securityManager"></property>
    </bean>
    
5.2 SSM整合Shiro进行认证及授权
  • 1.创建用户权限表,并添加数据

    • 因为此案例目前使用的是shiro默认数据库结构,则需要按照shiro默认的数据表结构进行创建及配置

    • 用户信息

      create table users(
      id int primary key auto_increment,
      username varchar( 60 ) not null, 
      password varchar( 60 ) not null,
        password_salt varchar( 100 )
      );
      
    • 添加用户信息

    insert into users(username,password)
    values('zhangsan','e10adc3949ba59abbe56e057f20f883e');
    insert into users(username,password)
    values('lisi','e10adc3949ba59abbe56e057f20f883e');
    insert into users(username,password)
    values('wangwu','e10adc3949ba59abbe56e057f20f883e');
    insert into users(username,password)
    values('zhaoliu','e10adc3949ba59abbe56e057f20f883e');
    insert into users(username,password)
    values('chenqi','e10adc3949ba59abbe56e057f20f883e');
    
    • 角色信息
    create table user_roles(
    id int primary key auto_increment,
    username varchar( 60 ) not null,
    role_name varchar( 100 ) not null
    );
    
    ‐‐ 添加用户角色
    insert into user_roles(username,role_name) values('zhangsan','admin');
    insert into user_roles(username,role_name) values('lisi','user');
    insert into user_roles(username,role_name) values('wangwu','cmanager');
    insert into user_roles(username,role_name) values('zhaoliu','mmanager');
    insert into user_roles(username,role_name) values('chenqi','xmanager');
    
    • 权限信息
    create table roles_permissions(
    id int primary key auto_increment,
    role_name varchar( 100 ) not null,
    permission varchar( 100 ) not null
    );
    
    ‐‐ 添加权限信息
    insert into roles_permissions(role_name,permission) values('admin','*');
    insert into roles_permissions(role_name,permission) values('user','sys:*:find');
    insert into roles_permissions(role_name,permission) values('cmanager','sys:c:save');
    insert into roles_permissions(role_name,permission) values('cmanager','sys:c:delete');
    insert into roles_permissions(role_name,permission) values('cmanager','sys:c:update');
    insert into roles_permissions(role_name,permission) values('cmanager','sys:c:find');
    insert into roles_permissions(role_name,permission) values('mmanager','sys:m:save');
    insert into roles_permissions(role_name,permission) values('mmanager','sys:m:delete');
    insert into roles_permissions(role_name,permission) values('mmanager','sys:m:update');
    insert into roles_permissions(role_name,permission) values('mmanager','sys:m:find');
    insert into roles_permissions(role_name,permission) values('xmanager','sys:x:save');
    insert into roles_permissions(role_name,permission) values('xmanager','sys:x:delete');
    insert into roles_permissions(role_name,permission) values('xmanager','sys:x:update');
    insert into roles_permissions(role_name,permission) values('xmanager','sys:x:find');
    

  • 2.Service层
    • UserService
@Service
public class UserService {
public void login(String userName,String userPwd) throws  Exception{
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd);
        subject.login(token);
    }
}
  • 3.Controller层
    • UserController
@Controller
public class UserController {
    @Autowired
    private UserService userService;
    
    @RequestMapping("/login.do")
	public String  login(String userName, String userPwd, Model model){
		try{
            userService.login(userName,userPwd);
            System.out.println("‐‐‐‐success");
			return "index.jsp";
        }catch (Exception e){
            System.out.println("‐‐‐‐fail");
            model.addAttribute("tips","登录验证失败,请重试!");
			return "login.jsp";
		}
   }
}
  • 4.View层
    • login.jsp
<%@ page contentType="text/html;charset=UTF‐8" language="java" %>
<html>
    <head>
    <title>Title</title>
    </head>
    <body>
        ${tips}
        <form action="login.do" method="post">
            <input type="text" name="userName"/><br/>
            <input type="password" name="userPwd"/><br/>
            <button type="submit">提交</button>
        </form>
    </body>
</html>

​ - index.jsp

<%@ page contentType="text/html;charset=UTF‐8" language="java" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
    <head>
    <title>Title</title>
    </head>
    <body>
        <shiro:principal/>,欢迎您!
        [用户角色:<shiro:hasRole name="admin">admin</shiro:hasRole>
        <shiro:hasRole name="user">user</shiro:hasRole>
        <shiro:hasRole name="cmanager">cmanager</shiro:hasRole>
        <shiro:hasRole name="mmanager">mmanager</shiro:hasRole>
        <shiro:hasRole name="xmanager">xmanager</shiro:hasRole>]
        <hr/>
                C模块
        <ul>
        <shiro:hasPermission name="sys:c:save"><li>添加C</li>
        </shiro:hasPermission>
        <shiro:hasPermission name="sys:c:delete"><li>删除C</li>
        </shiro:hasPermission>
        <shiro:hasPermission name="sys:c:update"><li>修改C</li>
        </shiro:hasPermission>
        <shiro:hasPermission name="sys:c:find"><li>查询C</li>
        </shiro:hasPermission>
        </ul>
                X模块
        <ul>
        <shiro:hasPermission name="sys:x:save"><li>添加X</li>
        </shiro:hasPermission>
        <shiro:hasPermission name="sys:x:delete"><li>删除X</li>
        </shiro:hasPermission>
        <shiro:hasPermission name="sys:x:update"><li>修改X</li>
        </shiro:hasPermission>
        <shiro:hasPermission name="sys:x:find"><li>查询X</li>
        </shiro:hasPermission>
        </ul>
                M模块
        <ul>
        <shiro:hasPermission name="sys:m:save"><li>添加M</li>
        </shiro:hasPermission>
        <shiro:hasPermission name="sys:m:delete"><li>删除M</li>
        </shiro:hasPermission>
        <shiro:hasPermission name="sys:m:update"><li>修改M</li>
        </shiro:hasPermission>
        <shiro:hasPermission name="sys:m:find"><li>查询M</li>
        </shiro:hasPermission>
        </ul>
    </body>
</html>
5.3 SpringBoot应用整合Shiro
  • JavaSE应用中使用
  • web应用中使用
    • SSM整合Shiro(配置多,用的少)
    • SpringBoot应用整合Shiro
1. 创建SpringBoot应用
  • lombok
  • spring web
  • thymeleaf
2. 整合Druid和MyBatis
  • 依赖

    <!-- druid starter -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>
     <!--mysql -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!-- mybatis -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.0</version>
    </dependency>
    
  • 配置

    spring:
      datasource:
        druid:
          url: jdbc:mysql://47.96.11.185:3306/test
          # MySQL如果是8.x   com.mysql.cj.jdbc.Driver
          driver-class-name: com.mysql.jdbc.Driver
          username: root
          password: admin123
          initial-size: 1
          min-idle: 1
          max-active: 20
    mybatis:
      mapper-locations: classpath:mappers/*Mapper.xml
      type-aliases-package: com.qfedu.springbootssm.beans
    
3. 整合Shiro
  • 导入依赖

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.1</version>
    </dependency>
    
  • Shiro配置(java配置方式)

    • SpringBoot默认没有提供对Shiro的自动配置
    @Configuration
    public class ShiroConfig {
    
        @Bean
        public IniRealm getIniRealm(){
            IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
            return iniRealm;
        }
    
        @Bean
        public DefaultWebSecurityManager getDefaultWebSecurityManager(IniRealm iniRealm){
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            //securityManager要完成校验,需要realm
            securityManager.setRealm(iniRealm);
            return securityManager;
        }
    
        @Bean
        public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager){
            ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
            //过滤器就是shiro就行权限校验的核心,进行认证和授权是需要SecurityManager的
            filter.setSecurityManager(securityManager);
    
            //设置shiro的拦截规则
            // anon   匿名用户可访问
            // authc  认证用户可访问
            // user   使用RemeberMe的用户可访问
            // perms  对应权限可访问
            // role   对应的角色可访问
            Map<String,String> filterMap = new HashMap<>();
            filterMap.put("/","anon");
            filterMap.put("/login.html","anon");
            filterMap.put("/regist.html","anon");
            filterMap.put("/user/login","anon");
            filterMap.put("/user/regist","anon");
            filterMap.put("/static/**","anon");
            filterMap.put("/**","authc");
    
            filter.setFilterChainDefinitionMap(filterMap);
            filter.setLoginUrl("/login.html");
            //设置未授权访问的页面路径
            filter.setUnauthorizedUrl("/login.html");
            return filter;
        }
    
    
    }
    
  • 认证测试

    • UserServiceImpl.java
    @Service
    public class UserServiceImpl {
    
        public void checkLogin(String userName,String userPwd) throws Exception{
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd);
            subject.login(token);
        }
    
    }
    
    • UserController.java
    @Controller
    @RequestMapping("user")
    public class UserController {
    
        @Resource
        private UserServiceImpl userService;
    
        @RequestMapping("login")
        public String login(String userName,String userPwd){
            try {
                userService.checkLogin(userName,userPwd);
                System.out.println("------登录成功!");
                return "index";
            } catch (Exception e) {
                System.out.println("------登录失败!");
                return "login";
            }
    
        }
    }
    
  • login.html

  <!DOCTYPE html>
  <html lang="en">
  <head>
      <meta charset="UTF-8">
      <title>Title</title>
  </head>
  <body>
      login
      <hr/>
      <form action="user/login">
          <p>帐号:<input type="text" name="userName"/></p>
          <p>密码:<input type="text" name="userPwd"/></p>
          <p><input type="submit" value="登录"/></p>
      </form>
  </body>
  </html>

六、SpringBoot应用整合Shiro—案例(JdbcRealm)

6.1 JdbcRealm介绍
在这里插入图片描述
  • 如果使用JdbcRealm,则必须提供JdbcRealm所需的表结构(权限设计)
在这里插入图片描述
6.2 JdbcRealm规定的表结构
  • 用户信息表: users

    create table users(
        id int primary key auto_increment,
        username varchar(60) not null unique,
        password varchar(20) not null,
        password_salt varchar(20)
    );
    
    insert into users(username,password) values('zhangsan','123456');
    insert into users(username,password) values('lisi','123456');
    insert into users(username,password) values('wangwu','123456');
    insert into users(username,password) values('zhaoliu','123456');
    insert into users(username,password) values('chenqi','123456');
    
  • 角色信息表: user_roles

    create table user_roles(
    	id int primary key auto_increment,
        username varchar(60) not null,
        role_name varchar(100) not null
    );
    
    -- admin系统管理员
    -- cmanager 库管人员
    -- xmanager 销售人员
    -- kmanager 客服人员
    -- zmanager 行政人员 
    insert into user_roles(username,role_name) values('zhangsan','admin');
    insert into user_roles(username,role_name) values('lisi','cmanager');
    insert into user_roles(username,role_name) values('wangwu','xmanager');
    insert into user_roles(username,role_name) values('zhaoliu','kmanager');
    insert into user_roles(username,role_name) values('chenqi','zmanager');
    
    
  • 权限信息表:roles_permissions

    create table roles_permissions(
    	id int primary key auto_increment,
        role_name varchar(100) not null,
        permission varchar(100) not null
    );
    
    -- 权限  sys:c:save   sys:c:delete...
    -- 管理员具备所有权限
    insert into roles_permissions(role_name,permission) values("admin","*");
    -- 库管人员
    insert into roles_permissions(role_name,permission) values("cmanager","sys:c:save");		
    insert into roles_permissions(role_name,permission) values("cmanager","sys:c:delete");
    insert into roles_permissions(role_name,permission) values("cmanager","sys:c:update");
    insert into roles_permissions(role_name,permission) values("cmanager","sys:c:find");
    -- 销售人员
    insert into roles_permissions(role_name,permission) values("xmanager","sys:c:find");
    insert into roles_permissions(role_name,permission) values("xmanager","sys:x:save");
    insert into roles_permissions(role_name,permission) values("xmanager","sys:x:delete");
    insert into roles_permissions(role_name,permission) values("xmanager","sys:x:update");
    insert into roles_permissions(role_name,permission) values("xmanager","sys:x:find");
    
    insert into roles_permissions(role_name,permission) values("xmanager","sys:k:save");
    insert into roles_permissions(role_name,permission) values("xmanager","sys:k:delete");
    insert into roles_permissions(role_name,permission) values("xmanager","sys:k:update");
    insert into roles_permissions(role_name,permission) values("xmanager","sys:k:find");
    -- 客服人员
    insert into roles_permissions(role_name,permission) values("kmanager","sys:k:find");
    insert into roles_permissions(role_name,permission) values("kmanager","sys:k:update");
    -- 新增人员
    insert into roles_permissions(role_name,permission) values("zmanager","sys:*:find");
    
6.3 SpringBoot整合Shiro
  • 创建SpringBoot应用

  • 整合Druid和MyBatis

  • 整合shiro

    • 添加依赖

      <dependency>
          <groupId>org.apache.shiro</groupId>
          <artifactId>shiro-spring</artifactId>
          <version>1.4.1</version>
      </dependency>
      
    • 配置Shiro

      @Configuration
      public class ShiroConfig {
      
          @Bean
          public JdbcRealm getJdbcRealm(DataSource dataSource){
              JdbcRealm jdbcRealm = new JdbcRealm();
              //JdbcRealm会自行从数据库查询用户及权限数据(数据库的表结构要符合JdbcRealm的规范)
              jdbcRealm.setDataSource(dataSource);
              //JdbcRealm默认开启认证功能,需要手动开启授权功能
              jdbcRealm.setPermissionsLookupEnabled(true);
              return  jdbcRealm;
          }
      
          @Bean
          public DefaultWebSecurityManager getDefaultWebSecurityManager(JdbcRealm jdbcRealm){
              DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
              securityManager.setRealm(jdbcRealm);
              return securityManager;
          }
      
          @Bean
          public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager){
              ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
              //过滤器就是shiro就行权限校验的核心,进行认证和授权是需要SecurityManager的
              filter.setSecurityManager(securityManager);
      
              Map<String,String> filterMap = new HashMap<>();
              filterMap.put("/","anon");
              filterMap.put("/login.html","anon");
              filterMap.put("/regist.html","anon");
              filterMap.put("/user/login","anon");
              filterMap.put("/user/regist","anon");
              filterMap.put("/static/**","anon");
              filterMap.put("/**","authc");
      
              filter.setFilterChainDefinitionMap(filterMap);
              filter.setLoginUrl("/login.html");
              //设置未授权访问的页面路径
              filter.setUnauthorizedUrl("/login.html");
              return filter;
          }
      
      }
      
    3.4 认证功能测试

6.4 Shiro的标签使用

当用户认证进入到主页面之后,需要显示用户信息及当前用户的权限信息;Shiro就提供了一套标签用于在页面来进行权限数据的呈现

  • Shiro提供了可供JSP使用的标签以及Thymeleaf中标签

    • JSP页面中引用:

      <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
      
    • Thymeleaf模版中引用:

      • 在pom.xml文件中导入thymeleaf模版对shiro标签支持的依赖
      <dependency>
          <groupId>com.github.theborakompanioni</groupId>
          <artifactId>thymeleaf-extras-shiro</artifactId>
          <version>2.0.0</version>
      </dependency>
      
      • 在ShiroConfig中配置Shiro的
      @Configuration
      public class ShiroConfig {
      
          @Bean
          public ShiroDialect getShiroDialect(){
              return new ShiroDialect();
          }
          //...
      }
      
      • Thymeleaf模版中引入shiro的命名空间
      <html xmlns:th="http://www.thymeleaf.org"
            xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
          ...
      </html>
      

常用标签

  • guest,判断用户是否是游客身份,如果是游客身份则显示此标签内容

    <shiro:guest>
        欢迎游客访问,<a href="login.html">登录</a>
    </shiro:guest>
    
  • user,判断用户是否是认证身份,如果是认证身份则显示此标签内容

  • principal,获取当前登录用户名

    <shiro:user>
        用户[<shiro:principal/>]欢迎您!
    </shiro:user>
    
  • notAuthenticated/authenticated

  • hasRole

  • hasPermission

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org"
          xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        index
        <hr/>
        <shiro:guest>
            欢迎游客访问,<a href="login.html">登录</a>
        </shiro:guest>
        <shiro:user>
            用户[<shiro:principal/>]欢迎您!
            当前用户为<shiro:hasRole name="admin">超级管理员</shiro:hasRole>
                    <shiro:hasRole name="cmanager">仓管人员</shiro:hasRole>
                    <shiro:hasRole name="xmanager">销售人员</shiro:hasRole>
                    <shiro:hasRole name="kmanager">客服人员</shiro:hasRole>
                    <shiro:hasRole name="zmanager">行政人员</shiro:hasRole>
        </shiro:user>
    
        <hr/>
        仓库管理
        <ul>
            <shiro:hasPermission name="sys:c:save"><li><a href="#">入库</a></li></shiro:hasPermission>
            <shiro:hasPermission name="sys:c:delete"><li><a href="#">出库</a></li></shiro:hasPermission>
            <shiro:hasPermission name="sys:c:update"><li><a href="#">修改</a></li></shiro:hasPermission>
            <shiro:hasPermission name="sys:c:find"><li><a href="#">查询</a></li></shiro:hasPermission>
        </ul>
    
        订单管理
        <ul>
            <shiro:hasPermission name="sys:x:save"><li><a href="#">添加订单</a></li></shiro:hasPermission>
            <shiro:hasPermission name="sys:x:delete"><li><a href="#">删除订单</a></li></shiro:hasPermission>
            <shiro:hasPermission name="sys:x:update"><li><a href="#">修改订单</a></li></shiro:hasPermission>
            <shiro:hasPermission name="sys:x:find"><li><a href="#">查询订单</a></li></shiro:hasPermission>
        </ul>
    
        客户管理
        <ul>
            <shiro:hasPermission name="sys:k:save"><li><a href="#">添加客户</a></li></shiro:hasPermission>
            <shiro:hasPermission name="sys:k:delete"><li><a href="#">删除客户</a></li></shiro:hasPermission>
            <shiro:hasPermission name="sys:k:update"><li><a href="#">修改客户</a></li></shiro:hasPermission>
            <shiro:hasPermission name="sys:k:find"><li><a href="#">查询客户</a></li></shiro:hasPermission>
        </ul>
    
    
    </body>
    </html>
    

七、SpringBoot整合Shiro完成权限管理案例—自定义Realm

使用JdbcRealm可以完成用户权限管理,但是我们必须提供JdbcRealm规定的数据表结构;如果在我们的项目开发中 ,这个JdbcRealm规定的数据表结构不能满足开发需求,该如何处理呢?

  • 自定义数据库表结构
  • 自定义Realm实现认证和授权
在这里插入图片描述
7.1 数据库设计
  • RBAC基于角色的访问控制

    -- 用户信息表
    create table tb_users(
    	user_id int primary key auto_increment,
    	username varchar(60) not null unique,
    	password varchar(20) not null,
      password_salt varchar(60)
    );
    
    insert into tb_users(username,password) values('zhangsan','123456');
    insert into tb_users(username,password) values('lisi','123456');
    insert into tb_users(username,password) values('wangwu','123456');
    insert into tb_users(username,password) values('zhaoliu','123456');
    insert into tb_users(username,password) values('chenqi','123456');
    
    -- 角色信息表
    create table tb_roles(
    	role_id int primary key auto_increment,
    	role_name varchar(60) not null
    );
    
    insert into tb_roles(role_name) values('admin');
    insert into tb_roles(role_name) values('cmanager');  -- 仓管
    insert into tb_roles(role_name) values('xmanager');  --  销售
    insert into tb_roles(role_name) values('kmanager');  -- 客服
    insert into tb_roles(role_name) values('zmanager');  -- 行政
    
    -- 权限信息表
    create table tb_permissions(
    	permission_id int primary key auto_increment,		-- 1
    	permission_code varchar(60) not null,						-- sys:c:find
    	permission_name varchar(60)											-- 仓库查询
    );
    insert into tb_permissions(permission_code,permission_name) values('sys:c:save','入库');
    insert into tb_permissions(permission_code,permission_name) values('sys:c:delete','出库');
    insert into tb_permissions(permission_code,permission_name) values('sys:c:update','修改');
    insert into tb_permissions(permission_code,permission_name) values('sys:c:find','查询');
    
    insert into tb_permissions(permission_code,permission_name) values('sys:x:save','新增订单');
    insert into tb_permissions(permission_code,permission_name) values('sys:x:delete','删除订单');
    insert into tb_permissions(permission_code,permission_name) values('sys:x:update','修改订单');
    insert into tb_permissions(permission_code,permission_name) values('sys:x:find','查询订单');
    
    
    insert into tb_permissions(permission_code,permission_name) values('sys:k:save','新增客户');
    insert into tb_permissions(permission_code,permission_name) values('sys:k:delete','删除客户');
    insert into tb_permissions(permission_code,permission_name) values('sys:k:update','修改客户');
    insert into tb_permissions(permission_code,permission_name) values('sys:k:find','查询客户');
    
    -- 用户角色表
    create table tb_urs(
    	uid int not null,
    	rid int not null
    	-- primary key(uid,rid),
    	-- constraint FK_user foreign key(uid) references tb_users(user_id),
    	-- constraint FK_role foreign key(rid) references tb_roles(role_id)
    );
    insert into tb_urs(uid,rid) values(1,1);
    insert into tb_urs(uid,rid) values(1,2);
    insert into tb_urs(uid,rid) values(1,3);
    insert into tb_urs(uid,rid) values(1,4);
    insert into tb_urs(uid,rid) values(1,5);
    
    insert into tb_urs(uid,rid) values(2,2);
    insert into tb_urs(uid,rid) values(3,3);
    insert into tb_urs(uid,rid) values(4,4);
    insert into tb_urs(uid,rid) values(5,5);
    
    -- 角色权限表
    create table tb_rps(
    	rid int not null,
    	pid int not null
    );
    -- 给仓管角色分配权限
    insert into tb_rps(rid,pid) values(2,1);
    insert into tb_rps(rid,pid) values(2,2);
    insert into tb_rps(rid,pid) values(2,3);
    insert into tb_rps(rid,pid) values(2,4);
    -- 给销售角色分配权限
    insert into tb_rps(rid,pid) values(3,4);
    insert into tb_rps(rid,pid) values(3,5);
    insert into tb_rps(rid,pid) values(3,6);
    insert into tb_rps(rid,pid) values(3,7);
    insert into tb_rps(rid,pid) values(3,8);
    insert into tb_rps(rid,pid) values(3,9);
    insert into tb_rps(rid,pid) values(3,10);
    insert into tb_rps(rid,pid) values(3,11);
    insert into tb_rps(rid,pid) values(3,12);
    -- 给客服角色分配权限
    insert into tb_rps(rid,pid) values(4,11);
    insert into tb_rps(rid,pid) values(4,12);
    -- 给行政角色分配权限
    insert into tb_rps(rid,pid) values(5,4);
    insert into tb_rps(rid,pid) values(5,8);
    insert into tb_rps(rid,pid) values(5,12);
    
7.2 DAO实现
  • Shiro进行认证需要用户信息:

    • 根据用户名查询用户信息
  • Shiro进行授权管理需要当前用户的角色和权限

    • 根据用户名查询当前用户的角色列表(3张表连接查询)

    • 根据用户名查询当前用户的权限列表(5张表连接查询)

7.2.1 创建SpringBoot项目,整合MyBatis
7.2.2 根据用户名查询用户信息
  • 创建BeanBean
@Data
public class User {
    private Integer userId;
    private String userName;
    private String userPwd;
    private String pwdSalt;
}
  • 创建DAO
public interface UserDAO {
    public User queryUserByUsername(String username) throws  Exception;
}
  • 映射配置
<?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.qfedu.shiro4.dao.UserDAO">

    <resultMap id="userMap" type="User">
        <id column="user_id" property="userId"></id>
        <result column="username" property="userName"/>
        <result column="password" property="userPwd"/>
        <result column="password_salt" property="pwdSalt"/>
    </resultMap>

    <select id="queryUserByUsername" resultMap="userMap">
        select * from tb_users
        where username=#{username}
    </select>

</mapper>
7.2.3 根据用户名查询角色名列表
  • 创建DAO
public interface RoleDAO {
    public Set<String>  queryRoleNamesByUsername(String username) throws Exception;
}
  • 映射配置
<?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.qfedu.shiro4.dao.RoleDAO">

    <select id="queryRoleNamesByUsername" resultSets="java.util.Set" resultType="string">
        select role_name
        from tb_users inner join tb_urs
        on tb_users.user_id = tb_urs.uid
        inner join tb_roles
        on tb_urs.rid = tb_roles.role_id
        where tb_users.username=#{username}
    </select>

</mapper>
7.2.4 根据用户名查询权限列表
  • 创建DAO
public interface PermissionDAO {
    public Set<String> queryPermissionsByUsername(String  username) throws Exception;
}
  • 映射配置
<?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.qfedu.shiro4.dao.PermissionDAO">

    <select id="queryPermissionsByUsername" resultSets="java.util.Set" resultType="string">
        select tb_permissions.permission_code from tb_users
        inner join tb_urs on tb_users.user_id=tb_urs.uid
        inner join tb_roles on tb_urs.rid=tb_roles.role_id
        inner join tb_rps on tb_roles.role_id=tb_rps.rid
        inner join tb_permissions on tb_rps.pid=tb_permissions.permission_id
        where tb_users.username=#{username}
    </select>

</mapper>
7.3 整合Shiro
  • 导入依赖
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.1</version>
</dependency>
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>
  • 配置Shiro-基于Java配置方式
@Configuration
public class ShiroConfig {

    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }

    //自定义Realm
    @Bean
    public MyRealm getMyRealm(){
        MyRealm myRealm = new MyRealm();
        return myRealm;
    }

    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
        //过滤器就是shiro就行权限校验的核心,进行认证和授权是需要SecurityManager的
        filter.setSecurityManager(securityManager);

        Map<String,String> filterMap = new HashMap<>();
        filterMap.put("/","anon");
        filterMap.put("/index.html","anon");
        filterMap.put("/login.html","anon");
        filterMap.put("/regist.html","anon");
        filterMap.put("/user/login","anon");
        filterMap.put("/user/regist","anon");
        filterMap.put("/layui/**","anon");
        filterMap.put("/**","authc");

        filter.setFilterChainDefinitionMap(filterMap);
        filter.setLoginUrl("/login.html");
        //设置未授权访问的页面路径()
        filter.setUnauthorizedUrl("/login.html");
        return filter;
    }

}

  • 自定义Realm
/**
 * 1.创建一个类继承AuthorizingRealm类(实现了Realm接口的类)
 * 2.重写doGetAuthorizationInfo和doGetAuthenticationInfo方法
 * 3.重写getName方法返回当前realm的一个自定义名称
 */
public class MyRealm extends AuthorizingRealm {
    
    @Resource
    private UserDAO userDAO;
    @Resource
    private RoleDAO roleDAO;
    @Resource
    private PermissionDAO permissionDAO;

    public String getName() {
        return "myRealm";
    }
    
    /**
     * 获取授权数据(将当前用户的角色及权限信息查询出来)
     */
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取用户的用户名
        String username = (String) principalCollection.iterator().next();
        //根据用户名查询当前用户的角色列表
        Set<String> roleNames = roleDAO.queryRoleNamesByUsername(username);
        //根据用户名查询当前用户的权限列表
        Set<String> ps = permissionDAO.queryPermissionsByUsername(username);

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(roleNames);
        info.setStringPermissions(ps);
        return info;
    }

    /**
     * 获取认证的安全数据(从数据库查询的用户的正确数据)
     */
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //参数authenticationToken就是传递的  subject.login(token)
        // 从token中获取用户名
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();
        //根据用户名,从数据库查询当前用户的安全数据
        User user = userDAO.queryUserByUsername(username);

        AuthenticationInfo info = new SimpleAuthenticationInfo(
                username,           //当前用户用户名
                user.getUserPwd(),   //从数据库查询出来的安全密码
                getName());

        return info;
    }
}
7.4 SpringBoot应用打包部署
  • SpringBoot项目集成了web容器(Tomcat),所以SpringBoot应用是可以打包成jar直接运行的

八、加密加盐

  • 明文-----(加密规则)-----密文
  • 加密规则可以自定义,在项目开发中我们通常使用BASE64和MD5编码方式
    • BASE64:可反编码的编码方式(对称)
      • 明文----密文
      • 密文----明文
    • MD5: 不可逆的编码方式(非对称)
      • 明文----密文
  • 如果数据库用户的密码存储的密文,Shiro该如何完成验证呢?
  • 使用Shiro提供的加密功能,对输入的密码进行加密之后再进行认证。
8.1 加密介绍
在这里插入图片描述
8.2 Shiro使用加密认证
  • 配置matcher

    @Configuration
    public class ShiroConfig {
    
        //...
        @Bean
        public HashedCredentialsMatcher getHashedCredentialsMatcher(){
            HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
            //matcher就是用来指定加密规则
            //加密方式
            matcher.setHashAlgorithmName("md5");
            //hash次数
            matcher.setHashIterations(1);	//此处的循环次数要与用户注册是密码加密次数一致
            return matcher;
        }
    
        //自定义Realm
        @Bean
        public MyRealm getMyRealm( HashedCredentialsMatcher matcher ){
            MyRealm myRealm = new MyRealm();
            myRealm.setCredentialsMatcher(matcher);
            return myRealm;
        }
    
    	//...
    }
    
    
8.3 用户注册密码加密处理
  • registh.html

    <form action="/user/regist" method="post">
        <p>帐号:<input type="text" name="userName"/></p>
        <p>密码:<input type="text" name="userPwd"/></p>
        <p><input type="submit" value="提交注册"/></p>
    </form>
    
  • UserController

    @Controller
    @RequestMapping("user")
    public class UserController {
    
        @Resource
        private UserServiceImpl userService;
    
    
    
        @RequestMapping("/regist")
        public String regist(String userName,String userPwd) {
            System.out.println("------注册");
    
            //注册的时候要对密码进行加密存储
            Md5Hash md5Hash = new Md5Hash(userPwd);
            System.out.println("--->>>"+ md5Hash.toHex());
    
            //加盐加密
            int num = new Random().nextInt(90000)+10000;   //10000—99999
            String salt = num+"";
            Md5Hash md5Hash2 = new Md5Hash(userPwd,salt);
            System.out.println("--->>>"+md5Hash2);
    
            //加盐加密+多次hash
            Md5Hash md5Hash3 = new Md5Hash(userPwd,salt,3);
            System.out.println("--->>>"+md5Hash3);
    
            //SimpleHash hash = new SimpleHash("md5",userPwd,num,3);
    		
            //将用户信息保存到数据库时,保存加密后的密码,如果生成的随机盐,盐也要保存
            
            return "login";
        }
    
    }
    
8.4 如果密码进行了加盐处理,则Realm在返回认证数据时需要返回盐
  • 在自定义Realm中:

    public class MyRealm extends AuthorizingRealm {
        
        @Resource
        private UserDAO userDAO;
        @Resource
        private RoleDAO roleDAO;
        @Resource
        private PermissionDAO permissionDAO;
    
        public String getName() {
            return "myRealm";
        }
    
        /**
         * 获取认证的安全数据(从数据库查询的用户的正确数据)
         */
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            //参数authenticationToken就是传递的  subject.login(token)
            // 从token中获取用户名
            UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
            String username = token.getUsername();
            //根据用户名,从数据库查询当前用户的安全数据
            User user = userDAO.queryUserByUsername(username);
    
    //        AuthenticationInfo info = new SimpleAuthenticationInfo(
    //                username,           //当前用户用户名
    //                user.getUserPwd(),   //从数据库查询出来的安全密码
    //                getName());
    
            //如果数据库中用户的密码是加了盐的
            AuthenticationInfo info = new SimpleAuthenticationInfo(
                    username,           //当前用户用户名
                    user.getUserPwd(),   //从数据库查询出来的安全密码
                    ByteSource.Util.bytes(user.getPwdSalt()),
                    getName());
    
            return info;
        }
    }
    

九、退出登录

  • 在Shiro过滤器中进行配置,配置logut对应的路径

    filterMap.put("/exit","logout");
    
  • 在页面的“退出”按钮上,跳转到logout对应的url

    <a href="exit">退出</a>
    

十、授权管理

用户登录成功之后,要进行响应的操作就需要有对应的权限;在进行操作之前对权限进行检查—授权

权限控制通常有两类做法:

  • 不同身份的用户登录,我们现在不同的操作菜单(没有权限的菜单不现实)
  • 对所有用户显示所有菜单,当用户点击菜单以后再验证当前用户是否有此权限,如果没有则提示权限不足
10.1 HTML授权
  • 在菜单页面只显示当前用户拥有权限操作的菜单

  • shiro标签

    <shiro:hasPermission name="sys:c:save">
        <dd><a href="javascript:;">入库</a></dd>
    </shiro:hasPermission>
    
10.2 过滤器授权
  • 在shiro过滤器中对请求的url进行权限设置

    filterMap.put("/c_add.html","perms[sys:c:save]");
    
    //设置未授权访问的页面路径—当权限不足时显示此页面
    filter.setUnauthorizedUrl("/lesspermission.html");
    
10.3 注解授权
  • 配置Spring对Shiro注解的支持:ShiroConfig.java

    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        autoProxyCreator.setProxyTargetClass(true);
        return autoProxyCreator;
    }
    
    @Bean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor( DefaultWebSecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
    
  • 在请求的控制器添加权限注解

    @Controller
    @RequestMapping("customer")
    public class CustomerController {
    
        @RequestMapping("list")
        //如果没有 sys:k:find 权限,则不允许执行此方法
        @RequiresPermissions("sys:k:find")
        //    @RequiresRoles("")
        public String list(){
            System.out.println("----------->查询客户信息");
            return "customer_list";
        }
    
    }
    
  • 通过全局异常处理,指定权限不足时的页面跳转

    @ControllerAdvice
    public class GlobalExceptionHandler {
    
        @ExceptionHandler
        public String doException(Exception e){
            if(e instanceof AuthorizationException){
                return  "lesspermission";
            }
            return null;
        }
    
    }
    
10.4 手动授权
  • 在代码中进行手动的权限校验

    Subject subject = SecurityUtils.getSubject();
    if(subject.isPermitted("sys:k:find")){
        System.out.println("----------->查询客户信息");
        return "customer_list";
    }else{
        return "lesspermission";
    }
    

十一、缓存使用

使用Shiro进行权限管理过程中,每次授权都会访问realm中的doGetAuthorizationInfo方法查询当前用户的角色及权限信息,如果系统的用户量比较大则会对数据库造成比较大的压力

Shiro支持缓存以降低对数据库的访问压力(缓存的是授权信息)

11.1 导入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.4.0</version>
</dependency>
11.2 配置缓存策略
  • 在resources目录下创建一个xml文件(ehcache.xml)
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" dynamicConfig="false">

    <diskStore path="C:\TEMP" />

    <cache name="users"  timeToLiveSeconds="300"  maxEntriesLocalHeap="1000"/>

    <defaultCache name="defaultCache"
                  maxElementsInMemory="10000"
                  eternal="false"
                  timeToIdleSeconds="120"
                  timeToLiveSeconds="120"
                  overflowToDisk="false"
                  maxElementsOnDisk="100000"
                  diskPersistent="false"
                  diskExpiryThreadIntervalSeconds="120"
                  memoryStoreEvictionPolicy="LRU"/>
            <!--缓存淘汰策略:当缓存空间比较紧张时,我们要存储新的数据进来,就必然要删除一些老的数据
                LRU 最近最少使用
                FIFO 先进先出
                LFU  最少使用
            -->
</ehcache>
11.3 加入缓存管理
  • ShiroConfig.java
@Bean
public EhCacheManager getEhCacheManager(){
    EhCacheManager ehCacheManager = new EhCacheManager();
    ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
    return ehCacheManager;
}

@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(myRealm);
    securityManager.setCacheManager(getEhCacheManager());
    return securityManager;
}

十二、session管理

Shiro进行认证和授权是基于session实现的,Shiro包含了对session的管理

  • 如果我们需要对session进行管理

    • 自定义session管理器
    • 将自定义的session管理器设置给SecurityManager
  • 配置自定义SessionManager:ShiroConfig.java

    @Bean
    public DefaultWebSessionManager getDefaultWebSessionManager(){
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        System.out.println("----------"+sessionManager.getGlobalSessionTimeout()); // 1800000
        //配置sessionManager
        sessionManager.setGlobalSessionTimeout(5*60*1000);
        return sessionManager;
    }
    
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        securityManager.setCacheManager(getEhCacheManager());
        securityManager.setSessionManager(getDefaultWebSessionManager());
        return securityManager;
    }
    

解疑

  • 如何避免登录页面显示在页面框架中?

  • 在登录页面添如下JS代码:

    <script type="text/javascript">
        //页面框架
        if(window.top != window.self){
            window.top.location = window.self.location;
        }
    </script>
    

十三、RememberMe

将用户对页面访问的权限分为三个级别:

  • 未认证—可访问的页面—(陌生人)—问候
  • login.html、regist.html
  • 记住我—可访问的页面—(前女友)—朋友间的拥抱
  • info.html
  • 已认证—可访问的页面—(现女友)—牵手
  • 转账.html
13.1 在过滤器中设置“记住我”可访问的url
// anon     表示未认证可访问的url
// user     表示记住我可访问的url(已认证也可以访问)
//authc     表示已认证可访问的url
//perms		表示必须具备指定的权限才可访问
//logout	表示指定退出的url
filterMap.put("/","anon");
filterMap.put("/index.html","user");
filterMap.put("/login.html","anon");
filterMap.put("/regist.html","anon");
filterMap.put("/user/login","anon");
filterMap.put("/user/regist","anon");
filterMap.put("/layui/**","anon");
filterMap.put("/**","authc");
filterMap.put("/c_add.html","perms[sys:c:save]");
filterMap.put("/exit","logout");
13.2 在ShiroConfig.java中配置基于cookie的rememberMe管理器
@Bean
public CookieRememberMeManager cookieRememberMeManager(){
    CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
   
    //cookie必须设置name
    SimpleCookie cookie = new SimpleCookie("rememberMe");
    cookie.setMaxAge(30*24*60*60);
    
    rememberMeManager.setCookie(cookie);
    return  rememberMeManager;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(myRealm);
    securityManager.setCacheManager(getEhCacheManager());
    securityManager.setSessionManager(getDefaultWebSessionManager());
    //设置remember管理器
    securityManager.setRememberMeManager(cookieRememberMeManager());
    return securityManager;
}
13.3 登录认证时设置token“记住我”
  • 登录页面
<form action="/user/login" method="post">
    <p>帐号:<input type="text" name="userName"/></p>
    <p>密码:<input type="text" name="userPwd"/></p>
    <p>记住我:<input type="checkbox" name="rememberMe"/></p>
    <p><input type="submit" value="登录"/></p>
</form>
  • 控制器
@Controller
@RequestMapping("user")
public class UserController {

    @Resource
    private UserServiceImpl userService;

    @RequestMapping("login")
    public String login(String userName,String userPwd,boolean rememberMe){
        try {
            userService.checkLogin(userName,userPwd,rememberMe);
            System.out.println("------登录成功!");
            return "index";
        } catch (Exception e) {
            System.out.println("------登录失败!");
            return "login";
        }

    }
    
    //...
}
  • service
@Service
public class UserServiceImpl {

    public void checkLogin(String userName, String userPwd,boolean rememberMe) throws Exception {
        //Shiro进行认证 ——入口
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd);
        token.setRememberMe(rememberMe);
        subject.login(token);
    }
}

十四、Shiro多Realm配置

14.1 使用场景
  • 当shiro进行权限管理,数据来自于不同的数据源时,我们可以给SecurityManager配置多个Realm
14.2 多个Realm的处理方式
14.2.1 链式处理
  • 多个Realm依次进行认证
14.2.2 分支处理
  • 根据不同的条件从多个Realm中选择一个进行认证处理
14.3 多Realm配置(链式处理)
  • 定义多个Realm

    • UserRealm

      public class UserRealm extends AuthorizingRealm {
      
          Logger logger = LoggerFactory.getLogger(UserRealm.class);
      
          @Override
          public String getName() {
              return "UserRealm";
          }
      
          @Override
          protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
              return null;
          }
      
          @Override
          protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
              logger.info("--------------------------------UserRealm");
              //从token中获取username
              UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
              String username = token.getUsername();
              //根据username从users表中查询用户信息
      
              SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,"123456",getName());
              return info;
          }
      }
      
    • ManagerRealm

      public class ManagerRealm extends AuthorizingRealm {
      
          Logger logger = LoggerFactory.getLogger(ManagerRealm.class);
      
          @Override
          public String getName() {
              return "ManagerRealm";
          }
      
          @Override
          protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
              return null;
          }
      
          @Override
          protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
              logger.info("--------------------------------ManagerRealm");
              //从token中获取username
              UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
              String username = token.getUsername();
              //根据username从吗managers表中查询用户信息
      
              SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,"222222",getName());
              return info;
          }
      }
      
  • 在ShiroConfig.java中为SecurityManager配置多个Realm

    @Configuration
    public class ShiroConfig {
    
        @Bean
        public UserRealm userRealm(){
            UserRealm userRealm = new UserRealm();
            return  userRealm;
        }
    
        @Bean
        public ManagerRealm managerRealm(){
            ManagerRealm managerRealm = new ManagerRealm();
            return managerRealm;
        }
    
        @Bean
        public DefaultWebSecurityManager getDefaultWebSecurityManager(){
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            
            //securityManager中配置多个realm
            Collection<Realm> realms = new ArrayList<>();
            realms.add(userRealm());
            realms.add(managerRealm());
    
            securityManager.setRealms(realms);
            return securityManager;
        }
    
       //...
    
    }
    
    
  • 测试代码:

    • login.html

      <form action="user/login" method="post">
          <p>帐号:<input type="text" name="userName"/></p>
          <p>密码:<input type="text" name="userPwd"/></p>
          <p><input type="radio" name="loginType" value="User"/>普通用户
              <input type="radio" name="loginType" value="Manager"/>管理员</p>
      
          <p><input type="submit" value="登录"/></p>
      </form>
      
    • UserController.java

      @Controller
      @RequestMapping("user")
      public class UserController {
          Logger logger = LoggerFactory.getLogger(UserController.class);
      
          @RequestMapping("login")
          public String login(String userName,String userPwd, String loginType){
              logger.info("~~~~~~~~~~~~~UserController-login");
              try{
                  UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd);
                  Subject subject = SecurityUtils.getSubject();
                  subject.login(token);
                  return "index";
              }catch (Exception e){
                  return "login";
              }
      
          }
      
      }
      
14.4 Shiro认证处理源码分析
在这里插入图片描述
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        this.assertRealmsConfigured();
        Collection<Realm> realms = this.getRealms();
    	
    	// this.doMultiRealmAuthentication(realms, authenticationToken);中的realms参数就是认证会执行的Realm
        return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
    }
14.5 多Realm配置(分支处理)

根据不同的条件执行不同的Realm

  • 流程分析

在这里插入图片描述

  • 实现案例:用户不同身份登录执行不同的Realm

    • 自定义Realm(UserRealm\ManagerRealm)

      • 当在登录页面选择“普通用户”登录,则执行UserRealm的认证
      • 当在登录页面选择“管理员”登录,则执行ManagerRealm的认证
    • Realm的声明及配置

    • 自定义Token

      public class MyToken extends UsernamePasswordToken {
      
          private String loginType;
      
          public MyToken(String userName,String userPwd, String loginType) {
              super(userName,userPwd);
              this.loginType = loginType;
          }
      
          public String getLoginType() {
              return loginType;
          }
      
          public void setLoginType(String loginType) {
              this.loginType = loginType;
          }
      }
      
    • 自定义认证器

      public class MyModularRealmAuthenticator extends ModularRealmAuthenticator {
      
          Logger logger = LoggerFactory.getLogger(MyModularRealmAuthenticator.class);
      
          @Override
          protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
              logger.info("------------------------------MyModularRealmAuthenticator");
      
              this.assertRealmsConfigured();
              Collection<Realm> realms = this.getRealms();
      
              MyToken token = (MyToken) authenticationToken;
              String loginType = token.getLoginType(); // User
              logger.info("------------------------------loginType:"+loginType);
      
              Collection<Realm> typeRealms = new ArrayList<>();
              for(Realm realm:realms){
                  if(realm.getName().startsWith(loginType)){  //UserRealm
                      typeRealms.add(realm);
                  }
              }
      
             if(typeRealms.size()==1){
                 return this.doSingleRealmAuthentication((Realm)typeRealms.iterator().next(), authenticationToken);
             }else{
                 return this.doMultiRealmAuthentication(typeRealms, authenticationToken);
             }
      
          }
      
      }
      
    • 配置自定义认证器

      @Configuration
      public class ShiroConfig {
      
          @Bean
          public UserRealm userRealm(){
              UserRealm userRealm = new UserRealm();
              return  userRealm;
          }
      
          @Bean
          public ManagerRealm managerRealm(){
              ManagerRealm managerRealm = new ManagerRealm();
              return managerRealm;
          }
      
          @Bean
          public MyModularRealmAuthenticator myModularRealmAuthenticator(){
              MyModularRealmAuthenticator myModularRealmAuthenticator = new MyModularRealmAuthenticator();
              return myModularRealmAuthenticator;
          }
      
          @Bean
          public DefaultWebSecurityManager getDefaultWebSecurityManager(){
              DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
              //配置自定义认证器(放在realms设置之前)
              securityManager.setAuthenticator(myModularRealmAuthenticator());
      
              //securityManager中配置多个realm
              Collection<Realm> realms = new ArrayList<>();
              realms.add(userRealm());
              realms.add(managerRealm());
      
              securityManager.setRealms(realms);
              return securityManager;
          }
      
          //...
      
      }
      
    • 测试:控制器接受数据进行认证

      • login.html
      <form action="user/login" method="post">
          <p>帐号:<input type="text" name="userName"/></p>
          <p>密码:<input type="text" name="userPwd"/></p>
          <p><input type="radio" name="loginType" value="User" checked/>普通用户
          <input type="radio" name="loginType" value="Manager"/>管理员</p>
      
          <p><input type="submit" value="登录"/></p>
      </form>
      
      • UserController.java
      @Controller
      @RequestMapping("user")
      public class UserController {
          Logger logger = LoggerFactory.getLogger(UserController.class);
      
          @RequestMapping("login")
          public String login(String userName,String userPwd, String loginType){
              logger.info("~~~~~~~~~~~~~UserController-login");
              try{
                  //UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd);
                  MyToken token = new MyToken(userName,userPwd,loginType);
                  Subject subject = SecurityUtils.getSubject();
                  subject.login(token);
                  return "index";
              }catch (Exception e){
                  return "login";
              }
      
          }
      
      }
      

附件 单体项目开发技术清单

1. JSP应用
  • View JSP(Java Server Page)

  • Control Servlet

  • Model JDBC(Java Database Connection)

  • 第一阶段的项目:JSP/Servlet+JDBC

2. SSM
  • View JSP
  • Control SpringMVC
  • Model MyBatis
  • 第二阶段项目:JSP+SSM
3. SpringBoot
  • View Thymeleaf
  • Control SpringMVC
  • Model MyBatis/tkMapper
  • 第三阶段练习项目:thymeleaf+SpringBoot(SSM)

单体项目:项目的前端页面与服务端的代码在同一个项目中(部署在同一个服务器上)

在这里插入图片描述
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐