Shiro入门

一、权限框架设计之ACL和RBAC

1.1 什么是ACL 和RBAC

  • ACL:Access Control List 访问控制列表
    • 以前盛行的一种权限设计,它的核心在于用户和权限直接挂钩
    • 优点:简单易用,开发便捷
    • 缺点:用户和权限直接挂钩,导致在授权时比较复杂,不易管理
    • 例子:常见的文件系统设计,直接给用户加权限
  • RBAC:Role Based Access Control
    • 基于角色的访问控制系统。 权限与角色挂钩,用户通过称为适当的角色而得到该角色的权限。
    • 优点:简化了用户与权限的管理,通过对用户进行分类,使得角色与权限关联起来
    • 缺点:相对ACL而言开发复杂
    • 例子:Apache Shiro,Spring Security

主流权限框架Spring Security和Apache Shiro技术选型

Spring Security 是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。
它提供了一组可以在Spring应用上下文中配置的Bean, 充分利用了Spring IOC(控制反转), DI(依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。
使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
  • 这两个安全框架该如何选择:
    • Apache Shiro轻量级,Spring Security 重量级
    • Shiro功能强大,简单,灵活,可以独立运行。
    • Spring Securiy 对Spring体系支持比较好。

二、Apache Shiro基础概念知识和架构

2.1 Shiro的四大核心模块

  • 四大核心模块官方说明地址http://shiro.apache.org/introduction.html
  • 什么是身份认证
    • Authentication:身份认证,一般指的是登录(但不局限于登录)
  • 什么是授权
    • Authorization:给用户分配角色或访问某些资源的权限
  • 什么是会话管理
    • Session Manaagement:用户的会员管理员,多数情况下是web Session
  • 什么是密码学
    • Cryptography:数据加解密,比如密码加解密等
      在这里插入图片描述

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

  • 官网地址:https://shiro.apache.org/architecture.html
  • 用户访问Shiro认证流程
    在这里插入图片描述
  • Subject
    • 我们把用户或者程序称为主体(如用户,第三方服务,cron作业),主体去访问系统或者资源
  • SecurityManager:
    • 安全管理器,Subject的认证和授权都要在安全管理器下进行。
  • Authenticator:
    • 认证器:主要负责Subject的认证
  • Authorizer:
    • 授权器,主要负责Subject的授权, 控制Subject拥有的角色或者权限
  • Realm:
    • 数据域,Shiro和安全数据的连接器,好比jdbc连接数据库;通过realm获取认证授权相关信息
  • Cryptography:
    • 加解密,Shiro的包含易于使用和理解的数据加解密方法,简化了很多复杂的API
  • CacheManager:
    • 缓存管理器,比如认证或授权信息,通过缓存进行管理,提高性能。
      在这里插入图片描述
  • 更多Shiro资料访问:https://shiro.apache.org/reference.html

三、SpringBoot整合Shiro快速入门实战

3.1 SpringBoot整合Shiro项目搭建

  • 开发环境: Maven3.8 + jdk1.8 + SpringBoot2.x + Shiro + MySQL + IDEA
  • 引入依赖
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <!-- MySQL -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>

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

        <!-- SpringBoot整合shiro -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
  • 启动主程序,检查环境是否搭建成功!

3.2 SpringBoot整合Shiro之认证快速入门

  • 认证流程
    在这里插入图片描述
  • 代码实现
@Test
    public void authenticationTest(){
        // 1.构建SecurityManager环境
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        // 2. 构建Realm数据域
        SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
        // 3. 给realm添加认证信息,addAccount()的参数就类似于存在数据库中的用户信息
        simpleAccountRealm.addAccount("rose","123");
        simpleAccountRealm.addAccount("tom","123");

        // 4. 将Realm和SecurityManager关联
        defaultSecurityManager.setRealm(simpleAccountRealm);

        // 5. 通过securityUtils设置上下文 SecurityManager ,将defaultSecurityManager交给Shiro管理
        SecurityUtils.setSecurityManager(defaultSecurityManager);

        // 5. 通过SecurityUtils 构建Subject主体
        Subject subject = SecurityUtils.getSubject();
        // 6.1 构建AuthenticationToken
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("rose","1234");
        // 6. 通过subject调用login方法
        subject.login(usernamePasswordToken);

        // 7. 查看认证结果
        System.out.println("是否认证通过:" + subject.isAuthenticated());

    }
  • isAuthenticated()返回false时,即认证失败,Shiro会报IncorrectCredentialsException错误。
  • 在实际的项目开发过程中我们可以针对这个报错做特殊处理,(根据需求需要)

3.3 SpringBoot整合Shiro之授权快速入门

  • 授权的相关API:
// 是否有对应角色: 返回值Boolean
subject.hasRole("root")

// 检查是否有对应角色, 返回值void ,当主体没有对应角色时,报UnauthorizedException错
subject.checkRole("root")

// 认证结果
subject.isAuthenticated()

//获取当前主体的用户名
subject.getPrincipal();
  • 代码实现:
    @Test
    public void authorizationTest(){
        // 1.实例化SecurityManager对象
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        // 2.实例化realm对象
        SimpleAccountRealm accountRealm = new SimpleAccountRealm();
        // 3.给realm添加授权信息
        accountRealm.addAccount("rose","123","root","admin");

        accountRealm.addAccount("tom","123","user");
        // 4. 将realm和securityManager关联
        defaultSecurityManager.setRealm(accountRealm);
        // 5.将实例化出来的SecurityManager对象交给Shiro管理
        SecurityUtils.setSecurityManager(defaultSecurityManager);

        // 6.实例化Subject主体对象
        Subject subject = SecurityUtils.getSubject();

        // 7.主体进行认证
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("rose","123");
        subject.login(usernamePasswordToken);

        //8.检查当前主体是否具有相关角色(权限)
        System.out.println("是否认证通过:"+ subject.isAuthenticated());
        //9.检查当前主体是否有root角色
        System.out.println("是否有root角色:" + subject.hasRole("root"));

        //10.获取当前主体的用户名
        System.out.println("用户名:" + subject.getPrincipal());

    }

四、详解Apache Shiro realm实战

4.1 Shiro安全数据来源之Realm讲解

  • Realm作用:Shiro从Realm获取安全数据
  • 默认自带的Realm:有默认实现和自定义继承的Realm
  • 两个概念:
    • Principal :主体的标识,可以有多个,但是需要具有唯一性,常见的有用户名、手机号、邮箱等
    • credential:凭证,一般指的是密码。
  • 实际开发中:往往是自定义realm,即继承AuthorizingRealm

4.2 快速上手之Shiro内置IniRealm实操和权限验证API

  • Resources目录下创建shiro.ini文件;后缀必须是.ini,文件名随意
  • shiro.ini内容格式如下:
# 格式 name=password,role1,role2,..roleN
[users]
jack = 123, user
xdclass = 123, root,admin

# 格式 role=permission1,permission2...permissionN 也可以用通配符
# 下面配置user的权限为所有video:find,video:buy,如果需要配置video全部操作crud 则
[roles]
user = video:find,video:buy
root = video:*
# 'admin' role has all permissions, indicated by the wildcard '*' admin = *
  • 编写测试代码:
    • 通过IniSecurityManagerFactory工厂类的getInstance()构建SecurityManager
    • 将构建的SecurityManager通过securityUtils.setSecurityManager()添加到Shiro中
public class IniRealmQuickStartTest {


    @Test
    public void iniRealmTest(){
        //1.通过SecurityManager工厂构建SecurityManager对象
        IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
        // 通过getInstance构建SecurityManager对象
        SecurityManager securityManager = securityManagerFactory.getInstance();
        // 将SecurityManager对象交给Shiro来管理
        SecurityUtils.setSecurityManager(securityManager);
        // 通过SecurityUtils获取subject主体对象
        Subject subject = SecurityUtils.getSubject();
        // 实例化UsernamePasswordToken
        UsernamePasswordToken token = new UsernamePasswordToken("jack","123");
        // 认证(登录)
        subject.login(token);

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

        System.out.println("是否有删除video的权限:" +subject.isPermitted("video:delete"));

        System.out.println("是否有root角色:" +subject.hasRole("root"));

    }
}

4.3 快速上手之Shiro内置JdbcRealm实战

  • 有两种实现方式:一个是通过.ini配置文件的方式,一种是通过代码 的方式实现
  • 通过代码的方式实现:
    • 第一步:创建数据库表:由于使用的是Shiro内置的JdbcRealm,因此表的名称和字段都需要根据官方的来。
    • 有三张表分别是users、user_role、role_permission
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;

  • 第二步:编写Java测试代码
public class JdbcRealmQuickStartTest {


    @Test
    public void jdbcRealmTest(){
        // 1.定义数据源
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql:///xdclass_shiro?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("root");

        // 2.定义jdbcRealm
        JdbcRealm jdbcRealm = new JdbcRealm();
        // 当使用Shiro的JdbcRealm时,如果想要让Shiro通过数据库查询权限信息,
        // 必须设置PermissionsLookupEnabled=true,默认为false.这是个坑!!!
        jdbcRealm.setPermissionsLookupEnabled(true);
        jdbcRealm.setDataSource(druidDataSource);

        // 实例化DefaultSecurityManager对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // 将jdbcRealm设置到DefaultSecurityManager中
        securityManager.setRealm(jdbcRealm);
        // 将DefaultSecurityManager交给Shiro管理
        SecurityUtils.setSecurityManager(securityManager);

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

        UsernamePasswordToken token = new UsernamePasswordToken("jack", "123");
        // 登录
        subject.login(token);

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

        System.out.println("当前主体的用户名:" + subject.getPrincipal());

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

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

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

        // 必须将PermissionsLookupEnabled设置为true(默认false),否则数据库中的权限是查不到的。 这算是个坑把!!!
        System.out.println("是否具有video:find权限(数据库中是有的)" + subject.isPermitted("video:find"));

    }
}
  • 方式二:使用.ini配置文件方式
  • 第一步:创建由Shiro提供的users、user_role、role_permission三张表
  • 第二步: 创建jdbcrealm.ini配置文件
# 声明Realm类型
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm

# 配置数据源
dataSource=com.alibaba.druid.pool.DruidDataSource

# mysql-connector-java 5 用的驱动是:com.mysql.jdbc.Driver
# mysql-connector-java 5 之后用的驱动是:com.mysql.cj.jdbc.Driver
dataSource.driverClassName=com.mysql.cj.jdbc.Driver

# 声明datasource的url
dataSource.url=jdbc:mysql:///xdclass_shiro?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false

# 声明数据源的账号和密码
dataSource.username=root
dataSource.password=root

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

# 开启去数据库查找权限,默认为false。
jdbcRealm.permissionsLookupEnabled=true

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

  • 第三步:测试代码
@Test
    public void jdbcRealmByIniTest(){

        // 实例化DefaultSecurityManager对象
        IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory("classpath:jdbcrealm.ini");
        SecurityManager securityManager = securityManagerFactory.getInstance();

        // 将DefaultSecurityManager交给Shiro管理
        SecurityUtils.setSecurityManager(securityManager);

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

        UsernamePasswordToken token = new UsernamePasswordToken("jack", "123");
        // 登录
        subject.login(token);

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

        System.out.println("当前主体的用户名:" + subject.getPrincipal());

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

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

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

        // 必须将PermissionsLookupEnabled设置为true(默认false),否则数据库中的权限是查不到的。 这算是个坑把!!!
        System.out.println("是否具有video:find权限(数据库中是有的)" + subject.isPermitted("video:find"));

    }

4.4 Shiro 自定义Realm实战

  • 步骤

    • 创建一个类,继承AuthorizingRealm
    • 重写授权方法 doGetAuthorizationInfo(PrincipalCollection principalCollection)
    • 重写认证方法doGetAuthenticationInfo(AuthenticationToken authenticationToken)
  • 方法

    • 当用户登陆的时候会调用doGetAuthenticationInfo
    • 进行权限校验的时候会调用doGetAuthorizationInfo
  • 对象介绍

    • UsernamePasswordToken:对应就是Shiro的token,其中有Principal和Credential

      • Principal:用户名
      • Credential:密码
    • SimpleAuthenticationInfo:代表该用户的认证信息

    • SimpleAuthorizationInfo:代表用户角色权限信息

  • 实战代码

/**
 * 自定义Realm
 */
public class CustomeRealm extends AuthorizingRealm {

    // 模拟从DB中获取用户信息
    private final Map<String,Object> userMap = new HashMap<>();
    {
        userMap.put("jack","123");
        userMap.put("xdclass","123");
    }

    // 模拟从DB中获取角色信息
    private final Map<String, Set<String>> rolesMap = new HashMap<>();
    {
        Set<String> set1 = new HashSet<>();
        set1.add("role1");
        set1.add("role2");

        Set<String> set2 = new HashSet<>();
        set2.add("admin");

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

    // 模拟从DB中获取权限信息
    private final Map<String, Set<String>> permissionsMap = new HashMap<>();
    {
        Set<String> set1 = new HashSet<>();
        set1.add("user:select");

        Set<String> set2 = new HashSet<>();
        set2.add("user:*");

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

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("授权操作:  doGetAuthenticationInfo()");
        String username = (String) principalCollection.getPrimaryPrincipal();

        Set<String> roles = rolesMap.get(username);
        Set<String> permissions = permissionsMap.get(username);
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRoles(roles);
        simpleAuthorizationInfo.addStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("认证操作:  doGetAuthenticationInfo()");
        String username = (String) authenticationToken.getPrincipal();
        String pwd = getPwdByNameFromDB(username);
        if (pwd == null || "".equals(pwd)) {
            return null;
        }
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username,pwd,this.getName());
        return simpleAuthenticationInfo;
    }

    private String getPwdByNameFromDB(String username) {
        return (String) userMap.get(username);
    }
}
  • 测试代码:
public class CustomeRealmTest {

    @Test
    public void customeRealmTest(){
        // 1.实例化DefaultSecurityManager
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        // 2.将自定义的realm与DefaultSecurityManager关联
        defaultSecurityManager.setRealm(new CustomeRealm());
        // 3.将defaultSecurityManager交给Shiro管理
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        // 4.实例化UsernamePasswordToken
        UsernamePasswordToken token = new UsernamePasswordToken("jack","123");
        // 5.通过SecurityUtils获取主体对象
        Subject subject = SecurityUtils.getSubject();
        // 6.通过主体对象调用登录功能
        subject.login(token);

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

        System.out.println("是否有user:delete操作权限" + subject.isPermitted(" user:select "));

		System.out.println("是否有admin角色" + subject.hasRole(" admin "));
    }
}

4.5 深入Shiro源码解读认证授权流程

  • 认证流程解读:
subject.login(usernamePasswordToken); 
DelegatingSubject->login() 
DefaultSecurityManager->login() 
AuthenticatingSecurityManager->authenticate()
AbstractAuthenticator->authenticate() 
ModularRealmAuthenticator->doAuthenticate() 
ModularRealmAuthenticator->doSingleRealmAuthentication() 
AuthenticatingRealm->getAuthenticationInfo()
  • 授权流程解读:
subject.checkRole("admin")
DelegatingSubject->checkRole()
AuthorizingSecurityManager->checkRole() 
ModularRealmAuthorizer->checkRole() 
AuthorizingRealm->hasRole() 
AuthorizingRealm->doGetAuthorizationInfo()

五、Shiro权限认证Web案例知识点讲解

5.1 Shiro内置的过滤器

  • 核心过滤器类:DefaultFilter,配置哪个路径对应哪个拦截器进行处理
  • authc:org.apache.shiro.web.filter.authc.FormAuthenticationFilter
    • 需要认证登录才能访问
  • user:org.apache.shiro.web.filter.authc.UserFilter
    • 用户拦截器,表示必须存在用户
  • anno: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
  • post:org.apache.shiro.web.filter.authz.PortFilter
    • 端口拦截器,可通过的端口。
  • ssl:org.apache.shiro.web.filter.authz.SslFilter
    • ssl拦截器,只有请求协议是https才能通过。

5.2 Shiro的Filter配置路径讲解

  • /admin/video/user/pub
  • 路径支持通配符 ?,*,**,注意通配符匹配不包括目录分隔符/
  • * 可以匹配所有,不加*可以进行前缀匹配,但多个冒号就需要多个*来匹配‘
  • :匹配一个字符,如/usr?/user3匹配,但/user/不匹配
  • *:匹配零个或多个字符串,如/add*/addtest匹配,但/usr/1不匹配
  • **:匹配路径中的零个或多个路径,如/user/**/user/xx/user/xxx/yyy匹配
  • 在Shiro中,URL权限采取第一次匹配优先的方式
    • 如/user/**=filter1
    • /user/add=filter2
    • 请求/user/add 命中的是filter1拦截器

5.3 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("xx"))
        hashedCredentialsMatcher.setHashIterations(2);
        return hashedCredentialsMatcher;
    }

5.4 Shiro权限控制注解和编程方式讲解

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:已经通过身份认证或者通过"记住我"登录的。
  • 编程方式

Subject subject = SecurityUtils.getSubject(); 
//基于角色判断 
if(subject.hasRole(“admin”)) { 
//有角色,有权限 
} else {
//无角色,无权限 
}
//或者权限判断 
if(subject.isPermitted("/user/add")){ 
//有权限 
}else{
//无权限 
}

5.5 Shiro缓存模块

  • 什么是Shiro缓存
    • Shiro中提供了对认证信息和授权信息的缓存
    • 认证信息缓存默认是关闭
    • 授权信息缓存默认是开启
  • AuthenticatingRealm 和 AuthorizingRealm 分别提供了对AuthenticationInfo 和 AuthorizationInfo信息的缓存

5.6 Shiro Session模块

  • 什么是会话Session

    • 用户和程序直接的连接,程序可以根据session识别到哪个用户,和javaweb的session类似
  • 什么是会话管理器SessionManager

    • 会话管理器管理所有subject的所有操作,是Shiro的核心组件
    • 核心方法
      • Session start(SessionContext context):开启一个session
      • Session getSession(SessionKey key):根据key获取session
  • SessionDAO会话存储/持久化

    • SessionDAO是一个接口,有如下几个实现类
    • AbstractSessionDAO CachingSessionDAO EnterpriseCacheSessionDAO MemorySessionDAO
    • SessionDAO核心方法
      • Serializable create(Session session):创建
      • Session readSession(Serializable sessionId):获取
      • void update(Session session):更新
      • void delete(Session session) :删除,会话过期时会调用
      • Collection<Session> getActiveSessions():获取活跃session
  • 附属资料

RememberMe 
1、 Cookie 写到客户端并 保存 
2、 通过调用subject.login()前,设置 token.setRememberMe(true); 
3、 关闭浏览器再重新打开;会发现浏览器还是记住你的 
4、 注意点: 
- subject.isAuthenticated() 表示用户进行了身份验证登录的,即Subject.login 进行了登录 
- subject.isRemembered() 表示用户是通过RememberMe登录的 
- subject.isAuthenticated()==true,则 subject.isRemembered()==false, 两个互斥 
-  总结:特殊页面或者API调用才需要authc进行验证拦截,该拦截器会判断用户是否是通过 subject.login()登录,安全性更高,其他非核心接口或者页面则通过user拦截器处理即可

六、Shiro整合SpringBoot2.x.x案例实战

  • 技术选型:SpringBoot + Mybatis + MySQL + Redis + Shiro + jdk1.8 + Maven + 前端后分离的权限校验

  • 该项目是基于RBAC的权限控制设计

    • RBAC:Role Based Access Control
    • 主要涉及的表有:
      • 用户表
      • 角色表
      • 权限表
  • 数据库表创建脚本

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','审核人员');


# 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;

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);



# 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;


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



# 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;

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');

6.1 项目搭建和依赖引入

  • 使用Spring脚手架来搭建 SpringBoot
  • 引入依赖:
    <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.2.2</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-tomcat</artifactId>
            <scope>provided</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>

6.2 数据库连接和Mybatis配置

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql:///xdclass_shiro?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username =root
spring.datasource.password =root
# 数据源类型
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

# 开启控制台sql打印
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

# 开启驼峰命令规则
mybatis.configuration.map-underscore-to-camel-case=true

  • 在主程序中添加Mapper接口扫描注解
@MapperScan(basePackages = "net.xdclass.rbac_shiro.dao")

6.3 案例实战之用户角色权限多对多关联查询

  • 用户表实体类:
public class User {

    private Integer id;
    private String username;
    private String password;
    private String salt;
    private List<Role> roleList;
	//getter/setter省略..
}
  • 角色表实体类:
public class Role {

    private Integer id;
    private String name;
    private String description;
    private List<Permission> permissionList;
    // getter/setter省略..
    }
  • 权限表实体类:
public class Permission {

    private Integer id;
    private String name;
    private String url;
}
  • UserMapper接口
public interface UserMapper {

    /**
     * 根据用户名查询用户
     * @param username
     * @return
     */
    @Select("select * from user where username = #{username}")
    User findUserByUsername(@Param("username") String username);

    /**
     * 根据id查询用户
     * @param userId
     * @return
     */
    @Select("select * from user where id=#{userId}")
    User findUserById(@Param("userId")Integer userId);

    /**
     * 根据用户名和密码查询用户
     * @param username
     * @param password
     * @return
     */
    @Select("select * from user where username=#{username} and password=#{password}")
    User findUserByUsernameAndPassword(@Param("username") String username,@Param("password") String password);

}
  • 角色Mapper
