前言
Shiro 提供了用于加密密码和验证密码服务的 CredentialsMatcher 接口,而 HashedCredentialsMatcher 正是 CredentialsMatcher 的一个实现类。写项目的话,总归会用到用户密码的非对称加密,目前主流的非对称加密方式是 MD5 ,以及在 MD5 上的加盐
处理,而 HashedCredentialsMatcher 也允许我们指定自己的算法和盐。本文将介绍 HashedCredentialsMatcher 的使用,以及对相关源码的进行解析,MD5 等加密知识请自行查阅。
HashedCredentialsMatcher 的使用
要使用 HashedCredentialsMatcher ,那么首先要进行配置。当然,前提是你已经引入了 Shiro 库。总共有三种配置方式:
-
XML 格式
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!-- 加密方式 --> <property name="hashAlgorithmName" value="MD5" /> <!-- 加密次数 --> <property name="hashIterations" value="2" /> <!-- 存储散列后的密码是否为16进制 --> <property name="storedCredentialsHexEncoded" value="true" /> </bean>
-
ini 等配置文件
首先在 web.xml 中自定义 shiro.ini 位置
<filter> <filter-name>ShiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class> <init-param> <param-name>configPath</param-name> <param-value>/WEB-INF/shiro.ini</param-value> </init-param> </filter>
然后除了 shiro 的通常配置之外,需加上:
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher ## 加密方式 credentialsMatcher.hashAlgorithmName=md5 ## 加密次数 credentialsMatcher.hashIterations=2 ## 存储散列后的密码是否为16进制 credentialsMatcher.storedCredentialsHexEncoded=true
-
建立 ShiroConfiguration 配置类,除了 shiro 的通常配置之外,需加上:
@Bean public HashedCredentialsMatcher hashedCredentialsMatcher(){ HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); //加密方式 hashedCredentialsMatcher.setHashAlgorithmName("md5"); //加密次数 hashedCredentialsMatcher.setHashIterations(2); //存储散列后的密码是否为16进制 //hashedCredentialsMatcher.isStoredCredentialsHexEncoded(); return hashedCredentialsMatcher; }
然后,在登录方法或者自定义的输入中获取登录 token,我选择的方式是在/login
中获取:
public Object login(@RequestBody User userParam, HttpSession session) {
String name = userParam.getName();
name = HtmlUtils.htmlEscape(name);
Subject subject = SecurityUtils.getSubject();
// 生成token
UsernamePasswordToken token = new UsernamePasswordToken(name, userParam.getPassword());
try {
// 从自定义Realm获取安全数据进行验证
subject.login(token);
User user = userService.getByName(name);
session.setAttribute("user", user);
return Result.success();
} catch (AuthenticationException e) {
String message ="账号密码错误";
return Result.fail(message);
}
}
接下来,是自定义 Realm,之前我也有博文写过相关知识,所以只贴下代码作参考:
public class JPARealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 权限分配的相关知识在此不做介绍,重点在验证方面
SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
return s;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName = token.getPrincipal().toString();
User user = userService.getByName(userName);
String passwordInDB = user.getPassword();
String salt = user.getSalt();
// 认证信息token里存放账号密码, getName() 是当前Realm的继承方法,通常返回当前类名
// 盐也放进去
// 这样通过配置中的 HashedCredentialsMatcher 进行自动校验
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName, passwordInDB, ByteSource.Util.bytes(salt),
getName());
return authenticationInfo;
}
}
HashedCredentialsMatcher 的源码分析
从开发者的角度来看,我们可以自己实现 CredentialsMatcher 的一个类来实现定制化的账户密码验证机制,例如:
public class MyCredentialsMatcher extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenCredentials = getCredentials(token);
Object accountCredentials = getCredentials(info);
return super.equals(tokenCredentials, accountCredentials);
}
}
SimpleCredentialsMatcher 是 CredentialsMatcher 这个类的默认实现,相信我,你基本上不会去自己实现 getCredentials 这种涉及到底层编码的方法,重点在于重写 doCredentialsMatch ,在这里你可以自定义账户密码验证机制。
不过实现 doCredentialsMatch 你还是有可能觉得麻烦,HashedCredentialsMatcher 封装好了 doCredentialsMatch() 方法,你可以完全不用管它。
上一节的使用中,流程就是:使用 token 类将用户输入的信息封装,然后采用 token 进行 login 操作。此时 shiro 将使用 token 中携带的用户信息调用 Realm 中自定义的 doGetAuthenticationInfo 方法进行校验比对,比对成功则登录成功。