shiro学习二:shiro的加密认证详解,加盐与不加盐两个版本。

前言

一、shiro不加盐加密认证

1. 逻辑流程

与上个博客大体一致。

  1. 构建安全管理器环境 DefaultWebSecurityManager
  2. 构建数据源,这里使用自定义的数据源CustomRealmCh07,模拟的数据代替从数据库获取数据。
  3. 构建HashedCredentialsMatcher,进行设置加密方式和加密次数。这里是对下面第五步中用户输入的密码进行加密。
  4. 获取主体
  5. 用户名和密码生成token(这里也就是 用户输入的用户名密码 )
  6. 进行登入(提交认证)
  7. 然后进入自定义数据源的doGetAuthenticationInfo()进行认证。(源码看上一个博客的最后一节)
    认证过程中需要根据用户名获取密码,然后在对密码进行加密,将加密后的密码、用户名、类名称 交给SimpleAuthenticationInfo类,去认证。

2. 修改部分

  • Ch07_testMatcher.java 中修改部分
    在这里插入图片描述

  • CustomRealmCh07 添加方法
    添加方法

    /**
     * 获得密文密码
     * 不加盐
     * @Author:     小冯
     * @UpdateUser:
     * @Version:     0.0.1
     * @param currentPassword
     * @return       java.lang.String
     * @throws
     */
    private String getPasswordMatcher(String currentPassword){
        return new Md5Hash(currentPassword, null,2).toString();
    }
  • doGetAuthenticationInfo()方法中修改
    在这里插入图片描述

3. 自定义数据源:CustomRealmCh07

编写类 com.feng.shiro.CustomRealmCh07

package com.feng.shiro;

import org.apache.shiro.SecurityUtils;
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.crypto.hash.Md5Hash;
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.HashMap;
import java.util.List;
import java.util.Map;

public class CustomRealmCh07 extends AuthorizingRealm {

    /**
     * 模拟数据库中的用户名和密码
     */
    private Map<String, String> userMap =new HashMap<>();

    /**
     * 使用代码块初始化数据
     */
    {
        userMap.put("admin", "123456");
        userMap.put("test","123456");
    }


    /**
     * 授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("---------->开始执行 CustomRealm.doGetAuthorizationInfo 方法啦");
        // SecurityUtils.getSubject().getPrincipal()用户名: 就是SimpleAuthenticationInfo(username,password,getName()); 第一个参数

        String name= (String) SecurityUtils.getSubject().getPrincipal();
        System.out.println("---------->(String)SecurityUtils.getSubject().getPrincipal():"+name);

        String username = (String) principalCollection.getPrimaryPrincipal();
        System.out.println("---------->(String) principalCollection.getPrimaryPrincipal():"+username);
        //从数据库或者缓存中获取角色数据
        List<String> roles = getRolesByUsername(username);
        //从数据库或者缓存中获取权限数据
        List<String> permissions = getPerminssionsByUsername(username);
        //创建AuthorizationInfo,并设置角色和权限信息
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addStringPermissions(permissions);
        authorizationInfo.addRoles(roles);
        return authorizationInfo;
    }

    /**
     * 认证
     * 逻辑:获取用户输入的用户名。密码不用获取,已经在 shiro 中(密码是 HashedCredentialsMatcher 进行 md5  加密的)
     * 根据用户名获取数据库的密码,然后进行MD5加密。
     * 然后将 用户名、加密后密码、类名称  交给shiro去认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        /*
        * AuthenticationToken 是 UsernamePasswordToken 顶级父类,getPrincipal() 和 getCredentials() 是其接口的两个方法,
        * 被 UsernamePasswordToken实现重新,分别是 获取用户名和密码。
        * 这里也就是获取用户输入的用户名和密码
        * */
        System.out.println("---------->开始执行 CustomRealm.doGetAuthenticationInfo 方法啦");
        //获取登录用户名
        String username = (String) authenticationToken.getPrincipal();
        System.out.println("---------->根据authenticationToken获取到的用户名:"+username);
        //通过用户名到数据库获取用户信息
        String password = getPasswordByUsername(username);
        System.out.println("---------->从数据库中获取的密码:"+password);
        if (password == null) {
            return null;
        }
        // 不加盐加密

