shiro入门

x33g5p2x  于2021-11-09 转载在 其他  
字(14.5k)|赞(0)|评价(0)|浏览(201)

一、shiro入门

1.什么是shiro

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。作为一款安全框架Shiro的设计相当巧妙。Shiro的应用不依赖任何容器,它不仅可以在JavaEE下使用,还可以应用在JavaSE环境中。

2.功能简介

  • Authentication:身份认证/登录(账号密码验证)
  • Authorization:授权,即角色或者权限验证
  • Session Manager:会话管理,用户登录后的session相关管理
  • Cryptography:加密,密码加密等
  • Web Support:Web支持,集成Web环境
  • Caching:缓存,用户信息、角色、权限等缓存到如redis等缓存中
  • Concurrency:多线程并发验证,在一个线程中开启另一个线程,可以把权限自动传播过去
  • Testing:测试支持
  • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问
  • Remember Me:记住我,登录后,下次再来的话不用登录了

记住一点,Shiro不会去维护用户、维护权限;这些需要我们自己去设计提供;然后通过相应的接口注入给Shiro即可。

3.Shiro核心架构

**Subject:**即主体,外部应用与subject进行交互,subject记录了当前的操作用户,将用户的概念理解为当前操作的主体。外部程序通过subject进行认证授权,而subject是通过SecurityManager安全管理器进行认证授权

**SecurityManager:**即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等

SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。

**Authenticator:**即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器

**Authrizer:**即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限;

**Realm:**即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息
不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码

**SessionManager:**即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录

**SessionDAO:**即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库

**CacheManager:**即缓存管理,将用户权限数据存储在缓存,这样可以提高性能

**Cryptography:**即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。

二、shiro认证

1.什么是认证

身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确

2.三大概念

Subject:访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体

Principal:身份信息,是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)

credential:凭证信息,是只有主体自己知道的安全信息,如密码、证书等

3.认证流程

  • 首先调用 Subject.login(token) 进行登录,其会自动委托给 SecurityManager
  • SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;
  • Authenticator 才是真正的身份验证者,Shiro API 中核心的身份 认证入口点,此处可以自定义插入自己的实现;
  • Authenticator 可能会委托给相应的 AuthenticationStrategy 进 行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
  • Authenticator 会把相应的 token 传入 Realm,从 Realm 获取 身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处 可以配置多个Realm,将按照相应的顺序及策略进行访问

4.认证的实现

(1)创建一个springboot项目,导入依赖

<dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.0</version>
        </dependency>
    </dependencies>

(2)引入shiro配置文件shiro.ini,并加入以下配置

# 约定写法
[users]
# 用户名=密码
christy=123456
tide=654321

shiro的配置文件是一个.ini文件,类似于.txt文件

.ini文件经常用作某些软件的特定的配置文件,可以支持一些复杂的数据格式,shiro可以按照内部约定的某种格式读取配置文件中的数据

之所以提供这个配置文件是用来学习shiro时书写我们系统中相关的权限数据,从而减轻配置数据库并从数据库读取数据的压力,降低学习成本,提高学习效率

(3)java代码

@Test
    public void testAuth(){
        // 1、创建安全管理器对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // 2、给安全管理器设置realm
        securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
        // 3、给全局安全工具类SecurityUtils设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        // 4、拿到当前的subject
        Subject subject = SecurityUtils.getSubject();
        // 5、创建令牌
        AuthenticationToken token = new UsernamePasswordToken("christy","123456");

        try {
            // 6、用户认证
            System.out.println("认证状态:"+subject.isAuthenticated());
            subject.login(token);
            System.out.println("认证状态:"+subject.isAuthenticated());
        } catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("认证失败:用户不存在!");
        } catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("认证失败:密码不正确!");
        } catch (Exception e){
            e.printStackTrace();
        }

    }

打印结果:
    认证状态:false
	认证状态:true

5.认证的几种状态

  • UnknownAccountException:用户名错误
  • IncorrectCredentialsException:密码错误
  • DisabledAccountException:账号被禁用
  • LockedAccountException:账号被锁定
  • ExcessiveAttemptsException:登录失败次数过多
  • ExpiredCredentialsException:凭证过期

三、Shiro自定义Realm实现认证

1.编写自定义realm的代码:

package com.mye.hl21shrio.shiro.realm;

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

/** * 自定义realm */
public class CustomerRealm extends AuthorizingRealm {
    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 在token中获取用户名
        String principal = (String) token.getPrincipal();
        System.out.println(principal);
        // 模拟根据身份信息从数据库查询
        if("christy".equals(principal)){
            // 参数说明:用户名 | 密码 | 当前realm的名字
            return new SimpleAuthenticationInfo(principal,"123456", this.getName());
        }

        return null;
    }
}

2.编写测试类

