深入Shiro如何优雅的进行配置?

欢迎光顾我的博客

开端

好的,起因是我把最近做好的项目给一部分人进行了测试,发现大部分朋友都提出了同一个问题,你的系统权限管理是如何实现的。我只能尴尬的说一句,不好意思这部分还没开发。然而我也知道,其实对于一个项目来说,权限可以说是最主要的一部分。后端除了对权限进行处理,其实也就是提供一些业务逻辑对 CRUD 进行组合拼装。所以我打算接下来学习权限控制方面的知识并整合到我的项目中去,顺便把我的学习笔记分享给大家。

而对于权限控制的框架呢,听的最多的还是 Shiro 还有 Spring Security。Spring 的安全框架单独用在 Spring 项目中是无可挑剔的,不管是功能上还是维护方面。但是考虑到 Shiro 是一个全能性的框架,可以用在各种场合,甚至非 Web 项目中,由于它的会话独立于容器,后期学习分布式和微服务的时候也比较方便使用。~~最重要的是 Spring 官网用的也是 Shiro 的框架。~~所以还是打算学习 Shiro。

由于这部分内容比较多,我也发现了前后端项目部署那篇文章接近 1w 字导致阅读的时候不是很舒服,所以我打算把这个内容分成几个部分:初识 Shiro、配置重点、整合技巧来讲述。这篇文章是关于 Shiro 该如何配置的,面向 Spring Boot,讲一些比较基础的内容。

深入Shiro

之前我们了解了一下 Shiro 的基本功能和架构,现在我们来看看它的工作流程。

工作流程

截屏2020-08-12 下午9.00.15

拦截

Shiro 特别重要的一块就是拦截,它通过过滤器拦截所有请求,根据配置决定哪些可以认证通过,哪些不用被拦截,还有哪些没有通过认证强制跳转。我们看一段配置代码,如何通过过滤器决定谁去谁留。

@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(org.apache.shiro.mgt.SecurityManager securityManager){
   
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/user/login");
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/user/login","anon");
        filterChainDefinitionMap.put("/**", "corsAuthenticationFilter");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("corsAuthenticationFilter", corsAuthenticationFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        return shiroFilterFactoryBean;
    }

代码非常简单,首先使用 @Bean 注释将这个方法交给 Spring 来管理,名字必须是 shiroFilter,这样才能和默认配置中的名字匹配,否则 Spring 会找不到这个过滤器。在这个方法中首先 new 一个过滤器工厂对象,将获取到的 securityManager 注入。之后设置一个登陆 url,告诉 Shiro,这个页面/接口是我的登陆页面,用户角色权限的获取也都在这里。

接着就是刚刚图中的 filterChainDefinition,我们定义一个 map 来存储不被拦截的页面。每一个键值对中 key 存储 url,value 存储拦截器的名字,拦截器主要是以下几个:

拦截器名称 拦截器功能
anno 不需要授权和登录,可以匿名访问
authc 需要登录授权才能访问
logout 退出成功之后可以自定义重定向
user 需要登录授权或者登录后开启了记住我(remember me)就能访问
ssl 通过https协议才能通过
roles 角色拦截
rest rest风格拦截
port 端口拦截
perms 权限拦截
noSessionCreation 不创建会话连接器
authcBasic 基本http身份验证拦截

比较常用的四个拦截器我使用加粗表示出来了,anno 一般是最先使用的,为什么说最先使用,因为拦截器拦截的顺序是按照你 map 中加入的键值对的顺序来的。按照第一次匹配优先原则,如果一开始就被拦截,那么之后如果符合条件也不会放行。有一些公开资源,尤其是登录接口,肯定是要设置 anno 匿名的。假如说连入口的登录功能都拦截,那用户还这么使用这个系统的其他功能呢?

authc 是指需要登录或授权才能访问,这个值的 key 一般都是 “/**” ,而且写在最后。表示除了上述设置的拦截规则,对于其他所有 url 全部拦截并且需要授权才能访问。而我这里没有这么写,而是自定义了一个 corsAuthenticationFilter。从后面的代码也可以看到我又定义了一个 filterMap ,使用我自定义的一个过滤方式拦截其他所有请求。本来我也是采用 authc 的方式,但是由于前后端跨域的需要,必须要加上跨域请求头的一些参数,所以直接另写了一个过滤器类,并且在其中处理了验证通过和验证失败的处理方式。具体可见下一篇,SSM 整合 Shiro,这里不多赘述。

认证

那么拦截之后我们怎么样判断用户是什么角色,拥有哪些权限呢?这块其实我在上一篇中有所讲到,已经把源码拉出来解析过了。有需要的朋友可以再去回顾一下,我这里只讲它整个认证的流程。

  1. 获取当前的 Subject,调用 SecurityUtils.getSubject( )
  2. 测试当前的用户是否已经被认证,即是否已登陆,调用 Subject 的 isAuthenticated( )
  3. 若没有被认证,则把用户名和密码封装为 UsernamePasswordToken 对象
  4. 执行登陆:调用 Subject 的 login(AuthenticationToken) 方法
  5. 自定义 Realm 方法,从数据库中获取相应的记录,返回给 Shiro
  6. 由 Shiro 完成对密码的比对

回顾第一篇我们讲到的,第一步是从数据库中找到所有的用户。第三步是通过我们前端获取到的用户名和密码生成 token。第四步是利用生成的 token 参数传给 login 方法进行登录。登录时对用户角色和权限的验证采用了自定义 Reaml 中重写的两个方法 doGetAuthenticationInfo( ) 和 doGetAuthorizationInfo( ),还记得吧,挺容易搞混的。

机制

在 Shiro 中有许多我们曾经经常使用到的内容,比如缓存,还有一些我们不太了解的东西,比如盐值加密。我这里简要讲一讲这些机制。

MD5盐值加密

盐值加密是我以前没有听说过的知识,所以在我第一次整合 Shiro 的时候也没有加入这一部分的内容。

众所周知,用户名和密码是被保存在数据库中。可是一旦数据库发生了泄露,用户名和密码就都遭到了泄露。攻击者可以轻松的获取用户名和密码,进行操作。更大的危害是,由于现在需要注册的网站、app越来越多。用户名和密码很多时候都是相同的。一旦某处发生了泄露,则后果会慢慢的扩散。这些危害大家可以查询下近些年发生的一些安全事故,如Sony数据库泄露、网易数据库泄露、CSDN数据库泄露等,因此我们要对密码进行加密。

为何取名为盐,此盐非彼颜。盐是许许多多微小的颗粒组成的,他就像一个随机数,你猜不到有多少颗。当你给密码加了盐,再结合 MD5,就不那么容易被破解了。

盐值加密主要就是如何生成盐,我们一般使用唯一的数据来产生。比如说我的数据库中用户名是不可重复的,那么用户名就是唯一的(昵称不唯一),所以可以使用用户名生成盐。

ByteSource.Util.bytes(userName)

ByteSource 是 Shiro 提供的一个工具类,可以转换进制。其中还有一个静态子类 Util,里面的 bytes( ) 方法返回了一个 SimpleByteSource 对象,而这个对象又使用 CodecSupport 中的 toBytes( ) 方法对接收到的不同参数采取不同的加密方式。以下为源码:

protected byte[] toBytes(Object o) {
   
        if (o == null) {
   
            String msg = "Argument for byte conversion cannot be null.";
            throw new IllegalArgumentException(msg);
        } else if (o instanceof byte[]) {
   
            return (byte[])((byte[]
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值