shiro授权与认证整合springboot实战详细

本文详细介绍了Apache Shiro的认证、授权、Session管理和缓存机制,并结合SpringBoot进行整合实践。内容涵盖Shiro的核心组件、过滤器配置、自定义Realm实现、数据加密、权限注解以及分布式环境下的Session管理。通过实例代码展示了Shiro的使用方法和配置过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

shiro授权与认证整合springboot实战详细

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

  • 什么是权限控制:
    • 忽略特别细的概念,比如权限能细分很多种,功能权限,数据权限,管理权限等
    • 理解两个概念:用户和资源,让指定的用户,只能操作指定的资源(CRUD)
  • 初学javaweb时怎么做
    • Filter接口中有一个doFilter方法,自己编写好业务Filter,并配置对哪个web资源进行拦截后
    • 如果访问的路径命中对应的Filter,则会执行doFilter()方法,然后判断是否有权限进行访问对应的资源
    • /api/user/info?id=1
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws Exception {
        HttpServletRequest httpRequest=(HttpServletRequest)request;
        HttpServletResponse httpResponse=(HttpServletResponse)response;
        
        HttpSession session=httpRequest.getSession();
        
        if(session.getAttribute("username")!=null){
            chain.doFilter(request, response);
        } else {
                  httpResponse.sendRedirect(httpRequest.getContextPath()+"/login.jsp");
        }
        
}

什么是ACL和RBAC

  • ACL: Access Control List 访问控制列表
    • 以前盛行的一种权限设计,它的核心在于用户直接和权限挂钩
    • 优点:简单易用,开发便捷
    • 缺点:用户和权限直接挂钩,导致在授予时的复杂性,比较分散,不便于管理
    • 例子:常见的文件系统权限设计, 直接给用户加权限
  • RBAC: Role Based Access Control
    • 基于角色的访问控制系统。权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限
    • 优点:简化了用户与权限的管理,通过对用户进行分类,使得角色与权限关联起来
    • 缺点:开发对比ACL相对复杂
    • 例子:基于RBAC模型的权限验证框架与应用 Apache Shiro、spring Security
  • BAT企业 ACL,一般是对报表系统,阿里的ODPS
  • 总结:不能过于复杂,规则过多,维护性和性能会下降, 更多分类 ABAC、PBAC等

二、主流权限框架介绍和技术选型

简介:介绍主流的权限框架 Apache Shiro、spring Security

  • 什么是 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 项目组中用来提供安全认证服务的框架
    
  • 什么是 Apache Shiro:官网基础介绍

    • https://github.com/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框架

  • 直达Apache Shiro官网 http://shiro.apache.org/introduction.html

    ​ http://shiro.apache.org/architecture.html

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

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eXgcdbQ1-1613568182205)()]

High-Level Overview

在这里插入图片描述

  • subject:正如我们在刚才提到教程,在Subject本质上是当前正在执行的用户的安全特定“视图”。“用户”一词通常表示一个人,一个人Subject可以是一个人,但它也可以表示第三方服务,守护程序帐户,cron作业或类似的东西-基本上是当前与该软件交互的任何东西。

    Subject实例都绑定到(并要求)SecurityManager。当您与互动时Subject,这些互动会转化为与主题相关的互动SecurityManager

  • SecurityManager:这SecurityManager是Shiro体系结构的核心,并充当一种“伞”对象,该对象协调其内部安全组件,这些安全组件一起形成对象图。但是,一旦为应用程序配置了SecurityManager及其内部对象图,通常就不用理会它了,应用程序开发人员几乎所有的时间都花在SubjectAPI上。

    稍后我们将SecurityManager详细讨论,但是重要的是要认识到,当您与进行交互时SubjectSecurityManager对于任何Subject安全操作而言,确实是幕后工作。这反映在上面的基本流程图中。

  • realm:领域充当Shiro与应用程序的安全数据之间的“桥梁”或“连接器”。当真正需要与安全性相关的数据(例如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro会从一个或多个为应用程序配置的领域中查找许多此类内容。

    从这个意义上说,领域本质上是特定于安全性的DAO:它封装了数据源的连接详细信息,并根据需要使关联数据可用于Shiro。在配置Shiro时,您必须至少指定一个领域用于身份验证和/或授权。所述SecurityManager可与多个境界被配置,但至少有一个是必需的。

    Shiro提供了开箱即用的领域,可以连接到许多安全数据源(又名目录),例如LDAP,关系数据库(JDBC),文本配置源(例如INI和属性文件)等。如果默认的Realms不能满足您的需求,那么您可以插入自己的Realm实现以表示自定义数据源。

    像其他内部组件一样,ShiroSecurityManager管理着如何使用领域来获取要表示为Subject实例的安全性和身份数据。

Detailed Architecture

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SXw7zDWm-1613568182221)()]

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

