35 | SpringBoot整合Shiro之MD5盐值加密

前言

这篇文章是对上一篇 34 | Spring Boot整合Shiro框架(Shiro简介+实现登录拦截、用户认证、请求授权并整合Mybatis和Thymeleaf)的扩展

1. MD5盐值加密和MD5加密的区别

当两个用户的密码相同时,单纯使用不加盐的MD5加密方式,会发现数据库中存在相同结构的密码,这样是不安全的。为了实现两个人的原始密码一样,但加密后的结果是不一样的效果,就要使用加了盐的MD5加密方式。其实就好像炒菜一样,两道一样的鱼香肉丝,加的盐不一样,炒出来的味道就不一样。

2. 使用步骤

2.1 在控制层的注册用户方法中使用MD5盐值加密

@PostMapping("/register")
    public String register(User user) {
        // 设置盐值
        Object salt = ByteSource.Util.bytes(user.getName());
        // 加密后的密码
        String newPassword = new SimpleHash("MD5", user.getPassword(), salt, 1000).toHex();
        user.setPassword(newPassword);
        if (userService.registerUser(user)) {
            return "redirect:/toLogin";
        }
        return "register";
    }

2.2 在UserRealm的认证方法中加入盐值校验

@Override
    // 认证
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行了=>认证doGetAuthorizationInfo");

        // 封装用户的登录数据
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        // 连接数据库
        User user = userService.getUserByName(userToken.getUsername());

        // 没有此人,抛出 UnkownAccountException
        if (user == null) {
            return null;
        }

        //获取当前登录的用户对象
        Subject subject = SecurityUtils.getSubject();

        //存放到Session中
        Session session = subject.getSession();
        session.setAttribute("user", user);

        // realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可
        String realmName = getName();
        System.out.println(realmName);

        // 盐值。使用 username作为盐值,盐值必须是唯一的
        ByteSource credentialsSalt = ByteSource.Util.bytes(user.getName());

        /*三个参数:
            principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。
            一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。
            credentials: 传递的密码对象
            realmName: 认证名(指定当前 Realm 的类名)
        */
        // 密码认证, shiro自己做
        // 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
        return new SimpleAuthenticationInfo(user, user.getPassword(), credentialsSalt, realmName);
    }

2.3 在ShiroConfig中配置认证匹配器

即创建HashedCredentialsMatcher对象,并设置加密算法即可
注意: 如果你没有配置此类,你会发现尽管密码被加密,但你输入原始密码是等不进去的,因为这个HashedCredentialsMatcher类处理密码校验

package com.nsx.shiro.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Created by NXS
 * 2020/9/1 17:05
 */
@Configuration
public class ShiroConfig {
    //shiroFilterFactoryBean 3
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        // 设置安全管理器
        factoryBean.setSecurityManager(securityManager);

        /*
            anon:无需认证就可以访问
            authc:必须认证了才能访问
            user:必须拥有 记住我功能才能用
            perms:拥有对某个资源的权限才能访问
            role:拥有某个角色权限才能访问
        */
        //添加shiro的内置过滤器 拦截
        Map<String, String> fiterMap = new LinkedHashMap<>();

        // 设置哪些路径授予哪些权限
        fiterMap.put("/user/add", "perms[user:add]");
        fiterMap.put("/user/update", "perms[user:update]");

        // /user/* 指的是路径 必须认证了才能访问
        fiterMap.put("/user/*", "authc");
        factoryBean.setFilterChainDefinitionMap(fiterMap);

        // 设置登录的请求
        factoryBean.setLoginUrl("/toLogin");

        // 跳转至未授权页面
        factoryBean.setUnauthorizedUrl("/unauthorize");
        return factoryBean;
    }

    //DefaultWebSecurityManager 2
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 关联userRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    //创建realm对象,需要自定义类 1
    @Bean
    public UserRealm userRealm(@Qualifier("credentialsMatcher") HashedCredentialsMatcher matcher) {
        UserRealm userRealm = new UserRealm();
        userRealm.setCredentialsMatcher(matcher);
        return userRealm;
    }

    // ShiroDialect 用来整合shiro和thymleaf
    @Bean
    public ShiroDialect getShiroDialect() {
        return new ShiroDialect();
    }


    /**
     * 替换当前 Realm 的 credentialsMatcher 属性.
     * 直接使用 HashedCredentialsMatcher 对象, 并设置加密算法即可.
     * 密码校验规则HashedCredentialsMatcher
     * 这个类是为了对密码进行编码的
     * 防止密码在数据库中明码表示,当然在登录认证的时候,
     * 这个类也负责对form里输入的密码进行编码
     * 处理认证匹配处理器
     */
    @Bean("credentialsMatcher")
    public HashedCredentialsMatcher credentialsMatcher() {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        //设置加密算法
        matcher.setHashAlgorithmName("MD5");
        //设置加密次数
        matcher.setHashIterations(1000);
        //是否存储为16进制
        //将setStoredCredentialsHexEncoded设置为true,则需要使用toHex()进行转换成字符串,默认使用的是toBase64()
        matcher.setStoredCredentialsHexEncoded(true);
        return matcher;
    }
}

重点加入什么,参见下面图片
35 | SpringBoot整合Shiro之MD5盐值加密_第1张图片

3. 效果

分享视频啦啦啦啦啦啦

4. 总结:

最后总结一下Shiro的MD5加盐加密:
1)在doGetAuthenticationInfo方法返回值创建SimpleAuthenticationInfo对象的时候,需要使用
SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName)构造器。
2)使用ByteSource.Util.bytes()来计算盐值
3)盐值需要唯一,一般使用随机字符串或者userid
4)使用new SimpleHash(hashAlgorithmName,crdentials,salt,hashIterations)来进行MD5盐值加密,并且hashIterations一定要与HashedCredentialsMatcher设置的加密次数相同
参考:http://blog.csdn.net/acmman/article/details/78585662

你可能感兴趣的:(SpringBoot)