/** * 测试自定义realm进行认证 */
    @Test
    public void testCustomerRealmAuthenticator(){
        //1.创建安全管理对象
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //2.设置自定义realm
        defaultSecurityManager.setRealm(new CustomerRealm());
        //3.给全局安全工具类SecurityUtils设置安全管理器
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //4.通过安全工具类获取subject
        Subject subject = SecurityUtils.getSubject();
        //5.创建token
        UsernamePasswordToken token = new UsernamePasswordToken("christy", "123456");
        try {
            //6.登录认证
            subject.login(token);
            System.out.println("认证状态:" + subject.isAuthenticated());
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("用户名不存在");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("密码不正确");
        }
    }

3.测试结果

四、Shiro的加密和随机盐

1.Shiro中密码的加密策略

实际应用中用户的密码并不是明文存储在数据库中的,而是采用一种加密算法将密码加密后存储在数据库中。Shiro中提供了一整套的加密算法,并且提供了随机盐。shiro使用指定的加密算法将用户密码和随机盐进行加密,并按照指定的散列次数将散列后的密码存储在数据库中。由于随机盐每个用户可以不同,这就极大的提高了密码的安全性。

2.Shiro中的加密方式

(1)编写测试类

@Test
    public void shiroMD5Test(){
        // MD5加密,无随机盐,无散列
        Md5Hash md5Hash01 = new Md5Hash("123456");
        System.out.println(md5Hash01.toHex());

        // MD5+随机盐加密,无散列
        Md5Hash md5Hash02 = new Md5Hash("123456","1q2w3e");
        System.out.println(md5Hash02.toHex());

        // MD5+随机盐加密+散列1024
        Md5Hash md5Hash03 = new Md5Hash("123456","1q2w3e",1024);
        System.out.println(md5Hash03.toHex());
    }

(2)测试结果

e10adc3949ba59abbe56e057f20f883e
9eab7472e164bb8c1b823ae960467f74
41a4e25bcf1272844e38b19047dd68a0

3.Shiro中自定义加密Realm

(1)编写自定义加密realm代码

package com.mye.hl21shrio.shiro.realm;

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

/** * 自定义加密Realm */
public class CustomerMD5Realm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 从token中获取用户名
        String principal = (String) token.getPrincipal();
        System.out.println(principal);
        // 模拟根据身份信息从数据库查询
        if("christy".equals(principal)){
            // 参数说明:用户名 | 加密后的密码 | 随机盐 | 当前realm的名字
            return new SimpleAuthenticationInfo(principal,
                    "41a4e25bcf1272844e38b19047dd68a0",
                    ByteSource.Util.bytes("1q2w3e"),
                    this.getName());
        }
        return null;
    }
}

(2)测试类

/** * 测试自定义加密realm进行认证 */
    @Test
    public void testCustomerMD5Authenticator(){
        // 1.创建SecurityManager
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        // 2.设置自定义realm
        CustomerMD5Realm realm = new CustomerMD5Realm();
        // 3.为realm设置凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 4.设置加密算法
        credentialsMatcher.setHashAlgorithmName("md5");
        // 5.设置hash次数
        credentialsMatcher.setHashIterations(1024);
        realm.setCredentialsMatcher(credentialsMatcher);
        defaultSecurityManager.setRealm(realm);
        // 设置安全工具类
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        // 通过安全工具类获取subject
        Subject subject = SecurityUtils.getSubject();
        // 创建token
        UsernamePasswordToken token = new UsernamePasswordToken("christy", "123456");
        try {
            // 登录认证
            subject.login(token);
            System.out.println("认证成功");
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用户名错误");
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            System.out.println("密码错误");
        }
    }

(3)测试结果

五、Shiro授权

1.什么是授权

授权可简单理解为who对what(which)进行How操作:

Who,即主体(Subject),主体需要访问系统中的资源。

What,即资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例。

How,权限/许可(Permission),规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。

2.授权方式

(1)基于角色的访问控制

RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制

if(subject.hasRole("admin")){
   //操作什么资源
}

(2)基于资源的访问控制

RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制

if(subject.isPermission("user:update:01")){ //资源实例
  //对01用户进行修改
}
if(subject.isPermission("user:update:*")){  //资源类型
  //对01用户进行修改
}

(3)权限字符串

  • 规则:资源标识符:操作:资源实例标识符。即对那个资源的那个实例可以进行什么操作,其默认支持通配符权限字符串

:表示资源/操作/实例的分割

,表示操作的分割

*表示任意资源/操作/实例

  • 多层次管理

  • 例如:user:query、user:edit

  • 冒号是一个特殊字符,它用来分割权限字符串的下一个部件,的第一个部分是权限被操作的领域,第二部分是被执行的操作

  • 多个值:每个部件能够保护多个值,例如user:query,edit

  • 还可以用*号代替所以值,例如user:*或者*:query,表示某个用户所有的领域都有query权限

  • 注意:通配符只能从字符串的结尾处省略部件,也就是说user:edit并不等价于user:*:edit

3.权限的编码方式

(1)编程式

Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("admin")) {
	//有权限
} else {
	//无权限
}

(2)注解式

@RequiresRoles("admin")
public void hello() {
	//有权限
}

(3)标签式

JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成:
<shiro:hasRole name="admin">
	<!— 有权限—>
</shiro:hasRole>
注意: Thymeleaf 中使用shiro需要额外集成!

4.授权的实现

