1.今年3月份使用了shiro搭建springboot+shiro+cas,遇到过一些问题,好在都解决了(https://www.cnblogs.com/thinkingandworkinghard/p/12596443.html)。 这几天公司又有项目要重新搭建,于是决定再重新搭建。之前使用时候没有使用脚手架,不得不重新搭建。 然后一个个的复制粘贴。。。 好在有同事搞了个脚手架的工具,整个目录改了后直接生成项目。改改包名、改改数据库配置之类的,直接新搞了一套
但是h5那边在开发时候,自己也搭建了一套服务,导致部分配置和之前的不太兼容。原来想着简单改改就行了,然而又遇到了几个问题,都是之前没遇到过的,好在最后都解决了,再次记录下。
1.跨域问题
之前也遇到过跨域的问题,不过这次有些不太一样,之前的登录接口是登录后直接通过redirect 到h5的首页地址,但是这次用code码让h5来控制了跳转。下面是区别:
h5那边估计是因为调整了框架的缘故,他们自己控制跳转。这样就和之前的不兼容了,而且发现个问题是这样的: 每次登陆成功后的sessionId 和 其他接口在客户端产生的不一致。也就是登录时候客户端产生了一个cookie,在访问其他接口时候又产生了一个cookie。 原因就是新的项目是h5控制了页面的跳转,登录成功后他们和服务器交互时候产生了新的cookie,这个cookie是客户端的域名下产生的,和服务器的不是一个。可以通过domain的属性查看
于是就在后端的接口里面添加了返回值,把后端生成的sessionId返回给了前端。跨域问题仍然是在Nginx里面设置。
2. 登录顶掉后h5端无法删除cookie的问题
这个问题是这样的:同一个账号,在不同的电脑登录。我们在代码里面设置了踢掉的功能,就是一个账号不允许同时登录。因为服务器第二次产生的sessionId 会和第一次不同,一般如果使用shiro,当修改了权限之类的,会把服务器的sessionId清空,所以设置了踢掉的功能。然而这次由于是通过code码的方式,发现h5的 sessionId 在第一次登录后会一直清除不掉,在Google下的cookie里面 看了下domain的域名,是后端产生的。第一次登录后后端产生了一个session,第二次在其他电脑登录又产生了一个,这个时候服务器已经清除了sessionId,但是客户端的域名由于是服务器产生的,他们清除不掉。 当第三次登录时候,就会出现登录后仍然提示你需要重新登录。 一直循环,原因就是客户端用的是第一次的cookieId,服务器是新生成的
产生的原因是shiro的源码里面,有一段redirect的跳转,源码如下:
FormAuthenticationFilter.java的 onAccessDenied 方法:
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { if (isLoginRequest(request, response)) { if (isLoginSubmission(request, response)) { if (log.isTraceEnabled()) { log.trace("Login submission detected. Attempting to execute login."); } return executeLogin(request, response); } else { if (log.isTraceEnabled()) { log.trace("Login page view."); } //allow them to see the login page ;) return true; } } else { if (log.isTraceEnabled()) { log.trace("Attempting to access a path which requires authentication. Forwarding to the " + "Authentication url [" + getLoginUrl() + "]"); } //此处会重定向 saveRequestAndRedirectToLogin(request, response); return false; } }
protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
saveRequest(request);
redirectToLogin(request, response);
}
protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
String loginUrl = getLoginUrl();
WebUtils.issueRedirect(request, response, loginUrl);
}
public static void issueRedirect(ServletRequest request, ServletResponse response, String url) throws IOException {
issueRedirect(request, response, url, null, true, true);
}
public static void issueRedirect(ServletRequest request, ServletResponse response, String url, Map queryParams, boolean contextRelative, boolean http10Compatible) throws IOException {
RedirectView view = new RedirectView(url, contextRelative, http10Compatible);
view.renderMergedOutputModel(queryParams, toHttp(request), toHttp(response));
}
//renderMergedOutputModel 最后重定向了
public final void renderMergedOutputModel(
Map model, HttpServletRequest request, HttpServletResponse response) throws IOException {
// Prepare name URL.
StringBuilder targetUrl = new StringBuilder();
if (this.contextRelative && getUrl().startsWith("/")) {
// Do not apply context path to relative URLs.
targetUrl.append(request.getContextPath());
}
targetUrl.append(getUrl());
//change the following method to accept a StringBuilder instead of a StringBuilder for Shiro 2.x:
appendQueryProperties(targetUrl, model, this.encodingScheme);
sendRedirect(request, response, targetUrl.toString(), this.http10Compatible);
}
就是因为这个重定向导致当第三次登录时候,服务器端先产生了sessionId,当被踢掉访问接口时候,他会先重定向一次,重定向的地址就是我们在配置里面的url.默认的是 unauthorized
@Bean(name = "shiroFilter") public PlatformShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) { PlatformShiroFilterFactoryBean shiroFilterFactoryBean = new PlatformShiroFilterFactoryBean(); Map<String,Filter> filterMap=shiroFilterFactoryBean.getFilters(); filterMap.put("formAuth",new ShiroFormAuthenticationFilter()); shiroFilterFactoryBean.setSecurityManager(securityManager); //这个是重定向的地址 shiroFilterFactoryBean.setLoginUrl("/unauthorized"); shiroFilterFactoryBean.setSuccessUrl(homepageUrl); //注意此处使用的是LinkedHashMap,是有顺序的,shiro会按从上到下的顺序匹配验证,匹配了就不再继续验证 //所以上面的url要苛刻,宽松的url要放在下面,尤其是"/**"要放到最下面,如果放前面的话其后的验证规则就没作用了。 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/nginx.html", "anon"); filterChainDefinitionMap.put("/common/filesUpload", "anon"); filterChainDefinitionMap.put("/common/upload", "anon"); filterChainDefinitionMap.put("/loginoutController/*","anon"); filterChainDefinitionMap.put("/authManage/*","anon"); filterChainDefinitionMap.put("/dispatcher/changeStatus", "anon"); filterChainDefinitionMap.put("/driver/getUnderWayDriver", "anon"); filterChainDefinitionMap.put("/getAllMerchants", "anon"); filterChainDefinitionMap.put("/dologin", "anon"); filterChainDefinitionMap.put("/updateLevel", "anon"); filterChainDefinitionMap.put("/permission/levelList", "anon"); filterChainDefinitionMap.put("/mp/car/batchImport", "anon"); filterChainDefinitionMap.put("/mp/index/merchantIdstatisticsinfo", "anon"); filterChainDefinitionMap.put("/mp/index/driverrankdaylist", "anon"); filterChainDefinitionMap.put("/mp/index/statisticsinfo", "anon"); //注意此处:如果是h5放开的话 会出现跨域问题 filterChainDefinitionMap.put("/unauthorized", "anon"); filterChainDefinitionMap.put("/getMsgCode", "anon"); filterChainDefinitionMap.put("/dologout", "anon"); filterChainDefinitionMap.put("/logout.html", "logout"); filterChainDefinitionMap.put("/**", "formAuth"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; }
正是由于这次的重定向,让后端每次被踢掉后产生了新的sessionId,但是服务器端仍然是第一次的,所以一直产生了这个。而我之前三月份的项目,由于都是服务端redirect的,客户端没有产生过这样的问题,
这个解决方案需要两块:一块是要重写 FormAuthenticationFilter.java 的 onAccessDenied方法,让其不产生重定向;另外需要在配置文件设置,在添加重定向时候禁止生成session。
我们写的配置如下:
package com.sq.transportmanage.gateway.service.common.shiro.filter; import com.alibaba.fastjson.JSON; import com.sq.transportmanage.gateway.service.common.web.AjaxResponse; import com.sq.transportmanage.gateway.service.common.web.RestErrorCode; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpStatus; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; import org.apache.shiro.web.util.WebUtils; import org.springframework.beans.factory.annotation.Value; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @Author:admin * @Date:2019/9/6 如果是ajax请求,header头部直接返回403 * @Description: */ public class ShiroFormAuthenticationFilter extends FormAuthenticationFilter { @Value(value = "${homepage.url}") private String homePageUrl; public ShiroFormAuthenticationFilter() { super(); } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { HttpServletResponse rep = toHttp(response); HttpServletRequest reque = (HttpServletRequest)request; String url=reque.getRequestURI(); System.out.print("url===================="+url); if(StringUtils.isNotBlank(url)&& url.contains("dologin")){ return true; } Subject currentLoginUser = SecurityUtils.getSubject(); if(currentLoginUser.isAuthenticated()){ return true; }else{ rep.setStatus(200); //去掉重定向 修改为返回状态码 前端拦截到登陆页面 AjaxResponse ajaxResponse = AjaxResponse.fail(RestErrorCode.HTTP_INVALID_SESSION); rep.setCharacterEncoding("UTF-8");//设置服务器的编码,默认是ISO-8859-1 rep.setContentType("text/html; charset = utf-8");//告诉浏览器服务器的编码格式 rep.getWriter().write(JSON.toJSONString(ajaxResponse)); return false; } /* return false;*/ } /** * 是否是ajax请求 * @param request * @return */ private boolean isAjax(HttpServletRequest request){ String requestHeader = request.getHeader("X-Requested-With"); return "XMLHttpRequest".equals(requestHeader); } }
禁止服务器端在重定向时候禁止生成sessionId,这样被踢掉后就不会产生服务器的sessionId
@Bean("sessionManager")
public DefaultWebSessionManager sessionManager(RedisSessionDAO sessionDAO, SimpleCookie sessionIdCookie, CacheManager cacheManager) {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
//session存活时间60分钟
sessionManager.setGlobalSessionTimeout(Constants.SESSION_REPIRE_TIME);
/**是否开启删除无效的session对象 默认为true 此处改为不开启*/
sessionManager.setDeleteInvalidSessions(false);
/**是否开启定时调度器进行检测过期session 默认为true 此处改为不检测*/
sessionManager.setSessionValidationSchedulerEnabled(false);
//自定义监听 fht 不能使用@WebListern的 HttpSessionListerner 因为shiro重写了session 2020-03-05
Collection<SessionListener> sessionListeners = new ArrayList<>();
sessionListeners.add(sessionListener());
sessionManager.setSessionListeners(sessionListeners);
sessionManager.setSessionDAO(sessionDAO);
sessionManager.setSessionIdCookieEnabled(true);
sessionManager.setSessionIdCookie(sessionIdCookie);
sessionManager.setCacheManager(cacheManager);
/*添加重定向时候禁止生成session**/
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
而且测试发现,这两个条件都不能去掉。理论上登录拒绝已经没有重定向了,这个重定向禁止生成sessionId应该是没必要在设置了。但是测试发现这两个都需要配置。