四、Springboot2.x整合 Apache Shiro快速上手

引入依赖:

<!--引入 springboot-->
        <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>
<!--shiro依赖-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
  <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

五、数据库表设计

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J2Q6PBmn-1613568182229)()]

user:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZqR48XQ2-1613568182234)()]

role:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kDPiaeMY-1613568182240)()]

permission:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NRikHE8f-1613568182244)()]

user_role:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wbwDIBoz-1613568182247)()]

role_permission:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0hbumWtS-1613568182249)()]

六、shiro常用api

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

//获取subject名
subject.getPrincipal()

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

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

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

shiro测试认证测试:

/**
 * @author zb
 * @date 2021/1/31 13:44
 * @Description: @Before @Test @After @AfterClass  shiro授权与认证 简单测试
 */
public class ShiroApplicationTest {

    private SimpleAccountRealm accountRealm = new SimpleAccountRealm();

    private DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();

    @Before
    public void init(){
        // 初始化数据源
        accountRealm.addAccount("zhangsan","123","root","admin");
        accountRealm.addAccount("lisi","456","user");
        // 构建环境
        defaultSecurityManager.setRealm(accountRealm);
    }

    @Test
    public void testAuthentication(){

        SecurityUtils.setSecurityManager(defaultSecurityManager);
        // 获取当前主体
        Subject subject = SecurityUtils.getSubject();
        // 输入账号和密码
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "123");
        // 登录认证
        subject.login(usernamePasswordToken);

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

        // 是否有对应的角色
        boolean hasRole = subject.hasRole("root");
        System.out.println("hasRole = " + hasRole);

        // 登录名
        Object principal = subject.getPrincipal();
        System.out.println("principal.toString() = " + principal.toString());

        // 退出登录
        subject.logout();
        System.out.println("subject.isAuthenticated() = " + subject.isAuthenticated());

}

七、 Shiro安全数据来源之Realm讲解

shiro默认自带的realm和常见使用方法

  • realm作用:Shiro 从 Realm 获取安全数据
  • 默认自带的realm:idae查看realm继承关系,有默认实现和自定义继承的realm
  • 两个概念
    • principal : 主体的标示,可以有多个,但是需要具有唯一性,常见的有用户名,手机号,邮箱等
    • credential:凭证, 一般就是密码
    • 所以一般我们说 principal + credential 就账号 + 密码
  • 开发中,往往是自定义realm , 即集成 AuthorizingRealm

apache Shiro 自定义Realm实战

  • 步骤:

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

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

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

      UsernamePasswordToken-》HostAuthenticationToken-》AuthenticationToken
      
    • SimpleAuthorizationInfo:代表用户角色权限信息

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

public class CustomRealm extends AuthorizingRealm {

    @Autowired
    private  UserService userService;

