学习Shiro笔记——入门

Shiro

介绍

​ 它是一个功能强大且易于使用的Java安全框架,可以执行身份验证、授权、加密和会话管理。使用Shiro易于理解的API,您可以快速且轻松地保护任何应用程序——从最小的移动应用程序到最大的web和企业应用程序。

核心概念

1、subject(主题):代表了正在操作的当前“用户”,这个用户并不一定是人也也可以指第三方进程、守护进程帐户或任何类似的东西。一旦您获得了主题,您就可以立即访问当前用户使用Shiro想要做的90%的事情,比如登录、注销、访问他们的会话、执行授权检查等。

2、securityManager(安全管理器):它是shiro的核心,所有与安全有关的操作都会与securityManager进行交互。

3、realm(数据域):充当Shiro和应用程序安全数据之间的“桥梁”,当实际需要与与安全相关的数据(如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro会从一个或多个为应用程序配置的领域中查找这些内容。realm可以有多个,但是至少得有一个。Shiro提供了开箱即用的realm,可以连接到许多安全数据源(即目录),如LDAP、关系数据库(JDBC)、文本配置源(如INI)和属性文件,等等。如果默认的realm不满足您的需求,可以添加您自己的领域实现来表示自定义数据源。

环境

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

以下例子 都是模拟的数据,并不是真正的数据库数据,和其他整合还需要其他的依赖,请前往官网查看

shiro 官网

ini配置

ini 配置文件类似于 Java 中的 properties(key=value),不过提供了将 key/value 分类的特性,key 是每个部分不重复即可,而不是整个配置文件。如下是 INI 配置分类:

  • [main] 部分

提供了对根对象 securityManager 及其依赖对象的配置。

  • [users] 部分

配置用户名 / 密码及其角色,格式:“用户名 = 密码,角色 1,角色 2”,角色部分可省略。

  • [roles] 部分

配置角色及权限之间的关系,格式:“角色 = 权限 1,权限 2”

  • [urls] 部分

配置 url 及相应的拦截器之间的关系,格式:“url = 拦截器 [参数],拦截器 [参数]

例子:

[main]
\#提供了对根对象securityManager及其依赖的配置
securityManager=org.apache.shiro.mgt.DefaultSecurityManager
…………
securityManager.realms=$jdbcRealm
[users]
\#提供了对用户/密码及其角色的配置,用户名=密码,角色1,角色2
username=password,role1,role2
[roles]
\#提供了角色及权限之间关系的配置,角色=权限1,权限2
role1=permission1,permission2
[urls]
\#用于web,提供了对web url拦截相关的配置,url=拦截器[参数],拦截器
/index.html = anon
/admin/** = authc, roles[admin], perms["permission1"]

认证

1、认证流程

1.1、首先调用 Subject.login(token)进行登录,其会自动委托给 Security Manager,调用之前必 须通过 SecurityUtils. setSecurityManager()设置。

1.2、SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;

1.3、Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,开发者可以自 定义插入自己的实现;

​ 1.4、Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;

1.5、Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返 回/抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进 行访问。

​ 1.6、最后可以调用 subject.logout 退出,其会自动委托给 SecurityManager.logout 方法退出。

2、例子(没有使用ini 配置)

​ 2.1、创建自定义realm

public class UserRealm extends AuthenticatingRealm {

    //模拟service 层
    private UserService userService =new UserService();
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //从token 中获取用户名
       String username= (String) authenticationToken.getPrincipal();
        System.out.println("用户名: "+username);
        //从数据库中获得user
        User user = userService.findUserByUsername(username);
        //如果存在该用户 才会进行认证
        if(null!=user ){
            AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),"");
            //认证成功
            return info;
        }
        //返回空  认证失败
        return null;
    }
}

如果只是认证接需要继承 AuthenticatingRealm

AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),"");

第一个参数 可以是任意类型 设置之后 可以通过subject.getPrincipal() 获得这个参数

第二个参数 原密码,数据库查询出的密码

它还有第三、第四个参数,在后面加密用例讲到

​ 2.2、创建测试类

public class Shiro {
    public static void main(String[] args) {
        String username="xhb";
        String password="123456";
        Factory<SecurityManager> factory = new IniSecurityManagerFactory();
        //得到securityManager  安全管理器
        DefaultSecurityManager securityManager= (DefaultSecurityManager) factory.getInstance();
        //将自定义的realm 给 securityManager 管理
        securityManager.setRealm(new UserRealm());
        //得到SecurityManager实例 并绑定给SecurityUtils
        SecurityUtils.setSecurityManager(securityManager);
        //得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证
        Subject subject = SecurityUtils.getSubject();
        //创建token
        UsernamePasswordToken token=new UsernamePasswordToken(username,password);
        //认证
       try {
           subject.login(token);
           System.out.println("登录成功");
       }catch (UnknownAccountException e){
           System.out.println("用户不存在");
       }catch (IncorrectCredentialsException e){
           System.out.println("密码错误");
       }
    }
}

如果身份验证失败请捕获 AuthenticationException 或其子类

  • DisabledAccountException : 禁用的帐号

  • LockedAccountException : 锁定的帐号

  • UnknownAccountException : 错误的帐号

  • ExcessiveAttemptsException : 登录失败次数过多

  • IncorrectCredentialsException : 错误的凭证

  • ExpiredCredentialsException :过期的凭证

    2.3、测试结果

    用户名: xhb
    登录成功
    

授权

1、授权流程

​ 1.1、首先调用 Subject.isPermitted*/hasRole*接口,其会委托给 SecurityManager,而 SecurityManager 接着会委托给 Authorizer

1.2、Authorizer 是真正的授权者,如果我们调用如 isPermitted(“user:view”),其首先会通过 PermissionResolver 把字符串转换成相应的 Permission 实例

1.3、在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用于匹配传入的 角色/权限

​ …

2、例子

​ 2.1、创建自定义realm

public class UserRealm extends AuthorizingRealm {

    private UserService userService =new UserService();
    private RoleService roleService = new RoleService();
    private PermissionService permissionService = new PermissionService();
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
      //认证例子中的代码
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("doGetAuthorizationInfo");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //模拟从数据库中查询 角色
        List roles=roleService.findRoles();
//        info.addRole("role1");
//        info.addRole1("role2");
        //添加多个角色
        info.addRoles(roles);
        //模拟从数据库中查询 角色
        List permissions = permissionService.findPermissions();
//        info.addStringPermission("user:query");
//        info.addStringPermission("user:update");
        //添加多个权限
        info.addStringPermissions(permissions);

        return info;
    }
}

