shiro学习笔记(一)shiro实现用户认证

shiro学习笔记(一)shiro实现用户认证

认证

​ 用户认证,就是将用户输入的用户名和密码与数据库中的用户名和密码进行比较,来判断一个用户是否是一个合法的用户。

shiro中认证用到的方法和关键对象

  • Subject:主体

    • 访问系统的用户,可以是用户也可以是程序,需要进行认证的都可以成为主体。
  • Principal:身份信息

    • 是主体进行身份认证的标识,标识必须唯一,如用户名、身份证号、邮箱地址等。一个主体可以有多个身份但是必须有一个主身份。
  • credential:凭证信息

    • 只有主体知道的安全信息,如密码、加密规则等。
  • SecurityManager:安全管理器,对Subject进行安全管理,通过SecurityManager可以完成Subject的认证、授权。

  • Realm:相当于数据源,使用SecurityManager进行用户认证,需要通过realm过去用户权限数据,如果用户身份数据在数据库,那么realm就需要从数据库获取用户身份信息。

    • 注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。
  • Authenticator:认证器:对用户身份进行认证,Authenticator是一个接口,用户可以自定义类实现Authenticator接口中的doGetAuthenticationInfo()方法,来实现认证操作。

  • HashedCredentialsMatcher:hash凭证匹配器,可以设置密码加密规则

    • public class HashedCredentialsMatcher extends SimpleCredentialsMatcher {
          //设置密码的加密规则
      	public void setHashAlgorithmName(String hashAlgorithmName) {
              this.hashAlgorithm = hashAlgorithmName;
          }
          //设置将密码进行hash处理的次数
          public void setHashIterations(int hashIterations) {
              if (hashIterations < 1) {
                  this.hashIterations = 1;
              } else {
                  this.hashIterations = hashIterations;
              }
          }
          
      }
      
    • HashedCredentialsMatcher有6个子类,可以相应设置不同的加密规则:

      • Md5CredentialsMatcher
      • Sha521CredentialsMathcer
      • Md2CredentialsMatcher
      • Sha1CredentialsMathcer
      • Sha256CredentialsMathcer
      • Sha384CredentialsMatcher

shiro中的认证流程

  1. 首先调用Subject.login(token)方法进行登录,然后shiro会自动委托给SecurityManager,调用之前需要通过SecurityUtils.setSecurityManager()设置。
  2. SecurityManager负责身份验证逻辑,并交给Authenticator进行身份验证。
  3. Authenticator执行身份验证,shiro API中核心的身份认证入口点,可以自定义类实现。
  4. Authenticator交给相应的AuthenticationSteategy进行多Realm身份验证,默认ModlarRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证。
  5. Authenticator会把相应的token传入Realm,congRealm获取身份验证信息,如果没有没有返回,或抛出异常表示身份验证失败,可以配置多个Realm,将按照相应的顺序及策略进行访问。
    • 当抛出UnknownAccountException异常时,表示用户名错误。

    • 当抛出IncorrectCredentialsException异常时,表示密码错误。

    • DisabledAccountException(帐号被禁用)

    • LockedAccountException(帐号被锁定)

    • ExcessiveAttemptsException(登录失败次数过多)

    • ExpiredCredentialsException(凭证过期)等

使用shiro自带的iniRealm认证:

package com.lany;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;

public class TestAuthenticator {
    public static void main(String[] args) {
        //1.创建安全管理器对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();

        //2.给安全管理器设置realm
        securityManager.setRealm(new IniRealm("classpath:shiro.ini"));

        //3.SecurityUtils 给全局安全工具类设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);

        //4.关键对象 subject 主体
        Subject subject = SecurityUtils.getSubject();

        //5.创建令牌
        UsernamePasswordToken token = new UsernamePasswordToken("xiaoming", "123");

        try {
            System.out.println("认证状态:" + subject.isAuthenticated());
            //用户认证
            subject.login(token);
            System.out.println("认证状态:" + subject.isAuthenticated());
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("认真失败:用户名不存在");
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            System.out.println("认真失败:密码错误");
        }
        Realm realm;

    }
}

