解决realm自定义异常失效的问题
一、项目环境
springboot 前后端分离
shiro:1.7.1
多个realm,分别处理小程序,手机号登录
本来项目只有账户密码登录,但是需求加微信绑定登录,加了一个处理微信登录的realm,但是本身单realm的异常提示竟然没有了。
{
"code": 401,
"msg": "Authentication token of type [class com.jack.modules.njp.security.oauth2.Oauth2Token] could not be authenticated by any configured realms. Please ensure that at least one realm can authenticate these tokens."
}
三、先直接说解决方案
自定义ModularRealmAuthenticator
public class CustomModularRealmAuthenticator extends ModularRealmAuthenticator {
private static final Logger log = LoggerFactory.getLogger(ModularRealmAuthenticator.class);
/**
* 重写多realm校验方法,避免无法捕获到realm中的自定义异常
* 取消执行代码 realm.getAuthenticationInfo(token); 的tey catch
* 多个realm时候,如果其中一个realm抛出异常则直接抛出异常,类似single方法,
* @param realms
* @param token
* @return
*/
@Override
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
AuthenticationStrategy strategy = getAuthenticationStrategy();
AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
if (log.isTraceEnabled()) {
log.trace("Iterating through {} realms for PAM authentication", realms.size());
}
for (Realm realm : realms) {
aggregate = strategy.beforeAttempt(realm, token, aggregate);
if (realm.supports(token)) {
log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);
AuthenticationInfo info = null;
Throwable t = null;
info = realm.getAuthenticationInfo(token);
//当匹配到info就返回不再继续查询
if (info !=null){
return aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
}
} else {
log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token);
}
}
aggregate = strategy.afterAllAttempts(token, aggregate);
return aggregate;
}
}
配置到shrio
@Configuration
public class ShiroConfig {
@Bean("securityManager")
public SecurityManager securityManager(Oauth2Realm oAuth2Realm, Oauth1Realm oAuth1Realm, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//将自定义的ModularRealmAuthenticator 添加到这里,不能放在realm下面,会失效
ModularRealmAuthenticator authenticator = new CustomModularRealmAuthenticator();
securityManager.setAuthenticator( authenticator );
List realms =new ArrayList(2);
realms.add(oAuth1Realm);
realms.add(oAuth2Realm);
securityManager.setRealms(realms);
securityManager.setSessionManager(sessionManager);
securityManager.setRememberMeManager(null);
return securityManager;
}
}
四、原因分析
先debug 看执行过程,很多地方都可以打断点
1.自定义过滤器 executeLogin打
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token,如果token不存在,直接返回401
String token = getRequestToken((HttpServletRequest) request);
if(StringUtils.isBlank(token)){
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
String json = new Gson().toJson(new Result().error(ErrorCode.UNAUTHORIZED));
httpResponse.getWriter().print(json);
return false;
}
return executeLogin(request, response);
}
2.自定义realm中doGetAuthenticationInfo 打
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String accessToken = (String) token.getPrincipal();
//根据accessToken,查询用户信息
SysUserTokenEntity tokenEntity = shiroService.getByToken(accessToken);
//token失效
if(tokenEntity == null || tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()){
throw new IncorrectCredentialsException(MessageUtils.getMessage(ErrorCode.TOKEN_INVALID));
}
/**
* 增加缓存中的用户实体类,实现临时修改tenant
*/
SysUserEntity userEntity =null;
userEntity= (SysUserEntity) redisUtils.get(accessToken);
if(userEntity ==null){
//查询用户信息
userEntity = shiroService.getUser(tokenEntity.getUserId());
}
//转换成UserDetail对象
UserDetail userDetail = ConvertUtils.sourceToTarget(userEntity, UserDetail.class);
//获取用户对应的部门数据权限
List<Long> deptIdList = shiroService.getDataScopeList(userDetail.getId());
userDetail.setDeptIdList(deptIdList);
//账号锁定
if(userDetail.getStatus() == 0){
throw new LockedAccountException(MessageUtils.getMessage(ErrorCode.ACCOUNT_LOCK));
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userDetail, accessToken, getName());
return info;
}
无论从哪里进都会进到 org.apache.shiro.authc.pam.doMultiRealmAuthentication;
try {
info = realm.getAuthenticationInfo(token);
} catch (Throwable var12) {
t = var12;
if (log.isDebugEnabled()) {
String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
log.debug(msg, var12);
}
}
在这里无论我们在realm的getAuthenticationInfo方法找那个定义了多少异常都会被捕获,所以我们需要重写他把
try catch去掉就可以
四、总结
耐下心bebug一遍流程,一般都能定位问题的位置,然后百度怎么实现目标。