public interface RoleMapper {


    /**
     * 根据用户id查询该用户的角色id
     * @param userId
     * @return
     */
    @Select("select r.id as id, r.name as name, r.description as description  from role r left join user_role ur on r.id = ur.role_id left join user u on u.id = ur.user_id where u.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.findPermissionsByRoleId",fetchType = FetchType.DEFAULT))
            }
    )
    List<Role> findRolesByUserId(@Param("userId") Integer userId);

}
  • PermissionMapper
public interface PermissionMapper {


    /**
     * 根据角色id查询该角色的权限
     * @param roleId
     * @return
     */
    @Select("select p.id id,p.name name, p.url as url  from permission p left join role_permission rp on p.id = rp.permission_id left join role r on r.id = rp.role_id where r.id=#{roleId}")
    List<Permission> findPermissionsByRoleId(@Param("roleId") Integer roleId);
}

6.4 自定义Realm——CustomeRealm

  • 编写一个类继承AuthorizingRealm类,并重写doGetAuthorizationInfo(PrincipalCollection principals)和doGetAuthenticationInfo(AuthenticationToken token)方法
  • doGetAuthenticationInfo(AuthenticationToken token) :认证方法
  • doGetAuthorizationInfo(PrincipalCollection principals):授权方法
public class CustomeRealm extends AuthorizingRealm {


