第三节 Shiro对加密的支持

一、用代码谈加密

        相信你已经知道,在实际项目中,用户的密码在数据库中一定是密文。比如你的CSDN帐号的密码是123456,但是在CSDN的数据库表中,可能存放的就是类似"e389b243168cd658ccae899240bf8321"这样的一段密文。

        为了快速上手,我们需要知道一些加密的算法。常见的加密算法有MD5。该如何使用呢?Shiro提供了一套加密API。请看下面代码的演示。

@Test
public void testPassword(){
    String password = "123456";
    Md5Hash md5Hash = new Md5Hash(password);
    //打印结果:e10adc3949ba59abbe56e057f20f883e
    System.out.println(md5Hash);
}

        你的密码123456,被加密成了e10adc3949ba59abbe56e057f20f883e。你觉得已经很安全了吗?很不幸,MD5算法可能会被破解。一般进行加密时最好提供一个salt(盐),我们可以加一些只有系统知道的干扰数据,这个盐值可以随意自定,比如加上你的名字(即盐)。这样散列的对象是“密码+ 盐”,这样生成的散列值相对来说更难破解。

        好的,既然要加干扰数据,那么就来点干脆的,加上独一无二的名字好了。此时盐(salt)值就是 csdnName,将其作为Md5Hash的构造参数一起传递进去。

    @Test
    public void testPassword2(){
        String password = "123456";
        String csdnName = "小大宇";
        //第二个参数就是 盐salt
        Md5Hash md5Hash = new Md5Hash(password,csdnName);
        //打印结果:6e588f84bc14b24845f9d2859665e4e5
        System.out.println(md5Hash);
    }

        通常情况下,为了保险起见,还会进行循环加密。这里将加密后的“6e588f84bc14b24845f9d2859665e4e5”视为明文,用之前相同的加密手段,再循环加密3次,那么这个密码的破解难度可想而知。

    @Test
    public void testPassword3(){
        String password = "123456";
        String csdnName = "小大宇";
        Md5Hash md5Hash = new Md5Hash(password,csdnName, 3);
        //打印结果:e36e406088fcbe05c70ec1ab33bffa64
        System.out.println(md5Hash);
    }

二、加密在Realm中应该怎么用

        首先,毋庸置疑的是,在你的真实项目中,插入用户密码的时候,需要先进行加密处理,再插入数据库的表。在验证用户密码的时候,再使用相同的加密算法计算用户输入的密码。

        好的,那我们的Realm应该怎么弄呢?

        首先,我们先创建出来这个Realm,依旧继承我们之前讨论的这个AuthorizingRealm类,并重写doGetAuthenticationInfo方法。

public class PasswordRealm extends AuthorizingRealm ...
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        String clientUsername = (String) token.getPrincipal();
        String yourInputPasswordFromWeb = new String((char[]) token.getCredentials());
        return null;
    }

        好了,你可以像上面一样得到用户输入的明文密码。但是应该怎么才能将其变成密文呢?

        这个时候,你要为Shiro提供你的加密算法。我们之前讨论的加密算法是:   使用 md5(密码 + 你的名字 ) 循环加密3次。所以,我们得把这个加密算法告诉我们的Realm。Shiro也提供了一个接收用户加密算法的组件。传说这个组件的名字叫:HashedCredentialsMatcher。

        OK,下一步,我们得为我们的Realm提供这个加密算法。你可以使用构造函数注入。就像下面一样。

public class PasswordRealm extends AuthorizingRealm ...

    public PasswordRealm() {
        //采用md5算法
        HashedCredentialsMatcher passwordMatcher = new HashedCredentialsMatcher("md5");
        //循环加密3次
        passwordMatcher.setHashIterations(3);
        //再将这个加密组件注入到我们的Realm中
        this.setCredentialsMatcher(passwordMatcher);
    }

         你也可以将这个加密组件的注入放在shiro.ini配置文件中,不过现在你不用关心这种方式。

         细心的你可能会发现:咦,说好的加密的是"密码 + 你的名字",那该在哪里定义这些数据呢?OK,不着急,我们看代码。

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        String clientUsername = (String) token.getPrincipal();
        String yourExpectedSalt = "小大宇";
        return new SimpleAuthenticationInfo(clientUsername, getPasswordFromDB(clientUsername),
                generateSalt(yourExpectedSalt), getName());
    }

    private String getPasswordFromDB(String clientUsername) {
        //模拟从数据库查出来的密文
        return "e36e406088fcbe05c70ec1ab33bffa64";
    }

    private ByteSource generateSalt(String salt) {
        //将你指定的盐做内部处理
        return ByteSource.Util.bytes(salt);
    }

        看到了吗,我们这次返回的凭证居然有四个参数。直接返回一个携带着相关参数的凭证info,再将其委托给Shiro去验证。程序员再次光荣的甩出任务。呵,程序员。

