【shiro】(4)---Shiro认证、授权案例讲解

Shiro认证、授权案例讲解

 

一、认证

 1、 认证流程

   

2、用户密码已经加密、加盐的用户认证

 (1)测试类

  // 用户登陆和退出,这里我自定了一个realm(开发肯定需要自定义realm获取数据库密码和权限)
    @Test
    public void testCustomRealmMd5() {

        // 创建securityManager工厂,通过ini配置文件创建securityManager工厂
        Factory<SecurityManager> factory = new IniSecurityManagerFactory(
                "classpath:shiro-realm-md5.ini");

        // 创建SecurityManager
        SecurityManager securityManager = factory.getInstance();

        // 将securityManager设置当前的运行环境中
        SecurityUtils.setSecurityManager(securityManager);

        // 从SecurityUtils里边创建一个subject
        Subject subject = SecurityUtils.getSubject();

        // 在认证提交前准备token(令牌)
        // 这里的账号和密码 将来是由用户输入进去
        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "111111");

        try {
            // 执行认证提交
            subject.login(token);
        } catch (AuthenticationException e) {
            e.printStackTrace();
        }

        // 是否认证通过
        boolean isAuthenticated = subject.isAuthenticated();

        System.out.println("加密后是否认证通过:" + isAuthenticated);
        // 退出操作
        subject.logout();
        // 是否认证通过
          isAuthenticated = subject.isAuthenticated();
      System.out.println("退出后是否认证通过:" + isAuthenticated);
    }

(2)shiro-realm-md5.ini (有关散列加密下面会举例子)

    我在ini配置了加密的规则,这个规则要和用户注册保存的密码加密规则一致。

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

#将凭证匹配器设置到realm
customRealm=com.jincou.shiro.realm.CustomRealmMd5
customRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$customRealm

(3)自定义CustomRealmMd5类

/**
 *自定义Realm特点
 *(1)继承AuthorizingRealm类
 *(2)重写AuthenticationInfo(认证)
 *     doGetAuthorizationInfo(授权)两个方法
 * 注意:
 * 1:这里password = "f3694f162729b7d0254c6e40260bf15c"肯定也是通过明文(这里指zhangsan)+盐(这里是qwerty)进行加密后的字符串
 * 2:实际应用是将盐和散列后的值存在数据库中,自动realm从数据库取出盐和加密后的值由shiro完成密码校验。
 * 3:这里要注意它们两的加密规则一定要一致,否则无法比较。
 */
public class CustomRealmMd5 extends AuthorizingRealm {

    // 设置realm的名称(任意和其它也无关联)
    @Override
    public void setName(String name) {
        super.setName("customRealmMd5");
    }

    // 用于认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {
        
         System.out.println("测试..自定义CustomRealmMd5方法里面......");
         
        // token是用户输入的
        // 第一步从token中取出身份信息
        String userCode = (String) token.getPrincipal();

        // 第二步:根据用户输入的userCode从数据库查询
        // ....

        //如果数据库查询不到zhangsan用户信息,则返回null
            /*if(user==null){
                    return null;
            }*/

        // 模拟从数据库查询到密码,散列值
        String password = "f3694f162729b7d0254c6e40260bf15c";
        // 从数据库获取salt
        String salt = "qwerty";
        //上边散列值和盐对应的明文:111111

        // 如果查询到返回认证信息AuthenticationInfo,这里的密码加密判断交给底层处理
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                userCode, password, ByteSource.Util.bytes(salt), this.getName());

        return simpleAuthenticationInfo;
    }

    // 用于授权(后面写)
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        return null;
    }
}

运行效果

结论:subject.login(token)我在提交认证,它就会去调用我自定义的CustomRealm,因为我在ini进行配置自定义CustomRealm

 3、加密算法

 因为在ini里面配置的加密规则,那我第一次用户注册的时候,肯定也需要按它的规则进行加密后,把密码保存到数据库

  /**
  * 加密算法
  */
