目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

Shiro

什么是权限控制

 将指定用户绑定到指定资源上,只能对指定资源进行(CRUD)操作。

限框架核心知识 ACL 和 RBAC

ACL: Access Control List 访问控制列表
 以前盛行的一种权限设计,它的核心在于用户直接和权限挂钩。
 例子:常见的文件系统权限设计(Linux 文件系统权限设计), 直接给用户加权限

  • 优点:简单易用,开发便捷。
  • 缺点:用户和权限直接挂钩,导致在授予时的复杂性,比较分散,不便于管理。

RBAC: Role Based Access Control
 基于角色的访问控制系统。权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。
 例子:基于 RBAC 模型的权限验证框架与应用 Apache Shiro、Spring Security。

  • 优点:简化了用户与权限的管理,通过对用户进行分类,使得角色与权限关联起来
  • 缺点:开发对比 ACL 相对复杂

 总结:不能过于复杂,规则过多,维护性和性能会下降, 更多分类 ABAC、PBAC 等。

Apache Shiro VS Spring Security

Spring Security
 Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在 Spring 应用上下文中配置的 Bean,充分利用了 Spring IoC,DI(控制反转 Inversion of Control ,DI:Dependency Injection 依赖注入)和 AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
 一句话:Spring Security 的前身是 Acegi Security ,是 Spring 项目组中用来提供安全认证服务的框架

Apache Shiro
 Apache Shiro 是一个强大且易用的 Java 安全框架,执行身份验证、授权、密码和会话管理。使用 Shiro 的易于理解的 API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
 一句话:Shiro 是一个强大易用的 Java 安全框架,提供了认证、授权、加密和会话管理等功能

优缺点对比

  • Apache Shiro 比 Spring Security , 前者使用更简单
  • Shiro 功能强大、 简单、灵活, 不跟任何的框架或者容器绑定,可以独立运行
  • Spring Security 对 Spring 体系支持比较好,脱离 Spring 体系则很难开发
  • SpringSecutiry 支持 OAuth 鉴权 https://spring.io/projects/spring-security-oauth,Shiro 需要自己实现。

 总结:两个框架没有谁超过谁,大体功能一致,新手一般先推荐 Shiro,学习会容易点。

Shiro 核心知识之架构图交互和四大模块讲解

ShiroFeatures

  • 身份认证(Authentication):一般指登录。
  • 授权(Authorization):用户分配角色或者访问某些资源的权限。
  • 会话管理(Session Management): 用户的会话管理员,多数情况下是 Web session。
  • 加密(Cryptography):数据加解密,比如密码加解密等。

用户访问 Shrio 权限控制运行流程和常见概念

ShiroArchitecture

  • Subject:我们把用户或者程序称为主题(如用户,第三方服务,cron 作业),主题去访问系统或者资源。
  • SecurityManager:安全管理器,Subject 的认证和授权都要在安全管理器下进行
  • Authenticator:认证器,主要负责 Subject 的认证。
  • Realm:数据域,Shiro 和安全数据的连接器,好比 JDBC 连接数据库; 通过 realm 获取认证授权相关信息。
  • Authorizer:授权器,主要负责 Subject 的授权, 控制 subject 拥有的角色或者权限。
  • Cryptography:加解密,Shiro 的包含易于使用和理解的数据加解密方法,简化了很多复杂的 API。
  • Cache Manager:缓存管理器,比如认证或授权信息,通过缓存进行管理,提高性能。

Reference

Apache Shiro 入门示例

https://start.spring.io/ 添加项目依赖

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<!--注释掉-->
			<!--<scope>runtime</scope>-->
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!--阿里巴巴druid数据源-->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.6</version>
		</dependency>

		<!--spring整合shiro-->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.4.0</version>
		</dependency>

快速上手认证和授权流程
认证流程

示例

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;

/**
 * 单元测试用例执行顺序
 *
 * @BeforeClass -> @Before -> @Test -> @After -> @AfterClass;
 */
public class QuickStartTest4_2 {

    //创建Realm
    private SimpleAccountRealm accountRealm = new SimpleAccountRealm();
    //创建SecurityManager
    private DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();

    @Before
    //初始化Realm、SecurityManager
    public void init() {
        //初始化Realm (数据源)
        accountRealm.addAccount("xdclass", "123");
        accountRealm.addAccount("jack", "456");

        //构建SecurityManager并且绑定Realm
        defaultSecurityManager.setRealm(accountRealm);
    }

    @Test
    //测试
    //SecurityUtils用于绑定SecurityManager,以及获取Subject。
    public void testAuthentication() {
        //将SecurityManager设置成上下文
        SecurityUtils.setSecurityManager(defaultSecurityManager);

        //以及获取Subject:获取当前操作主体, application user
        Subject subject = SecurityUtils.getSubject();

        //用户输入的账号密码
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack", "456");
        subject.login(usernamePasswordToken);

        //进行认证
        System.out.println("认证结果:"+subject.isAuthenticated());
    }
}

Shiro 认证和授权流程和常用 API 梳理
授权流程

//是否有对应的角色
subject.hasRole("root")

//获取subject名
subject.getPrincipal()

//检查是否有对应的角色,无返回值,直接在SecurityManager里面进行判断
subject.checkRole("admin")

//检查是否有对应的角色
subject.hasRole("admin")