    /**
     *  用户校验 【权限】 的时候会调用该方法
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>> 进入到授权");
        // 获取到身份信息
        Object primaryPrincipal = principalCollection.getPrimaryPrincipal();
        User newUser = (User) primaryPrincipal;
        UserVo userVo = userService.findAllUserInfoByUsername(newUser.getUsername());
        List<RoleVo> roleList = userVo.getRoleList();
        // 从数据库中获取到[权限]
        Set<String> permissions = new HashSet<>();
        // 从数据获取【角色】
        Set<String> roles = new HashSet<>(roleList.size());

        roleList.forEach(roleVo -> {

            List<Permission> permissionList = roleVo.getPermissionList();
            String roleName = roleVo.getName();
            roles.add(roleName);

            permissionList.forEach(permission -> {
                String name = permission.getName();
                permissions.add(name);
            });

        });


        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setStringPermissions(permissions);
        simpleAuthorizationInfo.setRoles(roles);

        return simpleAuthorizationInfo;
    }

    /**
     * 用户登录 的时候回调用此方法
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 进入到认证");
        // 身份信息
        Object principal = authenticationToken.getPrincipal();
        String username = (String) principal;
        // 从数据库获取密码
        User user = userService.findUserInfoByUsername(username);
//        UserVo userVo = userService.findAllUserInfoByUsername(username);
        // 获取到密码
        String password = user.getPassword();
        if (StrUtil.isEmpty(password)){
            return null;
        }

        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,password, this.getName());


        return simpleAuthenticationInfo;
    }


}

配置:

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

        // 使用加密方式验证 登录[设置加密方式]
        customRealm.setCredentialsMatcher(hashedCredentialsMatcher());

        return customRealm;
    }

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

Shiro认证和授权流程的源码,和断点测试

认证流程:subject.login(usernamePasswordToken);
	DelegatingSubject->login()
	DefaultSecurityManager->login()
	AuthenticatingSecurityManager->authenticate()
	AbstractAuthenticator->authenticate()
	ModularRealmAuthenticator->doAuthenticate()
	ModularRealmAuthenticator->doSingleRealmAuthentication()
	AuthenticatingRealm->getAuthenticationInfo()
	
	补充:密码验证方法 AuthenticatingRealm-> assertCredentialsMatch()
授权流程:subject.checkRole("admin")

	DelegatingSubject->checkRole()
	AuthorizingSecurityManager->checkRole()
	ModularRealmAuthorizer->checkRole()
	AuthorizingRealm->hasRole()
	AuthorizingRealm->doGetAuthorizationInfo()

九、Shiro权限认证Web

shiro内置的过滤器

  • 核心过滤器类: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: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才能通过。

十、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拦截器

十一、自定filter过滤器

/**
 * @author zb
 * @date 2021/2/11 15:00
 * @Description:
 *  ("/admin/**", "roles[admin,root]");  自定义过滤器,满足当用户拥有 admin或root 即可访问 /admin
 */
public class CustomRolesOrAuthorizationFilter extends AuthorizationFilter {

    /**
     *  {@link org.apache.shiro.web.filter.mgt.DefaultFilter}
     *  {@link RolesAuthorizationFilter}
     * @param request
     * @param response
     * @param mappedValue
     * @return
     * @throws Exception
     */
    @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) {
            // 没有角色限制,则就可以直接访问
            return true;
        }
        Set<String> roles = CollectionUtils.asSet(rolesArray);
        // 若有其中一个角色即可访问
        for (int i = 0; i < rolesArray.length; i++) {
            if (subject.hasRole(rolesArray[i])) {
                return true;
            }
        }
        return false;
    }
}

配置:

  ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 自定义过滤器
        Map<String, Filter> filtersMap = new LinkedHashMap<>();
        filtersMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
        // shiroFilterFactoryBean绑定自定义过滤器
        shiroFilterFactoryBean.setFilters(filtersMap);

十二、Shiro 数据安全之数据加解密

数据安全核心知识,介绍常见的处理办法,Shiro 里的 CredentialsMatcher使用

  • 什么是散列算法
    • 一般叫hash,简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数,适合存储密码,比如MD5
  • 什么是salt(盐) 667788——》aabbcc
    • 如果直接通过散列函数得到加密数据,容易被对应解密网站暴力破解,一般会在应用程序里面加特殊的自动进行处理,比如用户id,例子:加密数据 = MD5(明文密码+用户id), 破解难度会更大,也可以使用多重散列,比如多次md5
 /**
     * 设置密码 加密相关
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 设置加密方式
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        // 设置hash次数
        hashedCredentialsMatcher.setHashIterations(2);

        return hashedCredentialsMatcher;
    }
/**
     * 加密测试
     * @param args
     */
    public static void main(String[] args) {
        String hashName = "md5";
        String password = "123456";
        //                                       加密方式  加密密码   盐       hash次数
        SimpleHash simpleHash = new SimpleHash(hashName, password, null, 2);
        System.out.println("simpleHash = " + simpleHash);
    }

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