授权需要继承 AuthorizingRealm 实现 doGetAuthorizationInfo 方法

AuthorizingRealmAuthenticatingRealm 的子类,因为只有认证了才能授权

​ 2.2、创建测试类

public class Shiro {
    public static void main(String[] args) {
		//认证中的代码。。。。
        
       boolean hasRole = subject.hasRole("role1");
           System.out.println("用户:"+username+" 拥有角色role1:"+hasRole);
       boolean hasPermission = subject.isPermitted("content:query");
           System.out.println(("用户:"+username+" 拥有角色permission:"+hasPermission));
    }
}

​ 2.3、测试结果

用户名: xhb
登录成功
doGetAuthorizationInfo
用户:xhb 拥有角色role1:true
doGetAuthorizationInfo
用户:xhb 拥有角色permission:false

在测试过程中,发现每次调用subject.hasRole("role1");subject.isPermitted("content:query");都会执行自定义 realm 中的doGetAuthorizationInfo(PrincipalCollection principalCollection) 授权的方法,而在自定义 realm 会从数据库中获得 角色列表 和 权限列表 ,这样会耗费性能,所以可以在认证的时候,就把 角色列表 和 权限列表 获取下来,因为一般登录只会认证一次

获取下来之后,创建一个对象ActiveUser包含三个属性:user数据库查询到的用户 roles 查询到的角色列表 permissions 查询到的权限列表,通过AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),salt,this.getName());的第一个参数 ,通过subject.getPrincipal()可以获得这个参数,并且也可以在doGetAuthorizationInfo(PrincipalCollection principalCollection)方法中,可以通过principalCollection.getPrimaryPrincipal()方法拿到这个参数

这样就不要每次判断 有无角色,有无权限 去查询数据库

编码加密

MD5加密
  Md5Hash md5Hash=new Md5Hash("123456","cl",2);
  System.out.println(md5Hash.toString());
// 5be1ac6418314812cc1763365618fb9f

Md5Hash md5Hash=new Md5Hash(“123456”,“cl”,2);

第一个参数 加密的字符串

第二个参数 salt 盐,用于混淆加密密码,让密文更加难以破解

第三个参数 散列次数,就是将加密的密文用同样的方法再加密

在存取用户密码的时候,一般使用密文,再shiro 认证的时候,用用户输入的原密码 和 数据库中密文比较

这个时候我们需要告诉shiro 加密规则

例子

注意:在模拟sevice 层中的获得用户 模拟的数据现在应该是已经加密的密文

​ 自定义realm

//将认证例子代码中的认证方法中
     AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),"");
//替换为
	  ByteSource salt= ByteSource.Util.bytes("cl");
      AuthenticationInfo info = new  		   			SimpleAuthenticationInfo(user,user.getPassword(),salt,this.getName());

继续上面没说完的参数解释

AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),salt,this.getName());

第三个参数 salt 盐,用户密码加密 中用的盐

第四个参数 realmName 自定义realm 的名称

​ 创建测试类

public class Shiro {
    public static void main(String[] args) {
        String username="xhb";
        String password="123456";
        Factory<SecurityManager> factory = new IniSecurityManagerFactory();
        //得到securityManager  安全管理器
        DefaultSecurityManager securityManager= (DefaultSecurityManager) factory.getInstance();
        //创建自定义realm
        UserRealm userRealm=new UserRealm();
        *******************************重  要******************************************
        //创建凭证匹配器
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        //设置加密方式
        matcher.setHashAlgorithmName("md5");
        //设置散列次数
        matcher.setHashIterations(2);
        //将凭证匹配器 注入自定义raalm
        userRealm.setCredentialsMatcher(matcher);
        *******************************************************************************
        //将自定义realm 注入安全管理器
        securityManager.setRealm(userRealm);
        //得到SecurityManager实例 并绑定给SecurityUtils
        SecurityUtils.setSecurityManager(securityManager);
        //得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证
        Subject subject = SecurityUtils.getSubject();
        //创建token
        UsernamePasswordToken token=new UsernamePasswordToken(username,password);
        //认证
       try {
           subject.login(token);
           System.out.println("登录成功");
           User user = (User) subject.getPrincipal();
           System.out.println(user.toString());
       }catch (UnknownAccountException e){
           System.out.println("用户不存在");
       }catch (IncorrectCredentialsException e){
           System.out.println("密码错误");
       }
        .....
    }
}

​ 测试结果

用户名: xhb
登录成功
User{username='xhb', password='5be1ac6418314812cc1763365618fb9f'}//数据库中的user
用户:xhb 拥有角色role1:true
用户:xhb 拥有角色permission:false

这样就完成了shiro 的基础入门
理解有限,有错的地方希望提出0.0

参考文档

参考文档

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值