shiro快速入门

前2天写了web权限管理,那么实际开发中是如何实现权利管理的呢?下面一起来学习一下。

传统方案:通过设置拦截器,基于url的方式进行管理,创建一个user类,用于存储menus,把user存储到session中到前端进行菜单动态显示,而user类的permissions集合用于url拦截,有对应权限才放行。这种方式实现简单,但是不易于维护。

新方案:使用shiro权限管理框架

什么是shiro?
Shiro是apache麾下的 开源java安全框架 ,提供了认证、授权、加密、会话管理、与Web集成、缓存等功能,与此类似的框架还有spring security。

shiro功能图


subject:主体,可以是用户也可以是程序,主体要访问系统,系统需要对主体进行认证、授权。
securityManager:安全管理器,主体进行认证和授权都是通过securityManager进行,是shiro的核心。
authenticator:认证器,主体进行认证最终通过authenticator进行的。
authorizer:授权器,主体进行授权最终通过authorizer进行的。
sessionManager:web应用中一般是用web容器对session进行管理,shiro也提供一套session管理的方式。
SessionDao:通过SessionDao管理session数据,针对个性化的session数据存储需要使用sessionDao。
cache Manager:缓存管理器,主要对session和授权数据进行缓存,比如将授权数据通过cacheManager进行缓存管理,和ehcache整合对缓存数据进行管理。
realm:域,领域,相当于数据源,通过realm存取认证、授权相关数据。


shiro架构图


subject:主体,可以是用户也可以是程序,主体要访问系统,系统需要对主体进行认证、授权。

securityManager:安全管理器,主体进行认证和授权都是通过securityManager进行,是shiro的核心

authenticator:认证器,主体进行认证最终通过authenticator进行的。

authorizer:授权器,主体进行授权最终通过authorizer进行的。

sessionManagerweb应用中一般是用web容器对session进行管理,shiro也提供一套session管理的方式。

SessionDao:通过SessionDao管理session数据,针对个性化的session数据存储需要使用sessionDao

cache Manager:缓存管理器,主要对session和授权数据进行缓存,比如将授权数据通过cacheManager进行缓存管理,和ehcache整合对缓存数据进行管理。

realm:域,领域,相当于数据源,通过realm存取认证、授权相关数据。

示例1

下面是一个认证和授权的入门例子,只需要一个shiro-core的jar包和它所依赖的slf4j-api.jar、commons-logging、

commons-beanutils.jar、hamcrest-core.jar以及Junit,使用maven依赖的话,只要添加shiro-core、commons-logging和Junit就行了,

我使用的是shiro1.3.2。


下面这个例子所用到的数据都在shiro-data-from-ini.ini文件里,模拟数据库的数据,一般是测试用的,真正的项目肯定是自定义realm,然后到数据库中去获取用户信息和权限信息的,例子中对主要步骤都有注释。

shiro-data-from-ini.ini

#模拟用户数据源,帐号=密码,角色,角色...(有realm时不再起作用)
[users]
pens=123,role1,role2
holien=123,role3

#设置角色、权限和资源(有realm时不再起作用)
#格式:角色=资源:操作:实例,资源:操作 相当于 资源:操作:*
[roles]
#角色role1对资源user拥有create、update权限
role1=user:create,user:update
#角色role2对资源user拥有create、delete权限
role2=user:delete
#角色role3对资源user拥有create权限
role3=user:create

#以上的数据在没有设置realm时才起作用

测试类:AuthcAndAuthzByIniData.class

package shiro_authenc;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;

import java.util.Arrays;
import java.util.List;

/**
 * writer: holien
 * Time: 2017-08-19 20:30
 * Intent: 使用ini文件中测试数据作为数据源进行身份认证和授权
 */