讲解权限角色控制 @RequiresRoles, @RequiresPermissions等注解的使用和编程式控制

配置文件的方式

 @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 需要登录的接口,如果访问某个接口,【需要登录却没登录】,则调用此接口(如果不是前后端分离,则跳转页面)
        shiroFilterFactoryBean.setLoginUrl("/pub/need_login");
        // 登录成功,跳转Url,如果前后端分离,则没这个调用
        shiroFilterFactoryBean.setSuccessUrl("/");
        // 没有权限,未授权就会调用此方法,先验证登录--》 再验证是否有权限
        shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit");

        // 自定义过滤器
        Map<String, Filter> filtersMap = new LinkedHashMap<>();
        filtersMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
        // shiroFilterFactoryBean绑定自定义过滤器
        shiroFilterFactoryBean.setFilters(filtersMap);

        // 默认过滤器 【org.apache.shiro.web.filter.mgt  DefaultFilter】
        // 拦截器路径,部门路径无法进行拦截,时有时无;是因为同学使用的hashmap,无序的,应该改为LinkHashMap
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 退出过滤器
        filterChainDefinitionMap.put("/logout", "logout");
        // 匿名可以访问,也就是游客模式
        filterChainDefinitionMap.put("/pub/**", "anon");
        // 需要登录才可以访问
        filterChainDefinitionMap.put("/authc/**", "authc");
        // 需要管理员 角色才可以访问                此处配置意思为:用户同时拥有 admin和root 权限才可以访问
        filterChainDefinitionMap.put("/admin/**", "roles[admin,root]");
        // 有编辑权限才可以访问
        filterChainDefinitionMap.put("/video/update", "perms[video_update]");

        // 自定义过滤器        filterChainDefinitionMap.put("/admin/**","roleOrFilter[admin,root]");
        // 过滤链是顺序执行,从上而下,一般讲 /** 放到最下面

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

        return shiroFilterFactoryBean;
    }

注解方式

  • @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

    • 身份验证或者通过记 住我登录的