        // 获取密文密码,对从数据库获取的密码进行加密(然后与用户输入的加密后的进行比较,这个应该交给shiro去做)
        String matcherPwd=getPasswordMatcher(password);
        System.out.println("---------->获取到的密文密码:" + matcherPwd);
        // 传入用户名、密码、name 。流转到下面去认证
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, matcherPwd, getName());
        return authenticationInfo;
    }


    /**
     * 模拟通过数据库获取权限数据
     * @Author:     小冯
     * @UpdateUser:
     * @Version:     0.0.1
     * @param username
     * @return       java.util.List<java.lang.String>
     * @throws
     */
    private List<String> getPerminssionsByUsername(String username) {
        List<String> permissions = new ArrayList<>();
        /**
         * 只有是 admin 用户才有 新增、删除、编辑权限
         * 用户只有 遍历 权限
         */
        if(username.equals("admin")){
            permissions.add("user:delete");
            permissions.add("user:add");
            permissions.add("user:edit");
        }
        permissions.add("user:list");
        return permissions;
    }

    /**
     * 模拟通过数据库获取用户角色信息
     * @Author:     小冯
     * @UpdateUser:
     * @Version:     0.0.1
     * @param username
     * @return       java.util.List<java.lang.String>
     * @throws
     */
    private List<String> getRolesByUsername(String username) {
        List<String> roles = new ArrayList<>();
        if(username.equals("admin")){
            roles.add("admin");
        }
        roles.add("test");
        return roles;
    }
    /**
     * 通过用户名查询密码,模拟数据库查询
     * @Author:     小冯
     * @UpdateUser:
     * @Version:     0.0.1
     * @param username
     * @return       java.lang.String
     * @throws
     */
    private String getPasswordByUsername(String username) {
        return userMap.get(username);
    }

    /**
     * 获得密文密码
     * 不加盐
     * @Author:     小冯
     * @UpdateUser:
     * @Version:     0.0.1
     * @param currentPassword
     * @return       java.lang.String
     * @throws
     */
    private String getPasswordMatcher(String currentPassword){
        return new Md5Hash(currentPassword, null,2).toString();
    }
}

4. 编写:Ch07_testMatcher

package com.feng;

import com.feng.shiro.CustomRealmCh07;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class Ch07_testMatcher {

    /**
     *  SpringBoot整合 shiro 之 不加盐 加密认证
     *
     *  不加盐认证::  设置加密次数 2
     *  1、使用 类:HashedCredentialsMatcher,并设置加密算法和加密次数,
     *  2、自定义数据源 在设置 此类HashedCredentialsMatcher
     *
     *  3、然后在
     */
    @Test
    public void testMatcher() {
        // 1. 构建安全管理器环境
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 2. 构建数据源
        CustomRealmCh07 customRealmCh07 = new CustomRealmCh07();
        // 3. 初始化 md5 加密类
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("md5");
        //设置加密次数
        matcher.setHashIterations(2);
        // 对数据源设置加密
        customRealmCh07.setCredentialsMatcher(matcher);
        // 设置数据源
        securityManager.setRealm(customRealmCh07);

        // 设置 安全管理器
        SecurityUtils.setSecurityManager(securityManager);

        // 3. 获取主体
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("admin", "123456");
        System.out.println("从UsernamePasswordToken 获取的用户名和密码:"+usernamePasswordToken.getUsername()+"/"+usernamePasswordToken.getPassword());
        System.out.println("获取用户名和密码的 token:"+usernamePasswordToken.toString());
        try {
            // 2.主体提交认证
            subject.login(usernamePasswordToken);
            System.out.println("---------->用户认证的状态:isAuthenticated=" + subject.isAuthenticated());
            // 检查是否有角色
            subject.checkRoles("admin");
            System.out.println("---------->有 admin 角色");
            // 检查是够有权限
            subject.checkPermissions("user:delete", "user:edit", "user:list", "user:add");
            System.out.println("---------->有 user:delete, user:edit, user:list, user:add 权限");

            System.out.println("执行 logout()方法后");
            subject.logout();
            System.out.println("---------->用户认证的状态:" + subject.isAuthenticated());
        } catch (UnknownAccountException e) {
            //username wasn't in the system, show them an error message?
            System.out.println("账号不存在");
        } catch (IncorrectCredentialsException ice) {
            System.out.println("用户名密码不匹配");
            //password didn't match, try again?
        } catch (LockedAccountException lae) {
            //account for that username is locked - can't login.  Show them a message?
            System.out.println("账号已被禁用,请联系系统管理员");
        } catch (AuthenticationException ae) {
            //unexpected condition - error?
            System.out.println("用户认证失败");
        } catch (UnauthorizedException ae) {
            System.out.println("用户没有权限");
        }
    }
}