//退出登录
subject.logout();

示例代码

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;

/**
 * 单元测试用例执行顺序
 *
 * @BeforeClass -> @Before -> @Test -> @After -> @AfterClass;
 */
public class QuickStartTest4_3 {
    private SimpleAccountRealm accountRealm = new SimpleAccountRealm();
    private DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();

    @Before
    public void init() {
        //初始化Realm (数据源)    每个账户添加相应的角色
        accountRealm.addAccount("xdclass", "123","root","admin");
        accountRealm.addAccount("jack", "456","user");

        //SecurityManager绑定Realm
        defaultSecurityManager.setRealm(accountRealm);
    }

    @Test
    public void testAuthentication() {
        //将SecurityManager加入上下文
        SecurityUtils.setSecurityManager(defaultSecurityManager);

        //获取Subject 主体
        Subject subject = SecurityUtils.getSubject();

        //用户输入的账号密码
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack", "456");
        subject.login(usernamePasswordToken);

        //是否认证通过:true
        System.out.println(" 认证结果:"+subject.isAuthenticated());
        
        //是否有对应的角色,有返回值(boolean)    false   (jack拥有的角色是user)
        System.out.println(" 是否有对应的root角色:"+subject.hasRole("root"));
        
        //获取subject主体的用户名:  jack
        System.out.println(" getPrincipal=" + subject.getPrincipal());
        
        //检查是否有对应的角色,无返回值,直接在SecurityManager里面进行判断
        subject.checkRole("user");
        
        //退出登录
        subject.logout();   //Stopping session with id [70b2429e-0e35-4822-9e5d-46ebeda35b36]

        //退出登录后的的认证结果:  false
        System.out.println("logout后认证结果:"+subject.isAuthenticated());
    }
}

Apache Shiro Realm

 realm 作用:Shiro 从 Realm 获取安全数据;默认自带的 realm:IDEA 查看 realm 继承关系,有默认实现和自定义继承的 realm。
两个概念

  • principal : 主体的标示,可以有多个,但是需要具有唯一性,常见的有用户名,手机号,邮箱等
  • credential:凭证, 一般就是密码。(所以一般我们说 principal + credential 就账号 + 密码),开发中,往往是自定义 realm , 即继承 AuthorizingRealm。

Shiro 内置 IniRealm 实操和权限验证 API

测试类

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;

/**
 * 单元测试用例执行顺序
 *
 */
public class QuickStartTest5_2 {
    @Test
    public void testAuthentication() {

        //创建SecurityManager工厂,通过配置文件shiro.ini创建
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");

        SecurityManager securityManager = factory.getInstance();

        //将securityManager 设置到当前运行环境中
        SecurityUtils.setSecurityManager(securityManager);

        Subject subject = SecurityUtils.getSubject();

        //用户输入的账号密码
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack", "456");

        subject.login(usernamePasswordToken);
        
        System.out.println(" 认证结果:"+subject.isAuthenticated());

        System.out.println(" 是否有对应的user角色:"+subject.hasRole("user"));

        System.out.println(" getPrincipal=" + subject.getPrincipal());

        subject.checkRole("user");

        subject.checkPermission("video:find");

        System.out.println( "是否有video:find 权限:"+ subject.isPermitted("video:find"));

        subject.logout();

        System.out.println("logout后认证结果:"+subject.isAuthenticated());
    }
    
    @Test
    public void testAuthentication2() {

        //创建SecurityManager工厂,通过配置文件ini创建
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");

        SecurityManager securityManager = factory.getInstance();

        //将securityManager 设置到当前运行环境中
        SecurityUtils.setSecurityManager(securityManager);

        Subject subject = SecurityUtils.getSubject();

        //用户输入的账号密码
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("xdclass", "123");

        subject.login(usernamePasswordToken);
        
        System.out.println(" 认证结果:"+subject.isAuthenticated());

        System.out.println(" 是否有对应的admin角色:"+subject.hasRole("admin"));

        System.out.println(" getPrincipal=" + subject.getPrincipal());

        subject.checkPermission("video:find");

        System.out.println( "是否有video:find 权限:"+ subject.isPermitted("video:find"));
        
        subject.logout();

        System.out.println("logout后认证结果:"+subject.isAuthenticated());
    }
}

在资源目录下创建 shiro.ini 文件

# 格式 name=password,role1,role2,..roleN
[users]
jack = 456, user
xdclass = 123, root, admin

# 格式 role=permission1,permission2...permissionN   也可以用通配符
# 下面配置user的权限为所有video:find,video:buy,如果需要配置video全部操作crud 则 user = video:*
[roles]
user = video:find,video:buy
visitor = good:find,good:buy,comment:*

# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *

Shiro 内置 JdbcRealm

SQL 脚本

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
# Dump of table roles_permissions
# ------------------------------------------------------------
DROP TABLE IF EXISTS `roles_permissions`;