(1)编写自定义realm

package com.mye.hl21shrio.shiro.realm;

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

/** * 自定义加密Realm */
public class CustomerMD5Realm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 从系统返回的身份信息集合中获取主身份信息(用户名)
        String username = (String) principalCollection.getPrimaryPrincipal();
        System.out.println("用户名: "+username);

        //根据用户名获取当前用户的角色信息,以及权限信息
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        //将数据库中查询角色信息赋值给权限对象
        simpleAuthorizationInfo.addRole("admin");
        simpleAuthorizationInfo.addRole("user");
        //simpleAuthorizationInfo.setRoles(); 这里是放一个集合

        //将数据库中查询权限信息赋值个权限对象
        simpleAuthorizationInfo.addStringPermission("user:*:01");
        simpleAuthorizationInfo.addStringPermission("product:create");

        return simpleAuthorizationInfo;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 从token中获取用户名
        String principal = (String) token.getPrincipal();
        System.out.println(principal);
        // 模拟根据身份信息从数据库查询
        if("christy".equals(principal)){
            // 参数说明:用户名 | 加密后的密码 | 随机盐 | 当前realm的名字
            return new SimpleAuthenticationInfo(principal,
                    "41a4e25bcf1272844e38b19047dd68a0",
                    ByteSource.Util.bytes("1q2w3e"),
                    this.getName());
        }
        return null;
    }
}

(2)编写测试类

@Test
    public void testCustomerMD5Authenticator2(){
        // 创建SecurityManager
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        // 设置自定义realm
        CustomerMD5Realm realm = new CustomerMD5Realm();
        // 为realm设置凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置加密算法
        credentialsMatcher.setHashAlgorithmName("md5");
        // 设置hash次数
        credentialsMatcher.setHashIterations(1024);
        realm.setCredentialsMatcher(credentialsMatcher);
        defaultSecurityManager.setRealm(realm);
        // 设置安全工具类
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        // 通过安全工具类获取subject
        Subject subject = SecurityUtils.getSubject();
        // 创建token
        UsernamePasswordToken token = new UsernamePasswordToken("christy", "123456");
        try {
            // 登录认证
            subject.login(token);
            System.out.println("认证成功");
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用户名错误");
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            System.out.println("密码错误");
        }

        //授权
        if(subject.isAuthenticated()){
            //基于角色权限控制
            System.out.println(subject.hasRole("super"));

            //基于多角色权限控制(同时具有)
            System.out.println(subject.hasAllRoles(Arrays.asList("admin", "super")));

            //是否具有其中一个角色
            boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "super", "user"));
            for (boolean aBoolean : booleans) {
                System.out.println(aBoolean);
            }

            System.out.println("==============================================");

            //基于权限字符串的访问控制 资源标识符:操作:资源类型
            System.out.println("权限:"+subject.isPermitted("user:update:01"));
            System.out.println("权限:"+subject.isPermitted("product:create:02"));

            //分别具有那些权限
            boolean[] permitted = subject.isPermitted("user:*:01", "order:*:10");
            for (boolean b : permitted) {
                System.out.println(b);
            }

            //同时具有哪些权限
            boolean permittedAll = subject.isPermittedAll("user:*:01", "product:create:01");
            System.out.println(permittedAll);
        }
    }

(3)测试结果

christy
认证成功

用户名: christy
false
用户名: christy
用户名: christy
false
用户名: christy
用户名: christy
用户名: christy
true
false
true
==============================================
用户名: christy
权限:true
用户名: christy
权限:true
用户名: christy
用户名: christy
true
false
用户名: christy
用户名: christy
true

六、Shiro中常见的过滤器

配置缩写对应的 Java 类功能例子
anonorg.apache.shiro.web.filter.authc.AnonymousFilter没有参数,指定url可以匿名访问/admins/**=anon
authcorg.apache.shiro.web.filter.authc.FormAuthenticationFilter没有参数,表示需要认证(登录)才能使用/admins/**=auth
authcBasicorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter没有参数,指定url需要basic登录,如果不通过,跳转到登录页面/admins/**=authcBasic
permsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter需要指定权限才能访问/admins/**=perms[user:add:*]
portorg.apache.shiro.web.filter.authz.PortFilter需要指定端口才能访问/admins/**=port[8081]
restorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilter将http请求方法转化成相应的动词来构造一个权限字符串,这个感觉意义不大,有兴趣自己看源码的注释/admins/**=rest[user]
rolesorg.apache.shiro.web.filter.authz.RolesAuthorizationFilter需要指定角色才能访问/admins/**=roles[admin]
sslorg.apache.shiro.web.filter.authz.SslFilter需要https请求才能访问/admins/**=ssl
userorg.apache.shiro.web.filter.authc.UserFilter需要已登录或“记住我”的用户才能访问/admins/**=user
logoutorg.apache.shiro.web.filter.authc.LogoutFilter登出过滤器,配置指定url就可以实现退出功能,非常方便/logout=logout
noSessionCreationorg.apache.shiro.web.filter.session.NoSessionCreationFilter禁止创建会话

相关文章