5. 测试

  • 当用户 admin 登录进来的时候 程序输出
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("admin", "123456");

程序输出
在这里插入图片描述
在这里插入图片描述

  • 假如改成还是用明文检验
    在这里插入图片描述
    程序输出
    在这里插入图片描述

二、shiro加盐加密认证

当两个用户的密码相同时,单纯使用不加盐的MD5加密方式,会发现数据库中存在相同结构的密码,这样也是不安全的。我们希望即便是两个人的原始密码一样,加密后的结果也不一样。如何做到呢?其实就好像炒菜一样,两道一样的鱼香肉丝,加的盐不一样,炒出来的味道就不一样。MD5加密也是一样,需要进行盐值加密。

1. 流程逻辑

与上面的案例相同,仅有稍微几处有所改变。

2. 修改部分

  1. 修改加密方法getPasswordMatcher()
    在这里插入图片描述
  2. 修改doGetAuthenticationInfo()方法
    在这里插入图片描述

3. 自定义数据源:CustomRealmCh08

编写类 com.feng.shiro.CustomRealmCh08

package com.feng.shiro;

import org.apache.shiro.SecurityUtils;
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.crypto.hash.Md5Hash;
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.HashMap;
import java.util.List;
import java.util.Map;

public class CustomRealmCh08 extends AuthorizingRealm {

    /**
     * 模拟数据库中的用户名和密码
     */
    private Map<String, String> userMap =new HashMap<>();

    /**
     * 使用代码块初始化数据
     */
    {
        userMap.put("admin", "123456");
        userMap.put("test","123456");
    }


    /**
     * 授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("---------->开始执行 CustomRealm.doGetAuthorizationInfo 方法啦");
        // SecurityUtils.getSubject().getPrincipal()用户名: 就是SimpleAuthenticationInfo(username,password,getName()); 第一个参数

        String name= (String) SecurityUtils.getSubject().getPrincipal();
        System.out.println("---------->(String)SecurityUtils.getSubject().getPrincipal():"+name);

        String username = (String) principalCollection.getPrimaryPrincipal();
        System.out.println("---------->(String) principalCollection.getPrimaryPrincipal():"+username);
        //从数据库或者缓存中获取角色数据
        List<String> roles = getRolesByUsername(username);
        //从数据库或者缓存中获取权限数据
        List<String> permissions = getPerminssionsByUsername(username);
        //创建AuthorizationInfo,并设置角色和权限信息
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addStringPermissions(permissions);
        authorizationInfo.addRoles(roles);
        return authorizationInfo;
    }

    /**
     * 认证
     * 逻辑:获取用户输入的用户名。密码不用获取,已经在 shiro 中(密码是 HashedCredentialsMatcher 进行 md5  加密的)
     * 根据用户名获取数据库的密码,然后进行MD5加密。
     * 然后将 用户名、加密后密码、类名称  交给shiro去认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        /*
        * AuthenticationToken 是 UsernamePasswordToken 顶级父类,getPrincipal() 和 getCredentials() 是其接口的两个方法,
        * 被 UsernamePasswordToken实现重新,分别是 获取用户名和密码。
        * 这里也就是获取用户输入的用户名和密码
        * */
        System.out.println("---------->开始执行 CustomRealm.doGetAuthenticationInfo 方法啦");
        //获取登录用户名
        String username = (String) authenticationToken.getPrincipal();
        System.out.println("---------->根据authenticationToken获取到的用户名:"+username);
        //通过用户名到数据库获取用户信息
        String password = getPasswordByUsername(username);
        System.out.println("---------->从数据库中获取的密码:"+password);
        if (password == null) {
            return null;
        }
        // 加盐加密

