shiro快速入门

来源:互联网 发布:关于套路的网络用语 编辑:程序博客网 时间:2024/06/05 22:50
前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,role2holien=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#设置自定义的realmcustomRealm=shiro_authenc.CustomRealm#将凭证匹配器设置到realm中customRealm.credentialsMatcher=$credentialsMatchersecurityManager.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的整合了,不能再拖拖拉拉了,加油...