Shiro讲解/Spring Boot整合Shiro

SpringBoot_Shiro

权限控制

权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源,不多不少。权限管理几乎出现在任何系统里面,只要有用户和密码的系统。 很多人常将“用户身份认证”、“密码加密”、“系统管理”等概念与权限管理概念混淆。

主流权限框架
  1. spring Security
    官网:https://spring.io/projects/spring-security

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

  1. Apache Shiro
    官网:https://github.com/apache/shiro

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

两者比较:

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

Apache Shiro

Shiro核心架构图、四大模块讲解
  1. 身份验证

Authentication,身份证认证,一般就是登录

  1. 授权

Authorization,给用户分配角色或者访问某些资源的权限

  1. 会话管理

Session Management, 用户的会话管理员,多数情况下是web session

  1. 加密

Cryptography, 数据加解密,比如密码加解密等

官方架构图

用户访问Shrio权限流程、常用概念讲解
  1. Subject

我们把用户或者程序称为主体(如用户,第三方服务,cron作业),主体去访问系统或者资源,可以理解为任何与系统交互的“东西”都是Subject。

  1. SecurityManager

安全管理器,Subject的认证和授权都要在安全管理器下进行

  1. Authenticator

认证器,主要负责Subject的认证

  1. Realm

数据域,Shiro和安全数据的连接器,好比jdbc连接数据库; 通过realm获取认证授权相关信息

  1. Authorizer

授权器,主要负责Subject的授权, 控制subject拥有的角色或者权限

  1. Cryptography

加解密,Shiro的包含易于使用和理解的数据加解密方法,简化了很多复杂的api

  1. Cache Manager

缓存管理器,比如认证或授权信息,通过缓存进行管理,提高性能

在这里插入图片描述

SpringBoot2.x整合Shiro权限认证项目搭建
		Maven3.2 + Jdk8 + Springboot 2.X + IDEA
		<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>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>
Shiro常用api
//是否有对应的角色
subject.hasRole("root")//获取subject名
subject.getPrincipal()//检查是否有对应的角色,无返回值,直接在SecurityManager里面进行判断
subject.checkRole("admin")//检查是否有对应的角色
subject.hasRole("admin")//退出登录
subject.logout();

Apache Shiro 自定义Realm实战

步骤:

创建一个类 CustomRealm ,继承AuthorizingRealm
重写授权方法 doGetAuthorizationInfo
重写认证方法 doGetAuthenticationInfo

方法:

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

对象介绍:

UsernamePasswordToken : 对应就是 shiro的token中有Principal和Credential

UsernamePasswordToken -> HostAuthenticationToken -> AuthenticationToken

UsernamePasswordToken继承图

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

SimpleAuthenticationInfo :代表该用户的认证信息

package top.mitday.shiro_combat.config;

import jdk.nashorn.internal.parser.Token;
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 org.springframework.beans.factory.annotation.Autowired;
import top.mitday.shiro_combat.entity.Permission;
import top.mitday.shiro_combat.entity.Role;
import top.mitday.shiro_combat.entity.User;
import top.mitday.shiro_combat.service.UserService;

import java.util.ArrayList;
import java.util.List;

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

    @Autowired
    private UserService userService;

    /**
     * 进行权限校验的时候调用
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("用户授权 doGetAuthorizationInfo");

        User newuser = (User)principals.getPrimaryPrincipal();
        User user = userService.findAllUserInfoByUsername(newuser.getUsername());

        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 permission : permissionList){
                if (permission != null){
                    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("用户登录 doGetAuthenticationInfo");

        /**
         * 从token中获取中户信息,token代表用户的输入
         */
        String username = String.valueOf(token.getPrincipal());
        User user = userService.findAllUserInfoByUsername(username);

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

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


	// controller
	@PostMapping("/login")
    public JsonData Login(@RequestBody UserQuery userQuery, HttpServletRequest request, HttpServletResponse response){

        Subject subject = SecurityUtils.getSubject();

        Map<String, Object> info = new HashMap<>();
        try{
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userQuery.getName(),userQuery.getPwd());
            subject.login(usernamePasswordToken);
            info.put("msg","登录成功");
            info.put("session_id",subject.getSession().getId());

            return JsonData.buildSuccess(info);
        }catch (Exception e){
            e.printStackTrace();

            return JsonData.buildError("账号或者密码错误!");
        }

    }