public class AuthcAndAuthzByIniData {
    @Test
    public void testAuthcAndAuthozByRealm() {
        /*
            身份认证
         */
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-data-from-ini.ini");
        SecurityManager securityManager = factory.getInstance();
        // 把安全管理器与安全工具关联起来
        SecurityUtils.setSecurityManager(securityManager);
        // 模拟用户表单发送过来的帐号密码,默认名称必须是username和password,可以在配置文件更改
        String username = "pens";
        String password = "123";
        // 创建一个口令(用户输入的帐号密码),帐号信息与ini文件中的帐号信息匹配即可认证成功
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        // 获取用户主体
        Subject subject = SecurityUtils.getSubject();
        try {
            // 登录,即身份认证(另起一个线程执行此方法),到ini文件中去对比用户信息
            subject.login(token);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("是否认证:" + subject.isAuthenticated());
        // 退出后再判断,无论认证还是授权都是false
//        subject.logout();
//        System.out.println("是否认证:" + subject.isAuthenticated());

        /*
            基于角色的授权
        */
        // 验证是否具有指定角色
        System.out.println("是否具有角色role1:" + subject.hasRole("role1"));
        // 验证是否具有其中某个角色(这里我觉得使用多个hasRole方法进行逻辑判断比较直观)
        List<String> list = Arrays.asList("role1", "role2");
        boolean[] b = subject.hasRoles(list);
        System.out.println(b[0] + " " + b[1]);
        // 验证是否具有某组角色,需要多个角色一起拥有
        System.out.println("是否同时具有角色role1和role2:" + subject.hasAllRoles(Arrays.asList("role1", "role2")));

        /*
            基于资源的授权
        */
        System.out.println("是否具有对用户的创建权限:" + subject.isPermitted("user:create"));
        System.out.println("是否同时具有对用户的创建、更新、删除权限:" +
                subject.isPermittedAll("user:create", "user:update", "user:delete"));
        // 与isPermission不同,checkPermisson如果检测不到相应的权限会抛异常
        subject.checkPermission("user:create");
//        subject.checkPermission("user:batch");  // 抛出异常
    }
}
这里先看看shiro自带的md5加密的基本使用例子,下面会使用到

package shiro_authenc;

import org.apache.shiro.crypto.hash.Md5Hash;
import org.junit.Test;

/**
 * writer: holien
 * Time: 2017-08-20 20:38
 * Intent: shiro自带的MD5加密
 */
public class MD5Test {
    @Test
    public void testMD5() {
        String password = "123";
        // MD5加密加盐
        String salt = "110";
        // 参数1:待加密密码 参数2:盐 参数3:hash迭代次数
        Md5Hash md5Hash = new Md5Hash(password, salt, 1);
        String md5Password = md5Hash.toString();
        System.out.println(md5Password);  // 5319bf4ef8f5029ec32a4ad62a3f8eff
    }
}

示例2

下面这个例子用到了realm,一般自定义的类继承自AuthorizingRealm就可以,重写抽象类的两个方法,一个用来认证,一个用来授权,2个方法都需要到数据库或者缓存中获取相关信息,这里我也用了模拟数据,等后面整合spring时,再搭建个完整的项目。这个例子与上面的例子的区别主要就是数据获取途径的区别, 并且对密码进行了md5加盐加密。自定义realm和加密算法需要在ini文件中配置。

shiro-realm.ini

[main]
#定义凭证(密码)匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#指定散列算法
credentialsMatcher.hashAlgorithmName=md5
#散列次数
credentialsMatcher.hashIterations=1

#设置自定义的realm
customRealm=shiro_authenc.CustomRealm
#将凭证匹配器设置到realm中
customRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$customRealm

此ini文件定义了凭证匹配器,使用了md5算法,迭代1次;自定义realm类的引用配置到名为customRealm的realm,并把前面定义的凭证匹配器配置给customRealm,最后把customRealm配置到securityManager中。

自定义realm, 继承自AuthorizingRealm的CustomRealm.class


package shiro_authenc;

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;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * writer: holien
 * Time: 2017-08-20 14:47
 * Intent: 自定义realm,其中的用户信息需要到数据库中查询,在这里使用测试数据,
 *         配置了realm就不会再读取ini文件的user了
 */
public class CustomRealm extends AuthorizingRealm {

    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 认证成功后,用户身份会被存进principalCollection,我们根据主用户身份去数据库取相应的角色和权限
        String username = (String) principalCollection.getPrimaryPrincipal();
        // 模拟从数据库取出的资源权限,格式:资源:操作:实例
        ArrayList<String> permissions = new ArrayList<>();
        permissions.add("user:create");
        permissions.add("user:update");
        permissions.add("user:delete");
        // 把数据库取出的角色或权限存入info对象中,供授权器校验
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(permissions);
        info.addRoles(Arrays.asList("role1", "role2"));
        return info;
        // 若查询不到对应的角色或权限,则返回null
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 从token取出用户输入的帐号,根据此帐号去数据库取帐号,下面以userCode来模拟
        String username = (String) authenticationToken.getPrincipal();
        // 根据用户输入的帐号从数据库取出帐号,我们假设取出的帐号叫userCode
        String userCode = "pens";
        if (userCode == null || userCode.equals("")) {
            // 如果帐号不存在,返回null
            return null;
        } else {
            // 模拟根据帐号从数据库取出的散列密码(md5散列一次,盐为110),查不到返回null,查得到返回info
            String password = "5319bf4ef8f5029ec32a4ad62a3f8eff";
            // 从数据库获取盐,假设为110
            String salt = "110";
            // realm会根据此对象包含的password(数据库)与token中的password(用户输入的密码进行加盐加密)做对比,加密算法在ini文件中指定
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes(salt), this.getName());
            return info;
        }
    }
}
测试类: testAuthcAndAuthozByRealm.class
package shiro_authenc;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;