public class MD5Test {    
    public static void main(String[] args) {     
        //原始 密码 
        String source = "111111";
        //
        String salt = "qwerty";
        
        //散列次数(和ini中credentialsMatcher.hashIterations=1一致)
        int hashIterations = 1;
        //上边散列1次:f3694f162729b7d0254c6e40260bf15c
        //上边散列2次:36f2dfa24d0a9fa97276abbe13e596fc
        
        //第一个参数:散列算法 
        //这里的散列算法是md5要和ini中credentialsMatcher.hashAlgorithmName=md5一致)
        //第二个参数:明文,原始密码 
        //第三个参数:盐,通过使用随机数
        //第四个参数:散列的次数,比如散列两次,相当 于md5(md5(''))
        SimpleHash simpleHash = new SimpleHash("md5", source, salt, hashIterations);
        System.out.println(simpleHash.toString());    
    }
}
  //这个结果:f3694f162729b7d0254c6e40260bf15c就是在自定义realm中的密文也就是该用户保存到数据库中的密文。

 

 二、授权(基于资源的授权)

 1、授权流程

 

2、授权方式

Shiro 支持三种方式的授权:
(1)编程式:通过写if/else 授权代码块完成:

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

 (2)注解式:通过在执行的Java方法上放置相应的注解完成

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

 (3)JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成

<shiro:hasRole name="admin">
<!— 有权限—>
</shiro:hasRole>

 这里授权测试使用第一种编程方式,实际与web系统集成使用后两种方式。

 3、授权测试

 (1)测试类

// 自定义realm进行资源授权测试
    @Test
    public void testAuthorizationCustomRealm() {

        // 创建SecurityManager工厂
        Factory<SecurityManager> factory = new IniSecurityManagerFactory(
                "classpath:shiro-realm.ini");

        // 创建SecurityManager
        SecurityManager securityManager = factory.getInstance();

        // 将SecurityManager设置到系统运行环境,和spring后将SecurityManager配置spring容器中,一般单例管理
        SecurityUtils.setSecurityManager(securityManager);

        // 创建subject
        Subject subject = SecurityUtils.getSubject();

        // 创建token令牌
        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan",
                "111111");

        // 执行认证
        try {
            subject.login(token);
        } catch (AuthenticationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println("认证状态:" + subject.isAuthenticated());
        // 认证通过后执行授权

        // 基于资源的授权,调用isPermitted方法会调用CustomRealm从数据库查询正确权限数据
        // isPermitted传入权限标识符,判断user:create:1是否在CustomRealm查询到权限数据之内
        boolean isPermitted = subject.isPermitted("user:create:1");
        System.out.println("单个权限判断" + isPermitted);

        boolean isPermittedAll = subject.isPermittedAll("user:create:1",
                "user:create");
        System.out.println("多个权限判断" + isPermittedAll);

        // 使用check方法进行授权,如果授权不通过会抛出异常
        subject.checkPermission("items:add:1");

    }

(2)shiro-realm.ini

[main]
#自定义 realm
customRealm=com.jincou.shiro.realm.CustomRealm
#将realm设置到securityManager
securityManager.realms=$customRealm

(3)自定义CustomRealm类

public class CustomRealm extends AuthorizingRealm {

    // 用于认证(上面写过)
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {
        return null;
    }

    // 用于授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        
        //从 principals获取主身份信息
        //将getPrimaryPrincipal方法返回值转为真实身份类型(在上边的doGetAuthenticationInfo认证通过填充到SimpleAuthenticationInfo中身份类型),
        String userCode =  (String) principals.getPrimaryPrincipal();
        
        //根据身份信息获取权限信息
        //连接数据库...
        //模拟从数据库获取到数据
        List<String> permissions = new ArrayList<String>();
        permissions.add("user:create");//用户的创建
        permissions.add("items:add");//商品添加权限
        //....
        
        //查到权限数据,返回授权信息(要包括 上边的permissions)
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //将上边查询到授权信息填充到simpleAuthorizationInfo对象中
        simpleAuthorizationInfo.addStringPermissions(permissions);

        return simpleAuthorizationInfo;
    }
}

运行结果:

 

 想太多,做太少,中间的落差就是烦恼。想没有烦恼,要么别想,要么多做。少校【5】

 

posted on 2018-06-07 21:33  雨点的名字  阅读(1191)  评论(2编辑  收藏  举报