Shiro内置的Filter过滤器讲解
//核心过滤器类:DefaultFilter, 配置哪个路径对应哪个拦截器进行处理

//需要认证登录才能访问
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["admin,user"],当有多个参数时必须每个参数都通过才算通过
roles:org.apache.shiro.web.filter.authz.RolesAuthorizationFilter

//权限授权拦截器,验证用户是否拥有权限
//参数可写多个,表示需要某些权限才能通过,多个参数时写 perms["user, admin"],当有多个参数时必须每个参数都通过才算可以
perms:org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter

//httpBasic 身份验证拦截器。
authcBasic:org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter

//退出拦截器,执行后会直接跳转到shiroFilterFactoryBean.setLoginUrl(); 设置的 url
logout:org.apache.shiro.web.filter.authc.LogoutFilter

//端口拦截器, 可通过的端口。
port:org.apache.shiro.web.filter.authz.PortFilter

//ssl拦截器,只有请求协议是https才能通过。
ssl:org.apache.shiro.web.filter.authz.SslFilter
Shiro的Filter配置路径讲解

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

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

例子
/user/**=filter1
/user/add=filter2

请求 /user/add  命中的是filter1拦截器
Shiro 数据安全之数据加解密
	/**
     * 自定义HashedCredentialsMatcher
     * 密码加解密
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置散列算法 这里使用MD5算法
        credentialsMatcher.setHashAlgorithmName("md5");

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

        return credentialsMatcher;
    }
Shiro权限控制注解和编程方式讲解
配置文件的方式

使用ShiroConfig

注解方式
//需要角色 admin 和 editor两个角色 AND表示两个同时成立
@RequiresRoles(value={"admin", "editor"}, logical= Logical.AND)

//需要权限 user:add 或 user:del权限其中一个,OR是或的意思。
@RequiresPermissions (value={"user:add", "user:del"}, logical= Logical.OR)

//已经授过权,调用Subject.isAuthenticated()返回true
@RequiresAuthentication

//身份验证或者通过记 住我登录的
@RequiresUser
编程方式
Subject subject = SecurityUtils.getSubject(); 
//基于角色判断
if(subject.hasRole(“admin”)) {
	//有角色,有权限
} else {
	//无角色,无权限
	
}
//或者权限判断
if(subject.isPermitted("/user/add")){
    //有权限
}else{
    //无权限
}

常见API

subject.hasRole("xxx");
subject.isPermitted("xxx");
subject. isPermittedAll("xxxxx","yyyy");
subject.checkRole("xxx"); // 无返回值,可以认为内部使用断言的方式

Apache Shiro整合SpringBoot2.x综合案例实战

技术选型:
前后端分离的权限检验 + SpringBoot2.x + Mysql + Mybatis + Shiro + Redis + IDEA + JDK8

SpringBoot2.x项目框架和依赖搭建

项目依赖

		<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.1.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </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>

        <!-- shiro+redis缓存插件 -->
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.1.0</version>
        </dependency>
案例实战之权限相关服务接口开发

数据库配置

#==============================数据库相关配置========================================
spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mitday_shiro?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
spring.datasource.username =root
spring.datasource.password =123456
#使用阿里巴巴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
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
自定义CustomRealm实战
  1. 继承 AuthorizingRealm
  2. 重写 doGetAuthorizationInfo
  3. 重写 doGetAuthenticationInfo
package top.mitday.shiro_combat.config;

import jdk.nashorn.internal.parser.Token;
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 org.springframework.beans.factory.annotation.Autowired;
import top.mitday.shiro_combat.entity.Permission;
import top.mitday.shiro_combat.entity.Role;
import top.mitday.shiro_combat.entity.User;
import top.mitday.shiro_combat.service.UserService;

import java.util.ArrayList;
import java.util.List;

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

    @Autowired
    private UserService userService;

    /**
     * 进行权限校验的时候调用A
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("用户授权 doGetAuthorizationInfo");

        User newuser = (User)principals.getPrimaryPrincipal();
        User user = userService.findAllUserInfoByUsername(newuser.getUsername());

        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 permission : permissionList){
                if (permission != null){
                    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("用户登录 doGetAuthenticationInfo");

        /**
         * 从token中获取中户信息,token代表用户的输入
         */
        String username = String.valueOf(token.getPrincipal());
        User user = userService.findAllUserInfoByUsername(username);

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

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