    @Autowired
    private UserService userService;


    /**
     * 进行权限授权
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("授权方法:AuthorizationInfo.doGetAuthorizationInfo");

        String username = (String) principals.getPrimaryPrincipal();
        User user = userService.findAllUserInfoByUsername(username);
        if (user == null) {
            return null;
        }
        List<String> stringRoleList = new ArrayList<>();
        List<String> stringPermissionList = new ArrayList<>();
        for (Role role : user.getRoleList()) {
            stringRoleList.add(role.getName());
            for (Permission permission : role.getPermissionList()) {
                stringPermissionList.add(permission.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("认证方法:AuthenticationInfo.doGetAuthenticationInfo");
        String username = (String) token.getPrincipal();
        User user = userService.findSimpleUserInfoByUsername(username);
        if (user == null ) {
            return null;
        }
        if (user.getPassword() == null || user.getPassword()=="") {
            return null;
        }
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),this.getClass().getName());
        return simpleAuthenticationInfo;
    }
}

6.5 最最最关键的Shiro配置类来了

  • 配置流程如下:

  • ShiroFilterFactoryBean

    • SecurityManager
      • CustomeSessionManager
      • CustomeRealm
        • hashedCredentialsMatcher
  • SessionManager

    • DefaultSessionManager:默认实现,常用于javase
    • ServletContainerSessionManager:web环境
    • DefaultWebSessionManager:常用于自定义实现
  • 代码配置如下

@Configuration
public class ShiroConfig {

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        System.out.println("ShiroFilterFactoryBean 加载了..");

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 当访问某个资源时,该资源需要先认证,但当前用户未认证跳转的页面,
        // 若是前后端分离项目, 可以填写API的url, 若是单体项目,可以填写xxx.jsp
        shiroFilterFactoryBean.setLoginUrl("/pub/need_login");
        // 认证成功后跳转的页面, 若前后端分离,则没这个调用。
        shiroFilterFactoryBean.setSuccessUrl("/");
        // 用户已认证,但是没有权限时跳转的页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit");
        shiroFilterFactoryBean.setSecurityManager(securityManager());

        // 坑一:这里必须要设置成LinkedHashMap,不能设置为HashMap,因为HashMap是无序的

        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();

        // 常用的默认拦截器
        // 退出拦截器
        filterChainDefinitionMap.put("/logout","logout");

        // 游客可以访问的拦截器
        filterChainDefinitionMap.put("/pub/**","anon");

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

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

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

        // 防止漏洞, 一般最后都要加上这个拦截
        filterChainDefinitionMap.put("/**","authc");
        // 设置拦截器链
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        // 设置自定义realm
        defaultWebSecurityManager.setRealm(customeRealm());
        // 设置自定义SessionManager,如果不是前后端分离, 就不必设置下面的sessionManager
        defaultWebSecurityManager.setSessionManager(customeSessionManager());
        return defaultWebSecurityManager;
    }


    @Bean
    public CustomeRealm customeRealm(){
        CustomeRealm customeRealm = new CustomeRealm();
        customeRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return customeRealm;
    }

    @Bean
    public CustomeSessionManager customeSessionManager(){
        CustomeSessionManager customeSessionManager = new CustomeSessionManager();

        // 超时时间,默认为30分钟 参数的单位是毫秒。
        customeSessionManager.setGlobalSessionTimeout(20000);
        return customeSessionManager;
    }

    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 设置散列次数, 好比散列2次, 相当于md5(md5(xx)
        hashedCredentialsMatcher.setHashIterations(2);
        // 设置散列算法, 这里使用md5
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        return hashedCredentialsMatcher;
    }
}

6.6 前后端分离之自定义SessionManager

  • 编写一个类 继承DefaultWebSessionManager,重写getSessionId(ServletRequest request, ServletResponse response)方法
  • 重写的getSessionId()可以参考DefaultWebSessionManager类的getSessionId(ServletRequest request, ServletResponse response)
public class CustomeSessionManager extends DefaultWebSessionManager {

    private static final String AUTHORIZATION = "token";


    public CustomeSessionManager(){
        super();
    }


    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {

        String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        if (sessionId != null) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
                    ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);

            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
            //automatically mark it valid here.  If it is invalid, the
            //onUnknownSession method below will be invoked and we'll remove the attribute at that time.
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return sessionId;
        } else {
           return super.getSessionId(request,response);
        }
    }
}

6.7 编写相关路径的接口进行测试

  • localhost:8080/pub/login :对应ShiroConfig类中的 filterChainDefinitionMap.put("/pub/**","anon");表示任何人都可以访问
  • localhost:8080/authc/video/play_record :对应ShiroConfig类的filterChainDefinitionMap.put("/authc/**","authc");表示只有登录才可以进行访问
  • localhost:8080/admin/video/order:对应ShiroConfig类的filterChainDefinitionMap.put("/admin/**","roles[admin]");表示只有admin角色的才可以访问。
  • localhost:8080/video/update:对应ShiroConfig类中配置的filterChainDefinitionMap.put("/video/update","perms[video_update]");表示只有编辑权限的才可以访问

6.8 使用Shiro的Logout和加密处理

  • Shiro自带logout功能,因此只需要访问ip:端口号/logout,并携带请求头token既可以实现退出功能。
  • 加密处理步骤:
    • customeRealm中设置setCredentialsMatcher
  @Bean
    public CustomeRealm customeRealm(){
        CustomeRealm customeRealm = new CustomeRealm();
        customeRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return customeRealm;
    }
+ 通过Shiro自带的`SimpleHash`进行加密对应密码:存入到数据库表中,Shiro会自动校验密码是否正确。
@Test public void testMD5(){ 
//加密算法 
String hashName = "md5"; 
//密码明文 
String pwd = "123"; 
//加密函数,使用shiro自带的 
Object result = new SimpleHash(hashName, pwd, null, 2); 
System.out.println(result);
}

七、权限控制综合案例实战进阶

7.1 自定义Shiro Filter过滤器

  • 使用背景:

    • admin/order=roles["admin,"root"],表示访问/admin/order这个接口需要同时具备admin和root角色才可以进行访问。
    • 原因是因为:RolesAuthorizationFilter类的isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)方法中调用的是 hasAllRoles方法
    • 这显然是不合理的。拥有root角色的用户自然拥有admin角色的一切权限
  • 需求

    • 订单信息,可以由角色管理员admin或者超级管理员root查看
    • 只需要具备其中一个角色即可

代码实现步骤:
+ 自定义一个类,实现AuthorizationFilter过滤器,重写isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)方法
+ isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)方法的重写参考RolesAuthorizationFilter类的isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
+ 在Shiro配置类中的shiroFilterFactoryBean配置

     	Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
        filterChainDefinitionMap.put("/admin/**","roleOrFilter[admin,root]");
        shiroFilterFactoryBean.setFilters(filterMap);

7.2 性能提升之Redis整合CacheManager

  • 业务场景:授权的时候每次都去查数据库,频繁调用数据库会影响性能,因此使用缓存。
  • 步骤:
    • 引入redis依赖
<dependency> 
	<groupId>org.crazycake</groupId> 
	<artifactId>shiro-redis</artifactId> 
	<version>3.1.0</version> 
</dependency>
  • 对SecurityManager 配置cacheManager
 defaultWebSecurityManager.setCacheManager(redisCacheManager());

 public RedisManager redisManager(){
        RedisManager redisManager = new RedisManager();
        redisManager.setHost("localhost");
        redisManager.setPort(6379);
        return redisManager;
    }

    public RedisCacheManager redisCacheManager(){
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        // 默认30分钟,可以自定义设置过期时间,单位为秒。
        redisCacheManager.setExpire(20);
        return redisCacheManager;
    }
  • reids安装

这里会有一个坑:在redis安装完成启动并且启动项目之后访问报错:

class java.lang.String must has getter for field: authCacheKey or id\nWe need a field to identify this Cache Object in Redis. So you need to defined an id field which you can get unique id to identify this principal. For example, if you use UserInfo as Principal class, the id field maybe userId, userName, email, etc. For example, getUserId(), getUserName(), getEmail(), etc.\nDefault value is authCacheKey or id, that means your principal object has a method called \"getAuthCacheKey()\" or \"getId()\""

解决方案:改造原有的逻辑,修改缓存的唯一key
	doGetAuthorizationInfo方法中:
	原有: 
		String username = (String)principals.getPrimaryPrincipal(); 
		User user = userService.findAllUserInfoByUsername(username);
	改为 
		User newUser = (User)principals.getPrimaryPrincipal(); 
		User user = userService.findAllUserInfoByUsername(newUser.getUsername());

	doGetAuthenticationInfo方法 
		原有: 
			return new SimpleAuthenticationInfo(username, user.getPassword(), this.getClass().getName()); 
		改为:
			return new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getName());

7.3 性能提升之Redis整合SessionManager

  • 背景原因:为什么要持久化Session?
    • 重启应用,使得用户无感知,重启之后可以继续保持原先的状态进行操作。
    • 持久化步骤:
//配置session持久化 
customSessionManager.setSessionDAO(redisSessionDAO()); 
/** * 自定义session持久化 * @return */ 
public RedisSessionDAO redisSessionDAO(){ 
	RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); 
	redisSessionDAO.setRedisManager(getRedisManager()); 
	return redisSessionDAO;
}
  • **注意:**PO对象需要实现序列化接口Serializable
  • logout接口和以前一样调用,只是调用之后会删除redis里对应的key,即删除对应的token。

