1.shiro组件 Realm
使用shiro框架需要自己定义一个Realm来进行登陆信息以及权限信息的认证。可以看作是shiro与数据库的桥梁。自定义Realm需要继承AuthorizingRealm 需要重写两个方法。
//权限验证
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 登陆认证,失败的话返回null
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
//密码
String password = String.copyValueOf(usernamePasswordToken.getPassword());
//用户名
String principal = usernamePasswordToken.getUsername();
SysUser user = sysUserService.getByName(principal);
if (Objects.isNull(user)) {
return null;
}
/**
* 第一个参数 getPrincipal()获得,自己按需要传
* 第二个参数 是用来和authenticationToken里的密码比对
*/
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getPass(), this.getName());
return simpleAuthenticationInfo;
}
可以看到doGetAuthenticationInfo方法是通过查询数据库的用户信息,返回一个SimpleAuthenticationInfo来实现登陆信息认证。具体是通过什么机制来通过这个返回对象与登陆信息认证,跟踪代码。入口是subject.login(token)方法,流程长就不一一贴上,定位到关键部分:
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = this.getCredentialsMatcher();
if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) {
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
throw new IncorrectCredentialsException(msg);
}
} else {
throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify credentials during authentication. If you do not wish for credentials to be examined, you can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
}
}
这是AuthenticatingRealm的一个方法,入参示是登陆时前端传过来的登陆信息和我们自定义Realm返回的信息。通过 this.getredentialMatcher() 获取到一个CredentialsMatcher对象,如果我们不设置的话,默认是使用的SimpleCredentialsMatcher。进入到doCredentialsMatch方法:
protected boolean equals(Object tokenCredentials, Object accountCredentials) {
if (log.isDebugEnabled()) {
log.debug("Performing credentials equality check for tokenCredentials of type [" + tokenCredentials.getClass().getName() + " and accountCredentials of type [" + accountCredentials.getClass().getName() + "]");
}
if (this.isByteSource(tokenCredentials) && this.isByteSource(accountCredentials)) {
if (log.isDebugEnabled()) {
log.debug("Both credentials arguments can be easily converted to byte arrays. Performing array equals comparison");
}
byte[] tokenBytes = this.toBytes(tokenCredentials);
byte[] accountBytes = this.toBytes(accountCredentials);
return MessageDigest.isEqual(tokenBytes, accountBytes);
} else {
return accountCredentials.equals(tokenCredentials);
}
}
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenCredentials = this.getCredentials(token);
Object accountCredentials = this.getCredentials(info);
return this.equals(tokenCredentials, accountCredentials);
}
这里就是匹配密码是否匹配的具体逻辑。
我项目数据库是存储的加密的密码,使用的是BCryptPasswordEncoder来进行hash加密的,是需要用他的BCryptPasswordEncoder::matches(String plainPass,String encodePass) 来验证密码,所以可以自己重写一个matcher,代码如下:
public class CustomerCredentialsMatcher implements CredentialsMatcher {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Override
public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo authenticationInfo) {
//前端传来的密码
String currentPass = String.copyValueOf((char[]) authenticationToken.getCredentials());
//数据库密码
String dbPass = (String) authenticationInfo.getCredentials();
return passwordEncoder.matches(currentPass,dbPass);
}
}
在自己的Realm里设置一下我们自己的matcher:
public class CustRealm extends AuthorizingRealm {
...
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
CustomerCredentialsMatcher customerCredentialsMatcher = new CustomerCredentialsMatcher();
super.setCredentialsMatcher(customerCredentialsMatcher);
}
...
}
done.