CREATE TABLE `roles_permissions` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(100) DEFAULT NULL,
  `permission` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_roles_permissions` (`role_name`,`permission`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

LOCK TABLES `roles_permissions` WRITE;
/*!40000 ALTER TABLE `roles_permissions` DISABLE KEYS */;

INSERT INTO `roles_permissions` (`id`, `role_name`, `permission`)
VALUES
	(4,'admin','video:*'),
	(3,'role1','video:buy'),
	(2,'role1','video:find'),
	(5,'role2','*'),
	(1,'root','*');

/*!40000 ALTER TABLE `roles_permissions` ENABLE KEYS */;
UNLOCK TABLES;
# Dump of table user_roles
# ------------------------------------------------------------

DROP TABLE IF EXISTS `user_roles`;

CREATE TABLE `user_roles` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(100) DEFAULT NULL,
  `role_name` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_user_roles` (`username`,`role_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

LOCK TABLES `user_roles` WRITE;
/*!40000 ALTER TABLE `user_roles` DISABLE KEYS */;

INSERT INTO `user_roles` (`id`, `username`, `role_name`)
VALUES
	(1,'jack','role1'),
	(2,'jack','role2'),
	(4,'xdclass','admin'),
	(3,'xdclass','root');

/*!40000 ALTER TABLE `user_roles` ENABLE KEYS */;
UNLOCK TABLES;

# Dump of table users
# ------------------------------------------------------------

DROP TABLE IF EXISTS `users`;

CREATE TABLE `users` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(100) DEFAULT NULL,
  `password` varchar(100) DEFAULT NULL,
  `password_salt` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_users_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

LOCK TABLES `users` WRITE;
/*!40000 ALTER TABLE `users` DISABLE KEYS */;

INSERT INTO `users` (`id`, `username`, `password`, `password_salt`)
VALUES
	(1,'jack','123',NULL),
	(2,'xdclass','456',NULL);

/*!40000 ALTER TABLE `users` ENABLE KEYS */;
UNLOCK TABLES;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

在资源目录下创建 jdbcrealm.ini 文件

#注意 文件格式必须为ini,编码为ANSI

#声明Realm,指定realm类型
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm

#配置数据源
#dataSource=com.mchange.v2.c3p0.ComboPooledDataSource
dataSource=com.alibaba.druid.pool.DruidDataSource

# mysql-connector-java 5 用的驱动url是com.mysql.jdbc.Driver,mysql-connector-java6以后用的是com.mysql.cj.jdbc.Driver
dataSource.driverClassName=com.mysql.cj.jdbc.Driver

#避免安全警告
dataSource.url=jdbc:mysql://192.168.31.251:3306/shiro?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
dataSource.username=root
dataSource.password=soulboy

#指定数据源
jdbcRealm.dataSource=$dataSource

#开启查找权限
jdbcRealm.permissionsLookupEnabled=true

#指定SecurityManager的Realms实现,设置realms,可以有多个,用逗号隔开
securityManager.realms=$jdbcRealm

测试类

/**
 * 单元测试用例执行顺序
 *
 */
public class QuickStartTest5_3 {

    @Test
    public void testAuthentication() {

        //创建SecurityManager工厂,通过配置文件ini创建
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:jdbcrealm.ini");

        SecurityManager securityManager = factory.getInstance();

        //将securityManager 设置到当前运行环境中
        SecurityUtils.setSecurityManager(securityManager);

        Subject subject = SecurityUtils.getSubject();

        //用户输入的账号密码
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack", "123");

        subject.login(usernamePasswordToken);

        //org.apache.shiro.realm.jdbc.JdbcRealm

        System.out.println(" 认证结果:"+subject.isAuthenticated());

        System.out.println(" 是否有对应的role1角色:"+subject.hasRole("role1"));

        System.out.println("是否有video:find权限:"+ subject.isPermitted("video:find"));
    }
    
    @Test
	//方式二
    public void test2(){
        DefaultSecurityManager securityManager = new DefaultSecurityManager();

        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setUrl("jdbc:mysql://192.168.31.251:3306/shiro?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false");
        ds.setUsername("test");
        ds.setPassword("Xdclasstest");
        
	//创建JdbcRealm,
        JdbcRealm jdbcRealm = new JdbcRealm();
        jdbcRealm.setPermissionsLookupEnabled(true);
        jdbcRealm.setDataSource(ds);

	
	//将DruidDataSource绑定到Realm
        securityManager.setRealm(jdbcRealm);

        //将securityManager 设置到当前运行环境中
        SecurityUtils.setSecurityManager(securityManager);

        Subject subject = SecurityUtils.getSubject();

        //用户输入的账号密码
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack", "123");

        subject.login(usernamePasswordToken);
        
        System.out.println(" 认证结果:"+subject.isAuthenticated());

        System.out.println(" 是否有对应的role1角色:"+subject.hasRole("role1"));

        System.out.println("是否有video:find权限:"+ subject.isPermitted("video:find"));

        System.out.println("是否有任意权限:"+ subject.isPermitted("aaaa:xxxxxxxxx"));
    }
}

自定义 Realm

步骤

  • 创建一个类 ,继承 AuthorizingRealm->AuthenticatingRealm->CachingRealm->Realm
  • 重写授权方法 doGetAuthorizationInfo
  • 重写认证方法 doGetAuthenticationInfo

方法

  • 当用户登陆的时候会调用 doGetAuthenticationInfo
  • 进行权限校验的时候会调用: doGetAuthorizationInfo

对象介绍

* UsernamePasswordToken : 对应就是 shiro的token中有Principal和Credential
    * UsernamePasswordToken-》HostAuthenticationToken-》AuthenticationToken
* SimpleAuthorizationInfo:代表用户角色权限信息
* SimpleAuthenticationInfo :代表该用户的认证信息

定义 Realm 类

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.util.*;

/**
 * 自定义realm
 */
public class CustomRealm extends AuthorizingRealm {

    private final Map<String,String> userInfoMap = new HashMap<>();
    //初始化:userInfoMap
    {
        userInfoMap.put("jack","123");
        userInfoMap.put("xdclass","456");
    }

    //role -> permission
    private final Map<String,Set<String>> permissionMap = new HashMap<>();
    //初始化:permissionMap
    {
        Set<String> set1 = new HashSet<>();
        Set<String> set2 = new HashSet<>();

        set1.add("video:find");
        set1.add("video:buy");

        set2.add("video:add");
        set2.add("video:delete");

        permissionMap.put("user",set1);
        permissionMap.put("admin",set2);
    }

    //user -> role
    private final Map<String,Set<String>> roleMap = new HashMap<>();
    //初始化roleMap
    {
        Set<String> set1 = new HashSet<>();
        Set<String> set2 = new HashSet<>();

        set1.add("user");

        set2.add("admin");

        roleMap.put("jack",set1);
        roleMap.put("xdclass",set2);
    }

    //doGetAuthorizationInfo(获取授权信息): 进行权限校验的时候会调用
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("权限 doGetAuthorizationInfo");

        //拿到用户名
        String name = (String)principals.getPrimaryPrincipal();

        //根据user查role
        Set<String> roles = getRolesByNameFromDB(name);

        //根据role查permission:该用户拥有所有角色的权限集合
        Iterator<String> it = roles.iterator();
        Set<String> permissions = null;
        while(it.hasNext()){
            String role = it.next();
            Set<String> permission = getPermissionsByRoleFromDB(role);
            if(permissions == null){
                permissions = permission;
            }
            //并集
            permissions.addAll(permission);
        }

        //Set<String> permissions = getPermissionsByRoleFromDB(role);

        //封装simpleAuthorizationInfo类
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //封装roles
        simpleAuthorizationInfo.setRoles(roles);
        //封装permissions
        simpleAuthorizationInfo.setStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }

    //doGetAuthenticationInfo(获取身份验证信息): 当用户登陆的时候会调用
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        System.out.println("认证 doGetAuthenticationInfo");

        //从token获取身份信息,token代表用户输入的信息,获取用户名。
        String name = (String)token.getPrincipal();

        //模拟从数据库中取密码
        String pwd = getPwdByUserNameFromDB(name);

        //如果没有找到密码返回null
        if( pwd == null || "".equals(pwd)){
            return null;
        }

        //找到密码则把用户名和密码封装到SimpleAuthenticationInfo中返回:SecurityManager使用SimpleAuthenticationInfo自动做校验
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, pwd, this.getName());
        return simpleAuthenticationInfo;
    }

    /**
     * 模拟从数据库获取用户角色集合
     * @param name
     * @return
     */
    private Set<String> getRolesByNameFromDB(String name) {
        return roleMap.get(name);
    }

    /**
     *  模拟从数据库获取权限集合
     * @param name
     * @return
     */
    private Set<String> getPermissionsByRoleFromDB(String name) {
        return permissionMap.get(name);
    }

    private String getPwdByUserNameFromDB(String name) {
        return userInfoMap.get(name);
    }
}

测试类

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Before;
import org.junit.Test;

/**
 * 单元测试用例执行顺序
 *
 */
public class QuickStartTest5_4 {

    //创建自定义Realm
    private CustomRealm customRealm = new CustomRealm();
    private DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();

    @Before
    public void init(){
        //SecurityManager绑定自定义Realm
        defaultSecurityManager.setRealm(customRealm);
        //SecurityManager绑定到上下文中
        SecurityUtils.setSecurityManager(defaultSecurityManager);
    }

    @Test
    public void testAuthentication() {

        //获取当前操作的主体
        Subject subject = SecurityUtils.getSubject();

        //用户输入的账号密码
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack", "123");
        subject.login(usernamePasswordToken);

        //登录
        System.out.println(" 认证结果:"+subject.isAuthenticated());

        //拿到主体标示属性
        System.out.println(" getPrincipal=" + subject.getPrincipal());

        subject.checkRole("user");

        System.out.println("是否有对应的角色:"+subject.hasRole("user"));

        System.out.println("是否有对应的权限:"+subject.isPermitted("video:find"));
    }
}

Shiro 常用的内置 Filter

 核心过滤器类是 DefaultFilter。
 配置哪个路径对应哪个拦截器进行处理,URL 需要有一定的命名规范:/pub(游客)、/user(用户)、/admin(管理员)等对应不同的 Filter 处理。

  • authc:org.apache.shiro.web.filter.authc.FormAuthenticationFilter
需要认证登录才能访问
  • user:org.apache.shiro.web.filter.authc.UserFilter
用户拦截器,表示必须存在用户。
  • anon:org.apache.shiro.web.filter.authc.AnonymousFilter
匿名拦截器,不需要登录即可访问的资源,匿名用户或游客,一般用于过滤静态资源。
  • roles:org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
角色授权拦截器,验证用户是或否拥有角色。  
参数可写多个,表示某些角色才能通过,多个参数时写 roles["admin,user"],当有多个参数时必须每个参数都通过才算通过
  • perms:org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
权限授权拦截器,验证用户是否拥有权限  
参数可写多个,表示需要某些权限才能通过,多个参数时写 perms["user, admin"],当有多个参数时必须每个参数都通过才算可以
  • authcBasic:org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
httpBasic 身份验证拦截器。
  • logout:org.apache.shiro.web.filter.authc.LogoutFilter
退出拦截器,执行后会直接跳转到shiroFilterFactoryBean.setLoginUrl();	设置的 url
  • port:org.apache.shiro.web.filter.authz.PortFilter
端口拦截器, 可通过的端口。
  • ssl:org.apache.shiro.web.filter.authz.SslFilter
ssl拦截器,只有请求协议是https才能通过

Filter 路径配置

 路径通配符支持: ?、*、**。注意通配符匹配不 包括目录分隔符“/”
 * 可以匹配所有,不加*可以进行前缀匹配,但多个冒号就需要多个 * 来匹配。
匹配规则说明

URL权限采取第一次匹配优先的方式
? : 匹配一个字符,如 /user? , 匹配 /user3,但不匹配/user/;
* : 匹配零个或多个字符串,如 /user* ,匹配 /usertest,但不匹配 /user/1
** : 匹配路径中的零个或多个路径,如 /user/** 将匹 配 /user/xxx 或 /user/xxx/yyy

规则优先级
 优先命中原则,先配置的为主。
 性能问题:通配符比字符串匹配会复杂点,所以性能也会稍弱,推荐是使用字符串匹配方式。

/user/**=filter1  
/user/add=filter2  
  
请求 /user/add  命中的是filter1拦截器

Shiro 数据安全之数据加解密

  • 散列算法:一般叫 hash,简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数,适合存储密码,比如 MD5。
  • salt(盐):如果直接通过散列函数得到加密数据,容易被对应解密网站暴力破解,一般会在应用程序里面加特殊的自动进行处理,比如用户 id,例子:加密数据 = MD5(明文密码 + 用户 id), 破解难度会更大,也可以使用多重散列,比如多次 md5。
  • Shiro 里面 CredentialsMatcher,用来验证密码是否正确。
//源码:AuthenticatingRealm -> assertCredentialsMatch()
//一般会自定义验证规则
	@Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new 		HashedCredentialsMatcher();
        
        //散列算法,使用MD5算法;
        hashedCredentialsMatcher.setHashAlgorithmName("md5");

        //散列的次数,比如散列两次,相当于 md5(md5("xxx"));
        hashedCredentialsMatcher.setHashIterations(2);
        
        return hashedCredentialsMatcher;
    }

Shiro 权限控制注解方式、编程方式

配置文件的方式
 使用 ShiroConfig

注解方式

  • @RequiresRoles(value={"admin", "editor"}, logical= Logical.AND):需要角色 admin 和 editor 两个角色 AND 表示两个同时成立。
  • @RequiresPermissions (value={"user:add", "user:del"}, logical= Logical.OR):需要权限 user:add 或 user:del 权限其中一个,OR 是或的意思。
  • @RequiresAuthentication:已经授过权,调用 Subject.isAuthenticated()返回 true。
  • @RequiresUser:身份验证或者通过记 住我登录的。
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Controller
@RequestMapping("/api/admin/user")//admin代表权限,user代表模块
public class UserController {

    //需要同时拥有admin和editor两个角色才可以访问。logical= Logical.AND
    //需要同时拥有admin或editor两个角色才可以访问。logical= Logical.OR
    //@RequiresRoles(value={"admin", "editor"}, logical= Logical.OR)
    //需要权限"user:add", "user:del"
    @RequiresPermissions(value={"user:add", "user:del"}, logical= Logical.OR)
    @RequestMapping("/list_user")
    public Object listUser(){
        return null;
    }
}

编程方式

  • subject.hasRole("xxx");
  • subject.isPermitted("xxx");
  • subject. isPermittedAll("xxxxx","yyyy");
  • subject.checkRole("xxx"); // 无返回值,可以认为内部使用断言的方式
Subject subject = SecurityUtils.getSubject(); 
//基于角色判断
if(subject.hasRole(“admin”)) {
	//有角色,有权限
} else {
	//无角色,无权限
	
}
//或者权限判断
if(subject.isPermitted("/user/add")){
    //有权限
}else{
    //无权限
}

Shiro 缓存模块

 shiro 中提供了对认证信息和授权信息的缓存。 默认是关闭认证信息缓存的,对于授权信息的缓存 shiro 默认开启的(因为授权的数据量大).

  • AuthenticatingRealm 提供了对 AuthenticationInfo 信息的缓存。认证!
  • AuthorizingRealm 提供了对 AuthorizationInfo 信息的缓存。授权!

Shiro Session 模块

会话 session
 用户和程序直接的链接,程序可以根据 session 识别到哪个用户,和 javaweb 中的 session 类似。

会话管理器 SessionManager
 会话管理器管理所有 subject 的所有操作,是 shiro 的核心组件。v

SessionDao 会话存储/持久化
 SessionDAO AbstractSessionDAO CachingSessionDAOEnterpriseCacheSessionDAO MemorySessionDAO
核心方法

//创建
Serializable create(Session session);
//获取
Session readSession(Serializable sessionId) throws UnknownSessionException;
//更新
void update(Session session) 
//删除,会话过期时会调用
void delete(Session session);
//获取活跃的session
Collection<Session> getActiveSessions();

会话存储有多个实现

SpringBoot2.x 整合 Shiro 综合案例

表设计

# ************************************************************
# Sequel Pro SQL dump
# Version 4541
#
# http://www.sequelpro.com/
# https://github.com/sequelpro/sequelpro
#
# Host: 120.76.62.13 (MySQL 5.7.17)
# Database: xdclass_shiro
# Generation Time: 2019-05-12 13:44:51 +0000
# ************************************************************


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;


# Dump of table permission
# ------------------------------------------------------------

DROP TABLE IF EXISTS `permission`;

CREATE TABLE `permission` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(128) DEFAULT NULL COMMENT '名称',
  `url` varchar(128) DEFAULT NULL COMMENT '接口路径',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

LOCK TABLES `permission` WRITE;
/*!40000 ALTER TABLE `permission` DISABLE KEYS */;

INSERT INTO `permission` (`id`, `name`, `url`)
VALUES
	(1,'video_update','/api/video/update'),
	(2,'video_delete','/api/video/delete'),
	(3,'video_add','/api/video/add'),
	(4,'order_list','/api/order/list'),
	(5,'user_list','/api/user/list');

/*!40000 ALTER TABLE `permission` ENABLE KEYS */;
UNLOCK TABLES;


# Dump of table role
# ------------------------------------------------------------

DROP TABLE IF EXISTS `role`;

CREATE TABLE `role` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(128) DEFAULT NULL COMMENT '名称',
  `description` varchar(64) DEFAULT NULL COMMENT '描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

LOCK TABLES `role` WRITE;
/*!40000 ALTER TABLE `role` DISABLE KEYS */;

INSERT INTO `role` (`id`, `name`, `description`)
VALUES
	(1,'admin','普通管理员'),
	(2,'root','超级管理员'),
	(3,'editor','审核人员');

/*!40000 ALTER TABLE `role` ENABLE KEYS */;
UNLOCK TABLES;


# Dump of table role_permission
# ------------------------------------------------------------

DROP TABLE IF EXISTS `role_permission`;

CREATE TABLE `role_permission` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `role_id` int(11) DEFAULT NULL,
  `permission_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

LOCK TABLES `role_permission` WRITE;
/*!40000 ALTER TABLE `role_permission` DISABLE KEYS */;

INSERT INTO `role_permission` (`id`, `role_id`, `permission_id`)
VALUES
	(1,3,1),
	(2,3,2),
	(3,3,3),
	(4,2,1),
	(5,2,2),
	(6,2,3),
	(7,2,4);

/*!40000 ALTER TABLE `role_permission` ENABLE KEYS */;
UNLOCK TABLES;


# Dump of table user
# ------------------------------------------------------------

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(128) DEFAULT NULL COMMENT '用户名',
  `password` varchar(256) DEFAULT NULL COMMENT '密码',
  `create_time` datetime DEFAULT NULL,
  `salt` varchar(128) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

LOCK TABLES `user` WRITE;
/*!40000 ALTER TABLE `user` DISABLE KEYS */;

INSERT INTO `user` (`id`, `username`, `password`, `create_time`, `salt`)
VALUES
	(1,'二当家小D','123456',NULL,NULL),
	(2,'大当家','123456789',NULL,NULL),
	(3,'jack','123',NULL,NULL);

/*!40000 ALTER TABLE `user` ENABLE KEYS */;
UNLOCK TABLES;


# Dump of table user_role
# ------------------------------------------------------------

DROP TABLE IF EXISTS `user_role`;

CREATE TABLE `user_role` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `role_id` int(11) DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  `remarks` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

LOCK TABLES `user_role` WRITE;
/*!40000 ALTER TABLE `user_role` DISABLE KEYS */;

INSERT INTO `user_role` (`id`, `role_id`, `user_id`, `remarks`)
VALUES
	(1,3,1,'二当家小D是editor'),
	(2,1,3,'jack是admin'),
	(3,2,3,'jack是root'),
	(4,3,3,'jack是editor'),
	(5,1,2,'大当家是admin');

/*!40000 ALTER TABLE `user_role` ENABLE KEYS */;
UNLOCK TABLES;



/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

引入 pom.xml 依赖

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.0.1</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<!--注释掉-->
			<!--<scope>runtime</scope>-->
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>


		<!--阿里巴巴druid数据源-->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.6</version>
		</dependency>

		<!--spring整合shiro-->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.4.0</version>
		</dependency>

添加 application.properties

#==============================数据库相关配置========================================
spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.31.251:3306/shiros?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username =root
spring.datasource.password =soulboy
#使用阿里巴巴druid数据源,默认使用自带的
#spring.datasource.type =com.alibaba.druid.pool.DruidDataSource
#开启控制台打印sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

# mybatis 下划线转驼峰配置,两者都可以
#mybatis.configuration.mapUnderscoreToCamelCase=true
mybatis.configuration.map-underscore-to-camel-case=true

开发用户-角色-权限 多对多关联查询 SQL
第一步 查询用户对应的角色映射关系

select * from user u 
left join user_role ur on u.id=ur.user_id
where  u.id=3

第二步 查询用户对应的角色信息

select * from user u 
left join user_role ur on u.id=ur.user_id
left join role r on ur.role_id = r.id
where  u.id=3

第三步 查询角色和权限的关系

select * from user u 
left join user_role ur on u.id=ur.user_id
left join role r on ur.role_id = r.id
left join role_permission rp on r.id=rp.role_id
where  u.id=1

第四步 查询角色对应的权限信息(某个用户具备的角色和权限集合)

select * from user u 
left join user_role ur on u.id=ur.user_id
left join role r on ur.role_id = r.id
left join role_permission rp on r.id=rp.role_id
left join permission p on rp.permission_id=p.id
where  u.id=1

启动类添加 Mapper 路径扫描注解

@SpringBootApplication
@MapperScan("net.xdclass.rbac_shiro.dao")
public class RbacShiroApplication {

	public static void main(String[] args) {
		SpringApplication.run(RbacShiroApplication.class, args);
	}
}

Domain
Permission

public class Permission {

    private int id;

    private String name;

    private String url;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}

Role

/**
 * 角色
 */
public class Role {

    private int id;

    private String name;

    private String description;

    private List<Permission> permissionList;

    public List<Permission> getPermissionList() {
        return permissionList;
    }

    public void setPermissionList(List<Permission> permissionList) {
        this.permissionList = permissionList;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

RolePermission

/**
 * 角色权限
 */
public class RolePermission {

    private int id;

    private int roleId;

    private int permissionId;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getRoleId() {
        return roleId;
    }

    public void setRoleId(int roleId) {
        this.roleId = roleId;
    }

    public int getPermissionId() {
        return permissionId;
    }

    public void setPermissionId(int permissionId) {
        this.permissionId = permissionId;
    }
}

User

/**
 * 用户
 */
public class User {

    private int id;

    private String username;

    private Date createTime;

    private String salt;


    /**
     * 角色集合
     */
    private List<Role> roleList;

    public List<Role> getRoleList() {
        return roleList;
    }

    public void setRoleList(List<Role> roleList) {
        this.roleList = roleList;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }
}

UserRole

public class UserRole {

    private int id;

    private int userId;

    private int roleId;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public int getRoleId() {
        return roleId;
    }

    public void setRoleId(int roleId) {
        this.roleId = roleId;
    }
}

DAO 层
PermissionMapper

public interface PermissionMapper {
    @Select("select p.id as id, p.name as name, p.url as url from  role_permission rp " +
            "left join permission p on rp.permission_id=p.id " +
            "where  rp.role_id= #{roleId} ")
    List<Permission> findPermissionListByRoleId(@Param("roleId") int roleId);
}

RoleMapper

public interface RoleMapper {

    @Select("select ur.role_id as id, " +
            "r.name as name, " +
            "r.description as description " +
            " from  user_role ur left join role r on ur.role_id = r.id " +
            "where  ur.user_id = #{userId}")
    @Results(
            value = {
                    @Result(id=true, property = "id",column = "id"),
                    @Result(property = "name",column = "name"),
                    @Result(property = "description",column = "description"),
                    @Result(property = "permissionList",column = "id",
                    many = @Many(select = "net.xdclass.rbac_shiro.dao.PermissionMapper.findPermissionListByRoleId", fetchType = FetchType.DEFAULT)
                    )
            }
    )
    List<Role> findRoleListByUserId(@Param("userId") int userId);
}

UserMapper

public interface UserMapper {

    @Select("select * from user where username = #{username}")
    User findByUsername(@Param("username") String username);

    @Select("select * from user where id=#{userId}")
    User findById(@Param("userId") int id);

    @Select("select * from user where username = #{username} and password = #{pwd}")
    User findByUsernameAndPwd(@Param("username") String username, @Param("pwd") String pwd);
}

Service 层
UserService

public interface UserService {

    /**
     * 获取全部用户信息,包括角色,权限
     * @param username
     * @return
     */
    User findAllUserInfoByUsername(String username);

    /**
     * 获取用户基本信息
     * @param userId
     * @return
     */
    User findSimpleUserInfoById(int userId);

    /**
     * 根据用户名查找用户信息
     * @param username
     * @return
     */
    User findSimpleUserInfoByUsername(String username);
}

UserServiceImpl

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private UserMapper userMapper;

    @Override
    public User findAllUserInfoByUsername(String username) {
        User user = userMapper.findByUsername(username);

        //用户的角色集合
        List<Role> roleList =  roleMapper.findRoleListByUserId(user.getId());
        user.setRoleList(roleList);
        return user;
    }

    @Override
    public User findSimpleUserInfoById(int userId) {
        return userMapper.findById(userId);
    }

    @Override
    public User findSimpleUserInfoByUsername(String username) {
        return userMapper.findByUsername(username);
    }
}

Controller 层
PublicController

@RestController
@RequestMapping("pub")
public class PublicController {

    @Autowired
    private UserService userService;


    @RequestMapping("find_user_info")
    public Object findUserInfo(@RequestParam("username")String username){

        return userService.findAllUserInfoByUsername(username);
    }

}

测试

// http://localhost:8080/pub/find_user_info?username=jack

{
  "id": 3,
  "username": "jack",
  "createTime": null,
  "salt": null,
  "roleList": [
    {
      "id": 1,
      "name": "admin",
      "description": "普通管理员",
      "permissionList": [
        
      ]
    },
    {
      "id": 2,
      "name": "root",
      "description": "超级管理员",
      "permissionList": [
        {
          "id": 1,
          "name": "video_update",
          "url": "/api/video/update"
        },
        {
          "id": 2,
          "name": "video_delete",
          "url": "/api/video/delete"
        },
        {
          "id": 3,
          "name": "video_add",
          "url": "/api/video/add"
        },
        {
          "id": 4,
          "name": "order_list",
          "url": "/api/order/list"
        }
      ]
    },
    {
      "id": 3,
      "name": "editor",
      "description": "审核人员",
      "permissionList": [
        {
          "id": 1,
          "name": "video_update",
          "url": "/api/video/update"
        },
        {
          "id": 2,
          "name": "video_delete",
          "url": "/api/video/delete"
        },
        {
          "id": 3,
          "name": "video_add",
          "url": "/api/video/add"
        }
      ]
    }
  ]
}

自定义 CustomRealm 实战
 通过 Mapper 查询数据库数据,进行校验和登录认证。

/**
 * 自定义realm
 */
public class CustomRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    /**
     * 进行权限校验的时候回调用(授权)
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("授权 doGetAuthorizationInfo");
        String username = (String)principals.getPrimaryPrincipal();
        User user = userService.findAllUserInfoByUsername(username);

        List<String> stringRoleList = new ArrayList<>();
        List<String> stringPermissionList = new ArrayList<>();


        List<Role> roleList = user.getRoleList();

        for(Role role : roleList){
            //角色集合
            stringRoleList.add(role.getName());

            List<Permission> permissionList = role.getPermissionList();

            for(Permission p: permissionList){
                if(p!=null){
                    //权限集合
                    stringPermissionList.add(p.getName());
                }
            }
        }

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRoles(stringRoleList);
        simpleAuthorizationInfo.addStringPermissions(stringPermissionList);
        return simpleAuthorizationInfo;
    }

    /**
     * 用户登录的时候会调用(认证)
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        System.out.println("认证 doGetAuthenticationInfo");

        //从token获取用户信息,token代表用户输入
        String username = (String)token.getPrincipal();

        User user =  userService.findAllUserInfoByUsername(username);

        //取密码
        String pwd = user.getPassword();
        if(pwd == null || "".equals(pwd)){
            return null;
        }

        return new SimpleAuthenticationInfo(username, user.getPassword(), this.getClass().getName());
    }
}

ShiroFilterFactoryBean 配置

  • ShiroConfig
# 需要注入以下类型Bean
* ShiroFilterFactoryBean (过滤器路径设置)
* SecurityManager (环境)
* SessionManager(CustomSessionManager)
* CustomRealm (自定义Real)
* HashedCredentialsMatcher (加密散列加密算法)
  • SessionManager
# 自定CustomSessionManager需要继承以下其中一个类
 * DefaultSessionManager: 默认实现,常用于javase  
 * ServletContainerSessionManager: web环境  
 * DefaultWebSessionManager:常用于自定义实现

CustomSessionManager

import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
public class CustomSessionManager extends DefaultWebSessionManager {

}

ShiroConfig

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    //注入 ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){

        System.out.println("执行 ShiroFilterFactoryBean.shiroFilter()");

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        //必须设置securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //需要登录的接口,如果访问某个接口,需要登录却没登录,则调用此接口(如果不是前后端分离,则跳转页面)
        shiroFilterFactoryBean.setLoginUrl("/pub/need_login");

        //登录成功,跳转url,如果前后端分离,则没这个调用
        shiroFilterFactoryBean.setSuccessUrl("/");

        //没有权限,未授权就会调用此方法, 先验证登录-》再验证是否有权限
        shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit");

        //拦截器路径,坑一,部分路径无法进行拦截,时有时无;因为使用的是hashmap, 无序的,应该改为LinkedHashMap
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        //退出过滤器
        filterChainDefinitionMap.put("/logout","logout");

        //匿名可以访问,也是就游客模式
        filterChainDefinitionMap.put("/pub/**","anon");

        //登录用户才可以访问
        filterChainDefinitionMap.put("/authc/**","authc");

        //管理员角色才可以访问
        filterChainDefinitionMap.put("/admin/**","roles[admin]");

        //有编辑权限才可以访问
        filterChainDefinitionMap.put("/video/update","perms[video_update]");


        //坑二: 过滤链是顺序执行,从上而下,一般讲/** 放到最下面

        //authc : url定义必须通过认证才可以访问
        //anon  : url可以匿名访问
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }

    //注入 SecurityManager
    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //传入 CustomRealm
        securityManager.setRealm(customRealm());//不能使用new,只能调用customRealm()方法返回CustomRealm
        //传入 SessionManager
        securityManager.setSessionManager(sessionManager());

        return securityManager;
    }



    @Bean
    public CustomRealm customRealm(){
        CustomRealm customRealm = new CustomRealm();

        customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return customRealm;
    }


    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();

        //设置散列算法:这里使用的MD5算法
        credentialsMatcher.setHashAlgorithmName("md5");

        //散列次数,好比散列2次,相当于md5(md5(xxxx))
        credentialsMatcher.setHashIterations(2);

        return credentialsMatcher;
    }

    @Bean
    public SessionManager sessionManager(){
        CustomSessionManager customSessionManager = new CustomSessionManager();
        return customSessionManager;
    }
}

作者:Soulboy