7.4 Shiro常用Bean类配置

  • LifecycleBeanPostProcessor
    • 作用:管理shiro一些bean的生命周期 即bean初始化 与销毁
@Bean 
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { 
	return new LifecycleBeanPostProcessor(); 
	}
  • AuthorizationAttributeSourceAdvisor
    • 作用:使得注解生效(例如 @RequiresGuest),不加入的话注解不生效。
@Bean 
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
	AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); 
	authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); 
	return authorizationAttributeSourceAdvisor;
}
  • DefaultAdvisorAutoProxyCreator
    • 作用:用来扫描上下文寻找所有的Advistor(通知器), 将符合条件的Advisor应用到切入点的Bean中,需要在LifecycleBeanPostProcessor创建后才可以创建
@Bean 
@DependsOn("lifecycleBeanPostProcessor") 
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){ 
	DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new 
	DefaultAdvisorAutoProxyCreator(); 
	defaultAdvisorAutoProxyCreator.setUsePrefix(true); 
	return defaultAdvisorAutoProxyCreator; 
}

八、分布式应用的鉴权方式

  • UUID
  • 分布式Session
  • JWT :https://www.cnblogs.com/cjsblog/p/9277677.html
  • Oauth2.0:https://www.cnblogs.com/flashsun/p/7424071.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值