十四、shiro缓存

  • shiro缓存

    • shiro中提供了对认证信息和授权信息的缓存。

      默认是关闭认证信息缓存的,对于授权信息的缓存shiro默认开启的(因为授权的数据量大

  • AuthenticatingRealm 及 AuthorizingRealm 分别提供了对AuthenticationInfo 和 AuthorizationInfo 信息的缓存。

十五、Shiro Session模块

Shiro Session模块作用和SessionManager

  • 什么是会话session

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

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

    • 核心方法:

    • //开启一个session
      Session start(SessionContext context);
      //指定Key获取session
      Session getSession(SessionKey key)
      
    • shiro中的会话管理器有多个实现

  • SessionDao 会话存储/持久化

    • SessionDAO AbstractSessionDAO CachingSessionDAO EnterpriseCacheSessionDAO MemorySessionDAO

    • 核心方法

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

    附属资料:

     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整合springboot配置ShiroFilterFactoryBean

  • shiroFilterFactoryBean-》
    • SecurityManager-》
      • CustomSessionManager
      • CustomRealm-》hashedCredentialsMatcher
  • SessionManager
    • DefaultSessionManager: 默认实现,常用于javase
    • ServletContainerSessionManager: web环境
    • DefaultWebSessionManager:常用于自定义实现

在这里插入图片描述

shiro整合springboot配置文件

package cn.mesmile.shiro.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.session.mgt.eis.SessionDAO;
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;

/**
 * @author zb
 * @date 2021/2/7 17:58
 * @Description:
 */
@Configuration
public class MyShiroConfig {

    /**
     * 配置流程和思路
     *
     * shiroFilterFactoryBean-》
     *      SecurityManager-》
     *              CustomSessionManager
     *              CustomRealm-》hashedCredentialsMatcher
     * SessionManager
     *
     *  DefaultSessionManager: 默认实现,常用于javase
     *  ServletContainerSessionManager: web环境
     *  DefaultWebSessionManager:常用于自定义实现
     */


    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){


        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        shiroFilterFactoryBean.setSecurityManager(securityManager);


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

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

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

        // 自定义过滤器
        Map<String, Filter> filtersMap = new LinkedHashMap<>();
        filtersMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
        // shiroFilterFactoryBean绑定自定义过滤器
        shiroFilterFactoryBean.setFilters(filtersMap);

        // 默认过滤器 【org.apache.shiro.web.filter.mgt  DefaultFilter】
        // 拦截器路径,部门路径无法进行拦截,时有时无;是因为同学使用的hashmap,无序的,应该改为LinkHashMap
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 退出过滤器
        filterChainDefinitionMap.put("/logout", "logout");
        // 匿名可以访问,也就是游客模式
        filterChainDefinitionMap.put("/pub/**", "anon");
        // 需要登录才可以访问
        filterChainDefinitionMap.put("/authc/**", "authc");
        // 需要管理员 角色才可以访问                此处配置意思为:用户同时拥有 admin和root 权限才可以访问
        filterChainDefinitionMap.put("/admin/**", "roles[admin,root]");
        // 有编辑权限才可以访问
        filterChainDefinitionMap.put("/video/update", "perms[video_update]");


        // 自定义过滤器
        filterChainDefinitionMap.put("/admin/**","roleOrFilter[admin,root]");

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

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


        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }


    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        // 如果不是前后端分离,就不用设置 sessionManager
        securityManager.setSessionManager(sessionManager());

        // redis 整合shiro
        securityManager.setCacheManager(redisCacheManager());

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

        return securityManager;
    }


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

        // 使用加密方式验证 登录
        customRealm.setCredentialsMatcher(hashedCredentialsMatcher());

        return customRealm;
    }


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

        // 设置session过期时间,默认 30 分钟,单位是 毫秒
        customSessionManager.setGlobalSessionTimeout(30*60*1000);

        // 使用 Redis 自定义sessionDao 来持久化session
        customSessionManager.setSessionDAO(redisSessionDAO());

        return customSessionManager;
    }

    /**
     * 设置密码 加密相关
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 设置加密方式
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        // 设置hash次数
        hashedCredentialsMatcher.setHashIterations(2);

        return hashedCredentialsMatcher;
    }

    /**
     * redis 整合shiro
     * @return
     */
    @Bean
    public RedisManager getRedisManager(){
        RedisManager redisManager = new RedisManager();
        redisManager.setHost("81.69.43.78");
        redisManager.setPassword("root");
        redisManager.setPort(6379);

        return redisManager;
    }

    @Bean
    public RedisCacheManager redisCacheManager(){
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        // 设置过期时间,默认过期时间为 30 分钟,单位秒
        redisCacheManager.setExpire(1800);
        redisCacheManager.setRedisManager(getRedisManager());
        return redisCacheManager;
    }

    /**
     * 自定义 Session 的持久化
     * @return
     */
    @Bean
    public RedisSessionDAO redisSessionDAO(){
        // 实体类需要实现 Serializable 序列化接口
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        // 设置默认的超期时间,默认 30 分钟,单位秒
        redisSessionDAO.setExpire(1800);

        redisSessionDAO.setRedisManager(getRedisManager());

        // 自定义sessionId 生成
        redisSessionDAO.setSessionIdGenerator(new CustomSessionIdGenerator());

        return redisSessionDAO;
    }

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

    /**
     * 加入注解的使用,不加入这个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;
    }

}

十七、Redis整合CacheManager

引入maven文件:

 <!-- shiro+redis缓存插件 -->
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.1.0</version>
        </dependency>

配置文件:

/**
     * redis 整合shiro
     * @return
     */
    @Bean
    public RedisManager getRedisManager(){
        RedisManager redisManager = new RedisManager();
        redisManager.setHost("81.69.43.78");
        redisManager.setPassword("root");
        redisManager.setPort(6379);

        return redisManager;
    }

    @Bean
    public RedisCacheManager redisCacheManager(){
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        // 设置过期时间,默认过期时间为 30 分钟,单位秒
        redisCacheManager.setExpire(1800);
        redisCacheManager.setRedisManager(getRedisManager());
        return redisCacheManager;
    }

