其实这里所说的盐,简单的说,就是一组安全随机数。它会在特定的时候,加入到密码中(一般来说是加密后的密码)。从而使密码变得更有味道(从单一简单化到复杂化),更安全。
如何做到?
1). 在 doGetAuthenticationInfo 方法返回值创建 SimpleAuthenticationInfo 对象的时候, 需要使用
SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName) 构造器
2). 使用 ByteSource.Util.bytes() 来计算盐值.
3). 盐值需要唯一: 一般使用随机字符串或 user id
4). 使用 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); 来计算盐值加密后的密码的值.
注:下面的步骤我重点讲如何配置实现MD5 盐值加密
步骤:
- 当我们从前台页面进行登录时,点击提交登录后,在后台通过调用Subject的login方法,此时shiro会调用SecurityManage安全管理器,尝试对比密码并登录,而我们要做的就是自定义Realm,因为SecurityManage的认证需要从Realm中获取用户输入的信息。因此需要自定义一个Realm
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService service;
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection collection) {
return null;
}
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//通过token获取从前端输入的用户名
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
String username = userToken.getUsername();
//调用service方法判断该用户名是否存在
User user = service.queryUser(username);
if (user==null){
//抛出用户名不存在的错误
throw new UnknownAccountException();
}
//根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
//通常需要以下四个参数
//1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象.
Object principal = user;
//2). credentials: 密码.即从数据库中获取的密码
Object credentials = user.getPassword();
//3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可
String realmName = getName();
//4). credentialsSalt: 盐值,这里我使用的是用户名
ByteSource credentialsSalt = ByteSource.Util.bytes(user.getName());
return new SimpleAuthenticationInfo(principal,credentials,credentialsSalt,realmName);
}
}
说明:上面这个类中的第二个方法返回的SimpleAuthenticationInfo, 可以理解为里面传入的参数就是用于Shiro对用户输入的密码进行认证的相关信息。Shiro会将用户输入的密码先进行MD5 盐值加密再进行比较。
注:这里要注意,你存在数据库中的密码是要加密的,如果没有加密的话,Shiro会认证失败。会报如下错误
[org.apache.shiro.authc.AbstractAuthenticator] - Authentication failed
for token submission [org.apache.shiro.authc.UsernamePasswordToken -
admin01, rememberMe=false]. Possible unexpected error? (Typical or
expected login exceptions should extend from AuthenticationException)
- 接下来我们就需要配置Shiro,并在里面配置上使用MD5盐值加密
@Configuration
public class ShiroConfig {
/**
* ShiroFilterBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultSecurityManager);
return bean;
}
@Bean("securityManager")
public DefaultWebSecurityManager getDefaultSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联UserRealm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 创建 realm 对象 , 需要自定义
* @return
*/
@Bean("userRealm")
public UserRealm userRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher){
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(matcher);
return userRealm;
}
/**
* 替换当前 Realm 的 credentialsMatcher 属性.
* 直接使用 HashedCredentialsMatcher 对象, 并设置加密算法即可.
* 密码校验规则HashedCredentialsMatcher
* 这个类是为了对密码进行编码的
* 防止密码在数据库中明码表示,当然在登录认证的时候,
* 这个类也负责对form里输入的密码进行编码
* 处理认证匹配处理器
*/
@Bean("hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//指定加密方式为MD5
credentialsMatcher.setHashAlgorithmName("MD5");
//加密次数
credentialsMatcher.setHashIterations(1024);
return credentialsMatcher;
}
}
- 在Controller层只需获取当前的 Subject,即调用 SecurityUtils.getSubject()。然后把用户名和密码封装成封装为 UsernamePasswordToken 对象,最后调用Subject 的 login(AuthenticationToken) 方法.进行认证操作即可
@RequestMapping("/login")
public String login(String username,String password,Model model){
//获取当前的用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
try {
//执行登录方法,如果没有异常就Ok
subject.login(token);
return "index";
} catch (UnknownAccountException e) {
model.addAttribute("msg","用户名错误");
return "login";
} catch (IncorrectCredentialsException e){
model.addAttribute("msg","密码错误");
return "login";
}
}
至此关于使用MD5 盐值加密的配置就大功告成了。
总结一下:
Q:如何将获取的密码进行MD5 盐值加密:
A:替换当前 Realm 的 credentialsMatcher 属性. 直接使用 HashedCredentialsMatcher 对象, 并设置加密算法即可.
Q:如何比较用户输入的密码是否正确
A:通过 AuthenticatingRealm 的 credentialsMatcher 属性来进行的密码的比对!
- 获取当前的 Subject. 调用 SecurityUtils.getSubject();
- 测试当前的用户是否已经被认证. 即是否已经登录. 调用 Subject 的 isAuthenticated()
- 若没有被认证, 则把用户名和密码封装为 UsernamePasswordToken 对象
1). 创建一个表单页面
2). 把请求提交到 SpringMVC 的 Handler
3). 获取用户名和密码. - 执行登录: 调用 Subject 的 login(AuthenticationToken) 方法.
- 自定义 Realm 的方法, 从数据库中获取对应的记录, 返回给 Shiro.
1). 实际上需要继承 org.apache.shiro.realm.AuthenticatingRealm 类
2). 实现 doGetAuthenticationInfo(AuthenticationToken) 方法. - 由 shiro 完成对密码的比对.