clientUsername:用户输入的帐号

getPasswordFromDB(clientUsername) :通过用户输入的帐号,查询数据库,返回一串密文

generateSalt(yourExpectedSalt):你指定什么作为盐呢?本篇博客用的盐就是用户的名字,于是我就定了它是“小大宇”

getName():当前Realm的名字。

return new SimpleAuthenticationInfo(clientUsername, getPasswordFromDB(clientUsername),
                                                                                                  generateSalt(yourExpectedSalt), getName());

        返回了这个info凭证以后,Shiro应该怎么处理呢?

        还记得吗,我们在这个Realm的构造器中已经定义了如何去加密明文密码。聪明的Shiro接下来会取出token中的你输入的密码,然后采用构造函数的md5算法,再取出你指定的盐,循环加密md5( passwordFromToken , YourExpectedSalt) 3次。然后会将加密的结果,与getPasswordFromDB(clientUsername)这个值进行对比。如果对比结果一致,即密码正确,那么就说明本次登录成功。否则登录失败。

        本次完整的认证流程就结束了。如果你还有点迷糊,那么下面的完整的测试代码应该能够帮助到你哦。o(∩_∩)o 

三、附上测试代码     

package shiro;

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.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

/**
 * @author jay.zhou
 * @date 2018/12/20
 * @time 23:07
 */
public class PasswordRealm extends AuthorizingRealm {

    public PasswordRealm() {
        //采用md5算法
        HashedCredentialsMatcher passwordMatcher = new HashedCredentialsMatcher("md5");
        //循环加密3次
        passwordMatcher.setHashIterations(3);
        //再将这个加密组件注入到我们的Realm中
        this.setCredentialsMatcher(passwordMatcher);
    }

    @Override
    public String getName() {
        return "PasswordRealm";
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        String clientUsername = (String) token.getPrincipal();
        String yourExpectedSalt = "小大宇";
        return new SimpleAuthenticationInfo(clientUsername, getPasswordFromDB(clientUsername),
                generateSalt(yourExpectedSalt), getName());
    }

    private String getPasswordFromDB(String clientUsername) {
        //模拟从数据库查出来的密文
        return "e36e406088fcbe05c70ec1ab33bffa64";
    }

    private ByteSource generateSalt(String salt) {
        return ByteSource.Util.bytes(salt);
    }

    //授权,暂时不谈
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
}

        Shiro配置文件 shiro.ini 

[main]
myRealm=shiro.PasswordRealm
securityManager.realms=$myRealm
    @Test
    public void testPasswordRealm() {
        //1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
        Factory factory =
                new IniSecurityManagerFactory("classpath:shiro.ini");
        //2、得到SecurityManager实例 并绑定给SecurityUtils
        org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        //3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
        Subject subject = SecurityUtils.getSubject();
        //验证密码123456是否能够登录成功
        UsernamePasswordToken token = new UsernamePasswordToken("anyString", "123456");
        try {
            //4、登录,即身份验证
            subject.login(token);
        } catch (AuthenticationException e) {
            //5、身份验证失败
            e.printStackTrace();
        }
        Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录
        //6、退出
        subject.logout();
    }

    @Test
    public void generatePassword() {
        String password = "123456";
        String csdnName = "小大宇";
        Md5Hash md5Hash = new Md5Hash(password, csdnName, 3);
        //打印结果:e36e406088fcbe05c70ec1ab33bffa64
        System.out.println(md5Hash);
    }
  
    
      junit
      junit
      4.9
    
    
      commons-logging
      commons-logging
      1.1.3
    
    
      org.apache.shiro
      shiro-core
      1.2.2
    
  

四、源码下载

        本章节项目源码:点击我下载源码 

----------------------------------------------------分割线------------------------------------------------------- 

        下一篇:第四节 Shiro权限管理

        阅读更多:跟着大宇学Shiro目录贴

你可能感兴趣的:(跟着大宇学Shiro)