十八、Redis整合SessionManager以及自定义SessionManager

Redis整合SessionManager,管理Session会话

  • 为啥session也要持久化?

    • 重启应用,用户无感知,可以继续以原先的状态
     @Bean
        public SessionManager sessionManager(){
            CustomSessionManager customSessionManager = new CustomSessionManager();
    
            // 设置session过期时间,默认 30 分钟,单位是 毫秒
            customSessionManager.setGlobalSessionTimeout(30*60*1000);
    
            // 使用 Redis 自定义sessionDao 来持久化session
            customSessionManager.setSessionDAO(redisSessionDAO());
    
            return customSessionManager;
        }
    
    /**
         * 自定义 Session 的持久化
         * @return
         */
        @Bean
        public RedisSessionDAO redisSessionDAO(){
            // 实体类需要实现 Serializable 序列化接口
            RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
            // 设置默认的超期时间,默认 30 分钟,单位秒
            redisSessionDAO.setExpire(1800);
    
            redisSessionDAO.setRedisManager(getRedisManager());
    
            // 自定义sessionId 生成
            redisSessionDAO.setSessionIdGenerator(new CustomSessionIdGenerator());
    
            return redisSessionDAO;
        }
    

    注意点:

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

自定义SessionManager

/**
 * @author zb
 * @date 2021/2/7 19:04
 * @Description:
 * * SessionManager
 *      *
 *      *  DefaultSessionManager: 默认实现,常用于javase
 *      *  ServletContainerSessionManager: web环境
 *      *  DefaultWebSessionManager:常用于自定义实现
 */
public class CustomSessionManager extends DefaultWebSessionManager {

    /**
     *  自定义:前端请求传递过滤的请求头名称
     */
    private static final String AUTHORIZATION = "token";

    public CustomSessionManager(){
        super();
    }

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

        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        // 自定义请求头
        String sessionId = httpServletRequest.getHeader(AUTHORIZATION);

        if(StrUtil.isNotEmpty(sessionId)){            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);

            //在此处自动将其标记为有效。如果无效,则
            //下面的onUnknownSession方法将被调用,届时我们将删除该属性。
         request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);         request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return sessionId;
        }else {
            return super.getSessionId(request, response);
        }
    }
}

十九、自定义sessionId


/**
 * @author zb
 * @date 2021/2/12 17:16
 * @Description: 自定义 SessionId
 */
public class CustomSessionIdGenerator implements SessionIdGenerator {
    @Override
    public Serializable generateId(Session session) {
        String s = IdUtil.fastUUID();
        return "super"+ s;
    }
}

二十、ShiroConfig常用bean类配置

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登录与退出

登录:

 @PostMapping("/pub/login")
    public Map<String,Object> login(@RequestBody UserQueryDTO userQueryDTO, HttpServletRequest request, HttpServletResponse response){

        // 获取当前主体
        Subject subject = SecurityUtils.getSubject();
        // 输入账号和密码
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userQueryDTO.getUsername(),  userQueryDTO.getPassword());

        // 设置 rememberMe
        usernamePasswordToken.setRememberMe(true);

        // 登录认证
        subject.login(usernamePasswordToken);

        Serializable sessionId = subject.getSession().getId();

        List<String> strings = new ArrayList<>(Arrays.asList("1", "2", "3",sessionId.toString()));
        Map<String, Object> map = new HashMap<>();
        map.put("code",200);
        map.put("message","访问首页成功");
        map.put("data",strings);
        return map;
    }

退出直接访问 /logout 即可:

ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

  // 默认过滤器 【org.apache.shiro.web.filter.mgt  DefaultFilter】
        // 拦截器路径,部门路径无法进行拦截,时有时无;是因为使用的hashmap,无序的,应该改为LinkHashMap
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 退出过滤器
        filterChainDefinitionMap.put("/logout", "logout");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        // 请求头带上token

二十二、分布式应用的鉴权方式

单体应用到分布式应用下的鉴权方式

电商项目:商品服务,支付服务,用户服务,订单服务…

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值