shiro的主要功能为 拦截无权限管理的页面 需要接管登录服务 登录时设置加密方式
spring整合shiro基本配置
shiro的设置类
@Bean
public CustomRealm shiroReam() {
CustomRealm customRealm = new CustomRealm();
customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return customRealm;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroReam());
return securityManager;
}
/**
* 密码加密验证配置
*
* @return hash凭证匹配器
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 使用md5 算法进行加密
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
// 设置散列次数: 意为加密几次
hashedCredentialsMatcher.setHashIterations(ShiroUtil.hashIterations);
return hashedCredentialsMatcher;
}
package com.hz.treasury.componet;
import com.hz.treasury.bean.Login;
import com.hz.treasury.bean.Permissions;
import com.hz.treasury.bean.Role;
import com.hz.treasury.bean.User;
import com.hz.treasury.exception.LoginFreezeException;
import com.hz.treasury.service.LoginService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
/**
* @author Hz
*/
@Slf4j
public class CustomRealm extends AuthorizingRealm {
private LoginService loginService;
@Autowired
public void setLoginService(LoginService loginService) {
this.loginService = loginService;
}
/**
* 权限配置类
*
* @param principalCollection 收集信息
* @return 授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String name = principalCollection.getPrimaryPrincipal().toString();
User user = loginService.getLoginByUsername(name).getUser();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
Role role = user.getCurrentRole();
simpleAuthorizationInfo.addRole(role.getName());
for (Permissions permissions : role.getPermissions()) {
simpleAuthorizationInfo.addStringPermission(permissions.getName());
}
return simpleAuthorizationInfo;
}
/**
* 认证配置类
*
* @param authenticationToken 身份验证令牌
* @return 身份验证信息
* @throws AuthenticationException 身份验证异常
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
if (!StringUtils.hasText((String) authenticationToken.getPrincipal())) {
return null;
}
String name = authenticationToken.getPrincipal().toString();
Login login = loginService.getLoginByUsername(name);
if (login == null) {
return null;
} else if (!login.getState()) {
throw new LoginFreezeException();
} else {
ByteSource salt = ByteSource.Util.bytes(login.getSalt());
return new SimpleAuthenticationInfo(name, login.getPassword(), salt, getName());
}
}
}
shiro登录时验证过程源码
这时候在注册时用同样的格式存入密码
login.setPassword(ShiroUtil.salt(password, ByteSource.Util.bytes(login.getSalt())));
在登录时
subject.login(usernamePasswordToken);
即可完成登录
但是时怎么完成登录的呢,看看源码
调用了securityManager中的login方法实现类为DefaultSecurityManager
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = authenticate(token);
} catch (AuthenticationException ae) {
try {
onFailedLogin(token, ae, subject);
} catch (Exception e) {
if (log.isInfoEnabled()) {
log.info("onFailedLogin method threw an " +
"exception. Logging and propagating original AuthenticationException.", e);
}
}
throw ae; //propagate
}
Subject loggedIn = createSubject(token, info, subject);
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
token为我们传入的登录信息,传入的是UsernamePasswordToken类为AuthenticationToken的子类
info为正确的用户信息,如果验证不通过会抛出异常进入onFailedLogin 进入失败的Subject
如果验证通过则创建成功的Subject
authenticate方法进入了AbstractAuthenticator的authenticate方法
里面又调用了
info = doAuthenticate(token);
实现类为ModularRealmAuthenticator
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
如果我们创建设置了Realms类且只有一个时进入doSingleRealmAuthentication,调用接口
AuthenticationInfo info = realm.getAuthenticationInfo(token);
实现类为AuthenticatingRealm
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//otherwise not cached, perform the lookup:
info = doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}
if (info != null) {
assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}
return info;
}
这里的token仍然为传入的账号密码
info为通过token中的username获取数据库中的登录相关信息如果账号存在则会调用cacheAuthenticationInfoIfPossible方法将信息缓存起来用于下次登录
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
这里有一个抽象方法,如果我们在设置创建CustomRealm时重写了这个方法,就会进入到我们重写的方法中,获取到一个AuthenticationInfo的子类进入 assertCredentialsMatch方法中对比
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = getCredentialsMatcher();
if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) {
//not successful - throw an exception to indicate this:
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.");
}
}
/**
* Interface implemented by classes that can determine if an AuthenticationToken's provided
* credentials matches a corresponding account's credentials stored in the system.
*
* <p>Simple direct comparisons are handled well by the
* {@link SimpleCredentialsMatcher SimpleCredentialsMatcher}. If you
* hash user's credentials before storing them in a realm (a common practice), look at the
* {@link HashedCredentialsMatcher HashedCredentialsMatcher} implementations,
* as they support this scenario.
*
* @see SimpleCredentialsMatcher
* @see AllowAllCredentialsMatcher
* @see Md5CredentialsMatcher
* @see Sha1CredentialsMatcher
* @since 0.1
*/
public interface CredentialsMatcher {
/**
* Returns {@code true} if the provided token credentials match the stored account credentials,
* {@code false} otherwise.
*
* @param token the {@code AuthenticationToken} submitted during the authentication attempt
* @param info the {@code AuthenticationInfo} stored in the system.
* @return {@code true} if the provided token credentials match the stored account credentials,
* {@code false} otherwise.
*/
boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info);
}
从注释可以看到有四种验证器
SimpleCredentialsMatcher, AllowAllCredentialsMatcher, Md5CredentialsMatcher, Sha1CredentialsMatcher
从注释看到通常用直接比较和hash对比 我们用的是hash对比
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenHashedCredentials = hashProvidedCredentials(token, info);
Object accountCredentials = getCredentials(info);
return equals(tokenHashedCredentials, accountCredentials);
}
protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) {
Object salt = null;
if (info instanceof SaltedAuthenticationInfo) {
salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();
} else {
//retain 1.0 backwards compatibility:
if (isHashSalted()) {
salt = getSalt(token);
}
}
return hashProvidedCredentials(token.getCredentials(), salt, getHashIterations());
}
从info或token中获取盐值
protected Hash hashProvidedCredentials(Object credentials, Object salt, int hashIterations) {
String hashAlgorithmName = assertHashAlgorithmName();
return new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
}
对输入的密码加密然后对比密码,之后就是一系列的保存信息
其实他的登录也很简单,所以说我才能很顺利的读懂源码