        String passwordMatcher = getPasswordMatcher(password, username);
        System.out.println("---------->获取到的密文密码:" + passwordMatcher);
        // 传入用户名、密码、name 。流转到下面去认证
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                username, passwordMatcher,ByteSource.Util.bytes(username), getName());
        // authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(username));
        return authenticationInfo;
    }


    /**
     * 模拟通过数据库获取权限数据
     * @Author:     小冯
     * @UpdateUser:
     * @Version:     0.0.1
     * @param username
     * @return       java.util.List<java.lang.String>
     * @throws
     */
    private List<String> getPerminssionsByUsername(String username) {
        List<String> permissions = new ArrayList<>();
        /**
         * 只有是 admin 用户才有 新增、删除、编辑权限
         * 用户只有 遍历 权限
         */
        if(username.equals("admin")){
            permissions.add("user:delete");
            permissions.add("user:add");
            permissions.add("user:edit");
        }
        permissions.add("user:list");
        return permissions;
    }

    /**
     * 模拟通过数据库获取用户角色信息
     * @Author:     小冯
     * @UpdateUser:
     * @Version:     0.0.1
     * @param username
     * @return       java.util.List<java.lang.String>
     * @throws
     */
    private List<String> getRolesByUsername(String username) {
        List<String> roles = new ArrayList<>();
        if(username.equals("admin")){
            roles.add("admin");
        }
        roles.add("test");
        return roles;
    }
    /**
     * 通过用户名查询密码,模拟数据库查询
     * @Author:     小冯
     * @UpdateUser:
     * @Version:     0.0.1
     * @param username
     * @return       java.lang.String
     * @throws
     */
    private String getPasswordByUsername(String username) {
        return userMap.get(username);
    }


    /**
     * 获得密文密码
     * 加盐(用户名)
     *
     * @param currentPassword
     * @param username
     * @return
     */
    private String getPasswordMatcher(String currentPassword,String username){
        return new Md5Hash(currentPassword, username).toString();
    }

}

4. 编写:Ch08_testSaltMatcher

package com.feng;

import com.feng.shiro.CustomRealmCh08;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;


@SpringBootTest
public class Ch08_testSaltMatcher {

    /**
     *  SpringBoot整合 shiro 之 盐值加密认证
     *
     *  加盐认证:  设置加密次数 1
     *
     *  当两个用户的密码相同时,单纯使用不加盐的MD5加密方式,会发现数据库中存在相同结构的密码,这样也是不安全的。我们希望即
     *  便是两个人的原始密码一样,加密后的结果也不一样。如何做到呢?其实就好像炒菜一样,两道一样的鱼香肉丝,加的盐不一样,炒
     *  出来的味道就不一样。MD5加密也是一样,需要进行盐值加密。
     */
    @Test
    public void testSaltMatcher() {
        // 1. 构建安全管理器环境
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 2. 构建数据源
        CustomRealmCh08 customRealmCh08 = new CustomRealmCh08();

        // 对数据源进行 md5 加密
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("md5");
        //设置加密次数
        matcher.setHashIterations(1);
        customRealmCh08.setCredentialsMatcher(matcher);

        // 设置数据源
        securityManager.setRealm(customRealmCh08);
        // 设置 安全管理器
        SecurityUtils.setSecurityManager(securityManager);

        // 3. 获取主体
        Subject subject = SecurityUtils.getSubject();
        //用户名和密码的token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("admin", "123456");
        System.out.println("从UsernamePasswordToken 获取的用户名和密码:"+usernamePasswordToken.getUsername()+usernamePasswordToken.getPassword());
        System.out.println("获取用户名和密码的 token:"+usernamePasswordToken.toString());
        try {
            // 2.主体提交认证
            subject.login(usernamePasswordToken);
            System.out.println("---------->用户认证的状态:isAuthenticated=" + subject.isAuthenticated());
            // 检查是否有角色
            subject.checkRoles("admin");
            System.out.println("---------->有 admin 角色");
            // 检查是够有权限
            subject.checkPermissions("user:delete", "user:edit", "user:list", "user:add");
            System.out.println("---------->有 user:delete, user:edit, user:list, user:add 权限");

            System.out.println("执行 logout()方法后");
            subject.logout();
            System.out.println("---------->用户认证的状态:" + subject.isAuthenticated());
        } catch (UnknownAccountException e) {
            //username wasn't in the system, show them an error message?
            System.out.println("账号不存在");
        } catch (IncorrectCredentialsException ice) {
            System.out.println("用户名密码不匹配");
            //password didn't match, try again?
        } catch (LockedAccountException lae) {
            //account for that username is locked - can't login.  Show them a message?
            System.out.println("账号已被禁用,请联系系统管理员");
        } catch (AuthenticationException ae) {
            //unexpected condition - error?
            System.out.println("用户认证失败");
        } catch (UnauthorizedException ae) {
            System.out.println("用户没有权限");
        }
    }
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java冯坚持

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值