项目实战之ShiroFilterFactoryBean配置实战

shiroFilterFactoryBean -> SecurityManager -> CustomSessionManager
CustomRealm -> hashedCredentialsMatcher

SessionManager
DefaultSessionManager: 默认实现,常用于javase
ServletContainerSessionManager: web环境
DefaultWebSessionManager:常用于自定义实现

package top.mitday.shiro_combat.config;

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.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){

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

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

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

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

        /**
         * 登录后没有权限,未授权就会调用此接口
         * 先验证登录 ---> 再验证是否有权限
         */
        shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit");

        // 设置自定义filter
        Map<String , Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
        shiroFilterFactoryBean.setFilters(filterMap);

        /**
         * 拦截器路径
         * 坑1:不放呢路径无法进行拦截,时有时无,因使用的是hashmap,无序的,应该为LinkedHashMap
         */
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();

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

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

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

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

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

        /**
         * authc : url定义必须通过认证才可以访问
         * anon :可以匿名访问
         * 坑2 : 过滤链是顺序执行,从上而下,一般/** 放到最下面
         */
        filterChainDefinitionMap.put("/**","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }

    /**
     * SecurityManager 对customRealm、sessionManager进行绑定
     * 因不能直接new customRealm()、new sessionManager()
     * 所以得自定义customRealm、sessionManager 并@Bean让其Spring进行加载
     * @return
     */
    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        // 如果不是前后端分离,则不必设置下面的sessionManager
        securityManager.setSessionManager(sessionManager());

        //使用自定义cacheManager
        securityManager.setCacheManager(cachManager());

        // 设置realm (推荐放到最后,不然某些情况会不生效)
        securityManager.setRealm(customRealm());

        return securityManager;
    }

    /**
     * 加载自定义CustomRealm
     * 加载好后需要对其进行加密 setCredentialsMatcher
     * @return
     */
    @Bean
    public CustomRealm customRealm(){
        CustomRealm customRealm = new CustomRealm();
        customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return customRealm;
    }

    /**
     * 自定义HashedCredentialsMatcher
     * 密码加解密
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置散列算法 这里使用MD5算法
        credentialsMatcher.setHashAlgorithmName("md5");

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

        return credentialsMatcher;
    }

    /**
     * 自定义SessionManager
     * @return
     */
    @Bean
    public SessionManager sessionManager(){

        CustomSessionManager customSessionManager = new CustomSessionManager();

        /**
         * 超时时间,默认 30 分钟  会话超时
         * 方法里面的单位是毫秒
         */
        customSessionManager.setGlobalSessionTimeout(20000);

        //配置session持久化
        customSessionManager.setSessionDAO(redisSessionDAO());

        return customSessionManager;
    }


    /**
     * 配置redisManqger
     */
    public RedisManager getRedisManager(){
        RedisManager redisManager = new RedisManager();

        redisManager.setHost("localhost");
        redisManager.setPort(6378);
        return redisManager;
    }

    /**
     * 配置具体cache实现类
     * @return
     */
    public RedisCacheManager cachManager(){
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(getRedisManager());

        // 设置过期时间 单位是秒
        redisCacheManager.setExpire(20);
        return redisCacheManager;
    }


    /**
     * 自定义session持久化
     * @return
     */
    public RedisSessionDAO redisSessionDAO(){
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(getRedisManager());

        // 设置session id生成器
        redisSessionDAO.setSessionIdGenerator(new CustomSessionIdGenerator());
        return redisSessionDAO;
    }

    /**
     * 管理shiro一些bean的生命周期 即bean初始化 与销毁
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * api controller层面
     * 加入注解的使用,不加入这个AOP注解不生效(shiro的注解 例如 @RequiresGuest)
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 用来扫描上下文寻找所有的Advistor(通知器), 将符合条件的Advisor应用到切入点的Bean中,
     * 需要在LifecycleBeanPostProcessor创建后才可以创建
     * @return
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setUsePrefix(true);
        return defaultAdvisorAutoProxyCreator;
    }
}

前后端分离自定义SessionManager验证
package top.mitday.shiro_combat.config;

import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;

public class CustomSessionManager extends DefaultWebSessionManager {

    private static final String AUTHORIZATION = "token";

    public CustomSessionManager(){
        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);
        }
    }
}

使用Shiro Logout和加密处理
@Test
    public void testMD5(){
		//加密算法
        String hashName = "md5";
		//密码明文
        String pwd = "123";
		//加密函数,使用shiro自带的
        Object result = new SimpleHash(hashName, pwd, null, 2);
        System.out.println(result);
    }
自定义Shiro Filter过滤器

知识背景:

/admin/order= roles["admin, root"]
表示 /admin/order 这个接口需要用户同时具备 admin 与 root 角色才可访问
相当于hasAllRoles() 这个判断方法

需求:

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

第一步:

package top.mitday.shiro_combat.config;

import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.Set;

/**
 * 自定义filter
 */
public class CustomRolesOrAuthorizationFilter extends AuthorizationFilter {
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {

        Subject subject = getSubject(request, response);

        // 获取当前访问路径所需要的角色集合
        String[] rolesArray = (String[]) mappedValue;

        //没有角色限制,可以直接访问
        if (rolesArray == null || rolesArray.length == 0) {
            //no roles specified, so nothing to check - allow access.
            return true;
        }

        Set<String> roles = CollectionUtils.asSet(rolesArray);

        //当前subject是roles中任意一个,则有权限访问
        for (String role : roles) {
            if (subject.hasRole(role)){
                return true;
            }
        }

        return subject.hasAllRoles(roles);
    }
}

第二步:
在Shiroconfig类的shiroFilter方法中添加

		// 设置自定义filter
        Map<String , Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
 		/**
         * 拦截器路径
         * 坑1:不放呢路径无法进行拦截,时有时无,因使用的是hashmap,无序的,应该为LinkedHashMap
         */
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
		filterChainDefinitionMap.put("/admin/**","roleOrFilter[admin,root]");
性能提升之Redis整合CacheManager

使用原因:

授权的时候每次都去查询数据库,对于频繁访问的接口,性能和响应速度比较慢,所以使用缓存

加依赖:

		<!-- shiro+redis缓存插件 -->
     	<dependency>
			<groupId>org.crazycake</groupId>
			<artifactId>shiro-redis</artifactId>
			<version>3.1.0</version>
		</dependency>
//使用自定义的cacheManager
   securityManager.setCacheManager(cacheManager());
        
    /**
     * 配置redisManager
     *
     */
    public RedisManager getRedisManager(){
        RedisManager redisManager = new RedisManager();
        redisManager.setHost("localhost");
        redisManager.setPort(6379);
        return redisManager;
    }


    /**
     * 配置具体cache实现类
     * @return
     */
    public RedisCacheManager cacheManager(){
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(getRedisManager());
         //设置过期时间,单位是秒,20s,
        redisCacheManager.setExpire(20);

        return redisCacheManager;
    }

改造原有的逻辑,修改缓存的唯一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());
性能提升之Redis整合SessionManager