import java.util.Arrays;
import java.util.List;

/**
 * writer: holien
 * Time: 2017-08-19 20:30
 * Intent: 使用realm作为数据源进行身份认证和授权
 */
public class AuthcAndAuthzByRealm {
    @Test
    public void testAuthcAndAuthozByRealm() {
        /*
            身份认证
         */
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
        SecurityManager securityManager = factory.getInstance();
        // 把安全管理器与安全工具关联起来
        SecurityUtils.setSecurityManager(securityManager);
        // 模拟用户表单发送过来的帐号密码,默认名称必须是username和password,可以在配置文件更改
        String username = "pens";
        String password = "123";
        // 创建一个口令(用户输入的帐号密码),帐号信息与realm去数据库中获取的帐号信息相同即可认证成功
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        // 获取用户主体
        Subject subject = SecurityUtils.getSubject();
        try {
            // 登录,即身份认证(另起一个线程执行此方法),到realm中去对比用户信息
            subject.login(token);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("是否认证:" + subject.isAuthenticated());
        // 退出后再判断,无论认证还是授权都是false
//        subject.logout();
//        System.out.println("是否认证:" + subject.isAuthenticated());

        /*
            基于角色的授权
        */
        // 验证是否具有指定角色
        System.out.println("是否具有角色role1:" + subject.hasRole("role1"));
        // 验证是否具有其中某个角色(这里我觉得使用多个hasRole方法进行逻辑判断比较直观)
        List<String> list = Arrays.asList("role1", "role2");
        boolean[] b = subject.hasRoles(list);
        System.out.println(b[0] + " " + b[1]);
        // 验证是否具有某组角色,需要多个角色一起拥有
        System.out.println("是否同时具有角色role1和role2:" + subject.hasAllRoles(Arrays.asList("role1", "role2")));

        /*
            基于资源的授权
        */
        System.out.println("是否具有对用户的创建权限:" + subject.isPermitted("user:create"));
        System.out.println("是否同时具有对用户的创建、更新、删除权限:" +
                subject.isPermittedAll("user:create", "user:update", "user:delete"));
        // 与isPermission不同,checkPermisson如果检测不到相应的权限会抛异常
        subject.checkPermission("user:create");
//        subject.checkPermission("user:batch");  // 抛出异常
    }
}
其中login方法一经调用就会到 customRealm中的 doGetAuthenticationInfo方法去验证用户信息,而 hasRole方法和isPermitted等方法一经调用都会到customRealm中的 doGetAuthorizationInfo方法 去查询,这里有点耗资源,等spring整合时,使用ehcache做缓存,就不用每次都到customRealm中获取了,直接返回缓存的结果。


总结一下流程

认证流程

1、subject(主体)请求认证,调用subject.login(token)
2、SecurityManager (安全管理器)执行认证
3、SecurityManager通过ModularRealmAuthenticator进行认证。
4、ModularRealmAuthenticator将token传给realm,realm根据token中用户信息从数据库查询用户信息(包括身份和凭证)
5、realm如果查询不到用户给ModularRealmAuthenticator返回null,ModularRealmAuthenticator抛出异常(用户不存在)
6、realm如果查询到用户给ModularRealmAuthenticator返回AuthenticationInfo(认证信息)
7、ModularRealmAuthenticator拿着AuthenticationInfo(认证信息)去进行凭证(密码 )比对。如果一致则认证通过,如果不一致抛出异常(凭证错误)。

授权流程

1、对subject进行授权,调用方法isPermitted("permission串")
2、SecurityManager执行授权,通过ModularRealmAuthorizer执行授权
3、ModularRealmAuthorizer执行realm(自定义的CustomRealm)中的doGetAuthorizationInfo方法从数据库查询权限数据
4、realm从数据库查询权限数据,返回给ModularRealmAuthorizer
5、ModularRealmAuthorizer调用PermissionResolver进行权限串比对
6、如果比对后,isPermitted中"permission串"在realm查询到权限数据中,说明用户访问permission串有权限,否则 没有权限,抛出异常。


下一篇就是shiro和spring的整合了,不能再拖拖拉拉了,加油...

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值