自定义ShiroFilter,在登陆成功后访问接口时,会一直被拦截并报错”登陆失败“
public class AuthFilter extends BasicHttpAuthenticationFilter {
private static final Log logger = LogFactory.getLog(AuthFilter.class);
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
//获取请求token
String token = TokenUtil.getRequestToken((HttpServletRequest) request);
return new AuthToken(token);
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
return true;
}
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token,如果token不存在,直接返回
String token = null;
token = TokenUtil.getRequestToken((HttpServletRequest) request);
logger.info("从前端获取的token:" + token);
if (StringUtils.isBlank(token)) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtil.getOrigin());
httpResponse.setCharacterEncoding("UTF-8");
httpResponse.setContentType("application/json");
JSONObject result = new JSONObject();
result.put("errorCode",ReturnUtil.NO_LOGIN);
result.put("status",403);
result.put("errorMsg", "验证失败,token为空,请重新登录");
httpResponse.getOutputStream().write(result.toString().getBytes());
httpResponse.getOutputStream().close();
logger.info("onLoginFailure --------> 验证失败,token为空,请重新登录");
return false;
}
return executeLogin(request, response);
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtil.getOrigin());
httpResponse.setCharacterEncoding("UTF-8");
try {
//处理登录失败的异常
JSONObject result = new JSONObject();
result.put("errorCode",ReturnUtil.NO_LOGIN);
result.put("status",403);
result.put("errorMsg", "登录失败");
httpResponse.getOutputStream().write(result.toString().getBytes());
httpResponse.getOutputStream().close();
logger.info("onLoginFailure --------> 登录失败");
} catch (IOException exception) {
exception.printStackTrace();
logger.error("e:" + e);
}
return false;
}
/**
* 对跨域提供支持
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(Integer.parseInt(ReturnUtil.SUCCESS));
return false;
}
return super.preHandle(request, response);
}
}
在登录成功后,进入首页,访问接口时,最后会进入onLoginFailure这个方法,并返回前端提示错误信息
能够成功的访问接口
参考:
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
log.debug("=================校验用户权限=================");
Manager manager = (Manager) principals.getPrimaryPrincipal();
Long userId = manager.getManagerId();
// 设置用户拥有的角色集合,比如“超级管理员,测试员”
Set<String> roleSet = sysRoleService.getUserRoles(userId);
// 设置用户拥有的权限集合,比如“sys:role:add,sys:user:add”
Set<String> permissionSet = managerService.getUserMenus(userId);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(roleSet);
info.addStringPermissions(permissionSet);
log.debug("=================权限校验完成=================");
return info;
}
/**
* 认证(登录时调用,使用此方法进行用户名正确与否验证)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
String token = (String) auth.getPrincipal();
// 校验token有效性
Claims claims = jwtUtils.getClaimByToken(token);
if (claims == null || !jwtUtils.validateToken(token)) {
throw new AuthenticationException(jwtUtils.getHeader() + "非法无效!");
}
String userId = claims.getSubject();
String oldToken = (String) redisUtil.get(lunhuiConfig.getName() + ":sysUser:" + userId);
if (!oldToken.equals(token)) {
throw new AuthenticationException(jwtUtils.getHeader() + "过期,请重新登录!");
}
// 校验token是否过期
if (!jwtTokenRefresh(userId)) {
throw new AuthenticationException(jwtUtils.getHeader() + "过期,请重新登录!");
}
// 查询用户信息
Manager manager = managerService.getById(userId);
if (manager == null) {
throw new AuthenticationException("用户不存在!");
}
// 判断用户状态
if (manager.getInvalid()==Constant.CLOSE) {
throw new AuthenticationException("账号已被锁定,请联系管理员!");
}
return new SimpleAuthenticationInfo(manager, token, getName());
}
/**
* JwtToken刷新生命周期 (解决用户一直在线操作,突然token失效问题)
* 1、登录成功后将用户的JWT生成的token存储到redis中
* 2、当该用户再次请求时,通过JWTFilter层层校验之后会进入到doGetAuthenticationInfo进行身份验证
* 3、当用户请求时,token值还在生命周期内,过期时间重新计算
* 4、当用户请求时,token值已经超时,但该token对应redis中的key还存在,则表示该用户一直在操作只是token失效了,则会重新生成token并覆盖redis中的值,过期时间重新计算
* 5、当用户请求时,token值已经超时,并在redis中不存在对应的key,则表示该用户账户空闲超时,返回用户信息已失效,请重新登录。
* 7、注:当前端接收到Response的Header中的token值会存储起来,作为以后请求token使用
*
* @param userId
* @return
*/
public boolean jwtTokenRefresh(String userId) {
// 从redis中获取token
String redisToken = (String) redisUtil.get(lunhuiConfig.getName() + ":sysUser:" + userId);
if (StringUtils.isNotBlank(redisToken)) {
// 校验token是否过期
if (jwtUtils.isTokenExpired(redisToken)) {
// 生成新的token
String newToken = jwtUtils.createToken(userId);
redisUtil.set(lunhuiConfig.getName() + ":sysUser:" + userId, newToken, jwtUtils.getExpire());
} else {
// 更新token的过期时间
redisUtil.expire(lunhuiConfig.getName() + ":sysUser:" + userId, jwtUtils.getExpire());
}
return true;
}
return false;
}
ShiroFilterFactoryBean 没配置好吧,shiro登录成功后会进入LoginUrl。如果不会用shiro,建议换成sa-token吧,几个步骤,简单明了。
啊,一步步debug找到原因,在自定义的realms里面
这个问题是在我项目中出现,并且解决的,不代表都会出现这类问题!!!!!还是要自己一步步往下Debug找错误
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
logger.info("开始身份验证");
//1.获取前端传来的token
String token = (String) authenticationToken.getPrincipal();
Long userId = TokenGenerator.getUserId(token);
logger.info("userId:" + userId);
//2.根据token判断redis中是否存在
boolean hasKey = redisUtil.hasKey(token);
LoginVo loginVo = null;
if (hasKey){
//3.根据token从redis中查找对应的用户信息
loginVo = (LoginVo) redisUtil.get(token);
//4,如果用户不存在,抛出异常
if (loginVo == null) {
throw new UnknownAccountException("用户不存在");
}
}else {
//5,如果token不存在,抛出异常
throw new IncorrectCredentialsException("token失效,请重新登录");
}
SysUser sysUser = BeanUtil.toBean(loginVo, SysUser.class);
//将信息放到认证通过信息中去
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
sysUser,
token,
getName());
//设置密码盐
// authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(userInfo.getCredentialsSalt()));
return authenticationInfo;
}
在 认证信息中放入 “用户(sysUser),前端传来的token(token),realmName(getName() )”,而在此之前我放入的是“用户(sysUser),密码(sysUser.getPassWors),realm name(getName() )”,从而导致在
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenHashedCredentials = hashProvidedCredentials(token, info);
Object accountCredentials = getCredentials(info);
return equals(tokenHashedCredentials, accountCredentials);
}
//后续方法内
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 (isByteSource(tokenCredentials) && isByteSource(accountCredentials)) {
if (log.isDebugEnabled()) {
log.debug("Both credentials arguments can be easily converted to byte arrays. Performing " +
"array equals comparison");
}
byte[] tokenBytes = toBytes(tokenCredentials);
byte[] accountBytes = toBytes(accountCredentials);
return MessageDigest.isEqual(tokenBytes, accountBytes);
} else {
return accountCredentials.equals(tokenCredentials);
}
}
在判断tokenBytes和accountBytes时,两个值不相同而导致抛出异常
tokenBytes是前端传来的token转换得来的,而accountBytes则是你放入认证对象里面的credentials,即我放入的password
我看过几个Gitee上的Demo,有放密码的,也有放token的,所以这个问题的原因可能是参数不同而调用了不同的构造方法导致的(可能)
不过如果有问题还是Debug最保险,一步步的往下走,找到报错的地方,看具体原因以及参数问题