为啥session也要持久化:

重启应用,用户无感知,可以继续以原先的状态继续访问

怎么持久化:

//配置session持久化
   customSessionManager.setSessionDAO(redisSessionDAO());

 	
 	/**
     * 自定义session持久化
     * @return
     */
    public RedisSessionDAO redisSessionDAO(){
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(getRedisManager());
        return redisSessionDAO;
    }

注意点:

实体类对象需要实现序列化接口 Serializable
logout接口和以前一样调用,请求logout后会删除redis里面的对应的key,即删除对应的token

ShiroConfig常用bean类配置

LifecycleBeanPostProcessor

作用:管理shiro一些bean的生命周期 即bean初始化 与销毁

@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
	return new LifecycleBeanPostProcessor();
}

AuthorizationAttributeSourceAdvisor

作用:加入注解的使用,不加入这个AOP注解不生效(shiro的注解 例如 @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;
}
分布式应用鉴权方式之Shiro整合SpringBoot下自定义SessionId

基于原先项目,实现自定义sessionid

Shiro 默认的sessionid生成 类名 SessionIdGenerator
创建一个类,实现 SessionIdGenerator 接口的方法

package top.mitday.shiro_combat.config;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;

import java.io.Serializable;
import java.util.Random;
import java.util.UUID;

/**
 * 自定义session生成
 */
public class CustomSessionIdGenerator implements SessionIdGenerator {
    @Override
    public Serializable generateId(Session session) {
        return "mitday"+UUID.randomUUID().toString().replace("-","");
    }
}

Shiro实战源码GitHub:https://github.com/GBugIT/springboot_shiro.git

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值