shiro.ini:

[users]
xiaoming = 123
zhangsan = 456

自定义Realm

从shiro源码中可以看出,默认使用的是SimpleAccountRealm,它实现了AuthorizingRealm的两个方法,一个认证,一个授权

public class SimpleAccountRealm extends AuthorizingRealm {
		//.......省略
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        SimpleAccount account = getUser(upToken.getUsername());

        if (account != null) {

            if (account.isLocked()) {
                throw new LockedAccountException("Account [" + account + "] is locked.");
            }
            if (account.isCredentialsExpired()) {
                String msg = "The credentials for account [" + account + "] are expired";
                throw new ExpiredCredentialsException(msg);
            }

        }

        return account;
    }

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = getUsername(principals);
        USERS_LOCK.readLock().lock();
        try {
            return this.users.get(username);
        } finally {
            USERS_LOCK.readLock().unlock();
        }
    }
}

所以我们自定义Realm时也需要实现AuthorizingRealm接口

package com.lany.realm;

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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
 * 自定义realm实现,将认证/授权数据的来源转为数据库的实现。
 */

public class CustomerRealm extends AuthorizingRealm {

    /**
     * 授权
     *
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    /**
     * 认证
     *
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //在token中获取用户名
        String principal = (String) token.getPrincipal();
        System.out.println(principal);
        //根据身份信息使用jdbc mybatis查询相关数据库
        if ("zhangsan".equals(principal)) {
            // 参数1:返回正确的用户名
            // 参数2:返回数据库中正确的密码
            // 参数3:提供当前realm的名字,this.getName()
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, "123", this.getName());
            return simpleAuthenticationInfo;
        }
        return null;
    }
}

使用MD5+Salt

自定义md5+salt的realm

package com.lany.realm;

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

/**
 * @Description 使用自定义realm 加入md5+salt+hash
 * @Author 十里
 * @Date 2021/4/22 21:01
 */
public class CustomerMd5Realm extends AuthorizingRealm {
    /**
     * @Description 授权
     * @Author 十里
     * @Date 2021/4/22 20:59
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    /**
     * @Description 认证
     * @Author 十里
     * @Date 2021/4/22 20:59
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取身份信息
        String principal = (String) token.getPrincipal();
        //根据用户名查询数据库

        if ("zhangsan".equals(principal)) {
            //参数1:数据库用户名,参数2:数据库md5+Salt之后的密码 参数3:注册时的随机盐 参数4:realm的名字
            return new SimpleAuthenticationInfo(principal,
                    "0e2cc6db0f430296bac325822f99bb62",
                    ByteSource.Util.bytes("X0*ps"),
                    this.getName());
        }
        return null;
    }
}

自定义MD5Realm:使用MD5+Salt+散列认证

package com.lany;

import com.lany.realm.CustomerMd5Realm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;

/**
 * @author 十里
 * @description
 * @date 2021年04月22日 22:33
 */
public class MyTestAuthenticator {
    @Test
    public void Test1() {
        //设置安全管理器
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();

        //设置realm

        CustomerMd5Realm realm = new CustomerMd5Realm();
        //设置realm使用hash凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //设置将凭证进行hash处理的次数
        credentialsMatcher.setHashIterations(1024);
        //设置凭证加密方式
        credentialsMatcher.setHashAlgorithmName("md5");
        realm.setCredentialsMatcher(credentialsMatcher);
        defaultSecurityManager.setRealm(realm);
        //给安全工具类设置安全管理器
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //创建主体
        Subject subject = SecurityUtils.getSubject();
        //创建token
        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123");
        try {
            subject.login(token);
            System.out.println("登录成功");
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用户名错误");
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            System.out.println("密码错误");
        }

    }
}
上一篇:shiro


下一篇:SpringBoot8:Security与Shiro