这篇文章是对上一篇 34 | Spring Boot整合Shiro框架(Shiro简介+实现登录拦截、用户认证、请求授权并整合Mybatis和Thymeleaf)的扩展
当两个用户的密码相同时,单纯使用不加盐的MD5加密方式,会发现数据库中存在相同结构的密码,这样是不安全的。为了实现两个人的原始密码一样,但加密后的结果是不一样的效果,就要使用加了盐的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";
}
@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);
}
即创建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;
}
}
分享视频啦啦啦啦啦啦
最后总结一下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