解决shiro的登录成功后跳转到error.html
1. 先说一下问题现象:saas项目中,登录成功之后,跳转了error.html,停顿一会儿后进入主页面。
2. 说一下我处理这个问题的步骤:
- 搞清楚这个页面登录的流程。
- 问题产生的原因,产生的时机
3. 根据上边的思路先处理这个saas平台登录的流程,梳理如下:
- 项目的存在LoginController。存在接口login、main、mianIndex等接口。并且该controller对局部异常做了处理:
/**
* 当前controller类中的异常处理,主要作用于用户登录 ,其他操作请勿写在该类中
*
* @param req
* @param e
* @return
* @throws Exception
*/
@ExceptionHandler(value = BusinessException.class)
public ModelAndView ErrorHandler(HttpServletRequest req, BusinessException e) throws Exception {
ModelAndView view = new ModelAndView();
view.addObject("msg", "登录失败" + e.getMsg());
view.setViewName("error");
return view;
}
- 项目使用了shiro,所以会在登录的时候先进行Filter过滤。项目存在ShiroConfig、CustomAccessFilter
- CustomAccessFilter继承了FormAuthenticationFilter,所以登录会先执行重新的方法:onAccessDenied 然后执行到executeLogin,执行重写的onLoginSuccess,最后接着执行onAccessDenied方法中未完成的内容。
/**
*
*/
package cn.etcom.security.filter;
import java.util.Date;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import cn.etcom.entity.security.ActiveUser;
import cn.etcom.security.ShiroUtils;
import cn.etcom.util.prop.WebDefineProperties;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.util.WebUtils;
import org.crazycake.shiro.RedisSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cn.etcom.entity.SystemLog;
import cn.etcom.service.log.SystemLogService;
import cn.etcom.service.log.impl.SystemLogServiceImpl;
import cn.etcom.util.LoggerUtils;
import cn.etcom.util.enums.ActionEnum;
import cn.etcom.util.spring.SpringUtil;
/**
* @author admin
*
*/
public class CustomAccessFilter extends FormAuthenticationFilter {
private static final Logger logger = LoggerFactory.getLogger(LogoutFilter.class);
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
if (isLoginRequest(request, response)) { // 判断是否登录
if (isLoginSubmission(request, response)) { // 判断是否为post访问
SystemLogService systemLogService = SpringUtil.getBean(SystemLogServiceImpl.class);
String ip = LoggerUtils.getCliectIp((HttpServletRequest) request);
HttpSession session = ((HttpServletRequest) request).getSession();
//Session session = ShiroUtils.getSession();
session.setAttribute("ip", ip);
String username = super.getUsername(request);
WebDefineProperties wdp = SpringUtil.getBean(WebDefineProperties.class);
String s = ShiroUtils.getSession().getId().toString();
boolean flag = executeLogin(request, response);
logger.debug("--------------用户登录系统:{},false:登录成功,true:登录失败----------------------", flag);
if (!flag) {
Subject subject = getSubject(request, response);
ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
// System.out.println(activeUser);
if (wdp.getKickout()) ShiroUtils.kickout(activeUser, s);
SystemLog systemLog = new SystemLog();
systemLog.setRequestip(ip).setUrl(request.getLocalAddr() + "/login").setHttpmethod("POST")
.setType("normal").setActionName("用户登录").setAction(ActionEnum.LOGIN.toString())
.setOpTime(new Date()).setOpUser(username).setDescription(ActionEnum.LOGIN + ":系统登入")
.setTenantId(activeUser != null ? activeUser.getTenantId() : "");
systemLogService.saveLog(systemLog);
return flag;
} else {
return true;
}
} else {
// sessionID已经注册,但是并没有使用post方式提交
return true;
}
} else {
saveRequestAndRedirectToLogin(request, response);
}
return false;
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
// if (isLoginRequest(request, response)) {
return super.isAccessAllowed(request, response, mappedValue);
// } else {
// String host = request.getParameter("host");
// if("1.1.1.1".equals(host) )
//
// return true;
// else
// return false;
// }
//return true;
}
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
WebUtils.getAndClearSavedRequest(request);
WebUtils.redirectToSavedRequest(request, response, "/");
return false;
}
}
— shiro 配置文件
package cn.etcom.security;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import cn.etcom.security.filter.CustomAccessFilter;
import cn.etcom.security.filter.CustomLogoutFilter;
import cn.etcom.security.filter.SysUserFilter;
import cn.etcom.security.listener.ShiroSessionListener;
import cn.etcom.util.base.constant.Constants;
import cn.etcom.web.properties.FileUploadProperteis;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Configuration
public class ShiroConfig {
private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);
private final static String algorithmName = "md5";
private final static int hashIterations = 2;
private final static String CACHE_KEY = Constants.CACHE_KEY;
private final static String SESSION_KEY = Constants.SESSION_KEY;
private final static int EXPIRE = 1800;
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.password}")
private String password;
@Autowired
private FileUploadProperteis fileUploadProperteis;
@Bean
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(this.host);
redisManager.setPort(port);
redisManager.setTimeout(timeout);
redisManager.setPassword(password);
return redisManager;
}
@Primary
@Bean("redisCacheManager")
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
redisCacheManager.setKeyPrefix(CACHE_KEY);
// 配置缓存的话要求放在session里面的实体类必须有个id标识
redisCacheManager.setPrincipalIdFieldName("userid");
//redisCacheManager.setValueSerializer(myRedisSerializer());
return redisCacheManager;
}
@Bean
public ShiroSessionIdGenerator sessionIdGenerator() {
return new ShiroSessionIdGenerator();
}
/*
@Bean
public MyRedisSerializer myRedisSerializer(){
return new MyRedisSerializer();
}
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
redisSessionDAO.setKeyPrefix(SESSION_KEY);
redisSessionDAO.setExpire(EXPIRE);
// redisSessionDAO.setValueSerializer(myRedisSerializer());
return redisSessionDAO;
}
/*
@Bean(name="ehCacheManager")
public EhCacheManager ehCacheManager() {
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile(cacheFile);
return cacheManager;
}
*/
/*
@Bean
public SimpleCookie getSimpleCookie() {
SimpleCookie cookie = new SimpleCookie("shiro.sesssion");
cookie.setMaxAge(EXPIRE);
cookie.setPath("/");
return cookie;
}
*/
@Bean("lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/*
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean(name = "credentialsMatcher")
public RetryLimitHashedCredentialsMatcher hashedCredentialsMatcher() {
RetryLimitHashedCredentialsMatcher hashedCredentialsMatcher = new RetryLimitHashedCredentialsMatcher();
// 散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName(algorithmName);
hashedCredentialsMatcher.setHashIterations(hashIterations);
return hashedCredentialsMatcher;
}
@Bean
public CustomRealm realm() {
CustomRealm shiroRealm = new CustomRealm();
shiroRealm.setCachingEnabled(true);
//启用身份验证缓存,即缓存AuthenticationInfo信息,默认false
//shiroRealm.setAuthenticationCachingEnabled(true);
shiroRealm.setAuthorizationCachingEnabled(true);
//shiroRealm.setAuthenticationCacheName("authenticationCache");
shiroRealm.setAuthorizationCacheName("authorizationCache");
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return shiroRealm;
}
@Bean
public SessionManager getDefaultWebSessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
/* Collection<SessionListener> listeners = new ArrayList<>();
listeners.add(sessionListener());
sessionManager.setSessionListeners(listeners);
sessionManager.setGlobalSessionTimeout(EXPIRE);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionIdUrlRewritingEnabled(false);
sessionManager.setSessionValidationInterval(10000);
sessionManager.setSessionValidationSchedulerEnabled(true);*/
//sessionManager.setSessionIdCookie(getSimpleCookie());
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm());
securityManager.setSessionManager(getDefaultWebSessionManager());
securityManager.setCacheManager(cacheManager());
return securityManager;
}
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// Shiro的核心安全接口
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
//shiroFilterFactoryBean.setSuccessUrl("/");
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
Map<String, Filter> m = new ConcurrentHashMap<>();
// 实现登出日志记录
m.put("logout", new CustomLogoutFilter());
m.put("authc", new CustomAccessFilter());
m.put("sysUser", new SysUserFilter());
shiroFilterFactoryBean.setFilters(m);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/favicon.ico", "anon");
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put(fileUploadProperteis.getStaticPath(),"anon");
filterChainDefinitionMap.put("/error", "anon");
filterChainDefinitionMap.put("/outside/access", "anon");
filterChainDefinitionMap.put("/outside/getToken", "anon");
filterChainDefinitionMap.put("/auth", "anon");
filterChainDefinitionMap.put("/login/sub", "anon");
filterChainDefinitionMap.put("/profile/*", "anon");
filterChainDefinitionMap.put("/swagger/**", "anon");
filterChainDefinitionMap.put("/resources/**", "anon");
filterChainDefinitionMap.put("/actuator", "anon");
filterChainDefinitionMap.put("/actuator/health", "anon");
filterChainDefinitionMap.put("/actuator/info", "anon");
filterChainDefinitionMap.put("/dept/findAll", "anon");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
/**
* 在线人统计
*
* @return
*/
@Bean("sessionListener")
public ShiroSessionListener sessionListener() {
ShiroSessionListener sessionListener = new ShiroSessionListener();
return sessionListener;
}
}
4. 处理这个问题的时候,我定位问题一直在后台的Filter的onLoginSuccess() 方法的重定向路径上。
其实问题的原因是:shiro中存在某种规则,onLoginSuccess() 方法第一次重定向后,浏览器路径会拼接:;JSESSIONID=XXXX 所以特殊字符; 导致访问请求变成:http://localhost:10003/;JSESSIONID=login_token_9b0ceaba-4448-469b-9652-a9eaae6abe10/。
导致异常400 会被项目异常拦截,然后访问error.html.
在error.html中存在setTimeOut定时器。2秒后跳转window.location.href="/"。这样会访问到index.html,然后如果登录成功会访问:/main 接口。如果登录失败会回到登录页面。
5. 根据问题点处理问题:
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
sessionManager.setSessionIdUrlRewritingEnabled(false);
6. 了解到的知识点:
-
我们写的CustomAccessFilter继承了FormAuthenticationFilter。实际的最开始需要重写的方法是:onAccessDenied()。实现核心的处理。但是如果不重写onLoginSuccess()这个方法,这个登录成功后访问的路径是:之前的存在session中的原请求,也就是login。浏览器会访问:http://localhost:10003/login
重写这个方法的时候,首先清理了原有的请求地址,然后重定向到 “/” ,如果没有;jsXXX 会访问到index.html ,然后访问/main 到main.html
-
第二点,就是;jsXXX这个东西拼接的地方在:ShiroHttpServletResponse 里。
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionIdUrlRewritingEnabled(false);
或者:新建MyShiroHttpServletResponse继承ShiroHttpServletResponse
重写其方法encodeRedirectURL和toEncoded
参考文章地址:
https://blog.csdn.net/ruanes/article/details/108417460
https://blog.csdn.net/weixin_34128534/article/details/92655476
https://blog.csdn.net/qq_39727959/article/details/106350249
https://www.cnblogs.com/sevenlin/p/sevenlin_shiro20150924.html
https://www.jianshu.com/p/a157c666159b
- 1
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)