公司很多系统用的都是同一个架构 , 现在要将多个服务整合在一起 ,需要做单点登录,下面是百度了很多抽离出来一些有用的自己组装起来的,由于很多代码都是不同的博主写的,时间关系没有记录,还望各位原博主见谅
该配置实现了各种情况的单点登录,后续使用ngix 负载均衡时session 的共享,以及同一时间同一个地方只能登录一次
1. 各个程序的shiro配置一定要一样 稍微的差别也可以 只要不影响整体就行(★★★★★ 一定要把shiro生成cookie的名字重命名;这是重点,这是重点,这是重点。因为默认为: JSESSIONID 问题: 与SERVLET容器名冲突, 如JETTY, TOMCAT 等默认JSESSIONID, 当跳出SHIRO SERVLET时会为JSESSIONID重新分配值导致登录会话丢失!)
下面是子系统spring-shiro.xml的配置:(登录服务器的配置大致相同,比下面的少了SSOFilter的相关配置,开放了autho, authe过滤器)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:property-placeholder location="classpath:env.properties"/> <bean id="kickoutSessionControlFilter" class="com.yundun.system.shiro.KickoutSessionControlFilter"> <!-- 注入缓存管理器 --> <property name="cacheManager" ref="cacheManager"/> <!-- 注入session管理器 --> <property name="sessionManager" ref="sessionManager"/> <property name="kickoutAfter" value="false"/> <property name="maxSession" value="1"/> <property name="kickoutUrl" value="?kickout=1"/> </bean> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/"/> <property name="filters"> <map> <!--<entry key="autho" value-ref="authorizationFilter"/>--> <!-- <entry key="authe" value-ref="authenticationFilter"/> --> <entry key="kickout" value-ref="kickoutSessionControlFilter"/> <entry key="sso" value-ref="SSOFilter"/> </map> </property> <property name="filterChainDefinitions"> <value> /system/getProjectConfigByConfigName=anon /static/**=anon /system/logout = anon /system/login=anon /system/toLoginJsp=anon /oauth/**=anon /userInterface/**=anon /error/**=anon /v2/**/=anon /webjars/**=anon /authorization/**=anon /swagger-resources/**=anon /swagger-ui.html/**=anon /**=sso,<!--autho, authe, -->kickout </value> </property> </bean> <!-- securityManager安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realms"> <list> <ref bean="shiroRealm"/> </list> </property> <!-- 注入缓存管理器 --> <property name="cacheManager" ref="cacheManager"/> <!-- 注入session管理器 --> <property name="sessionManager" ref="sessionManager"/> <!-- 记住我 --> </bean> <bean id="shiroRealm" class="com.yundun.system.shiro.ShiroRealm"> <property name="credentialsMatcher" ref="credentialsMatcher"/> </bean> <bean id="redisSessionDAO" class="com.yundun.system.shiro.RedisSessionDao"/> <bean id="cacheManager" class="com.yundun.system.shiro.RedisCacheManager"> <property name="redisTemplate" ref="redisTemplate"/> </bean> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="md5"/> <property name="hashIterations" value="2"/> </bean> <bean id="sessionManager" class="com.yundun.system.shiro.MyWebSessionManager"> <!-- 设置session的失效时间,单位为毫秒 --> <property name="globalSessionTimeout" value="${shiro.sessionTimeout}"/> <property name="sessionIdCookie" ref="simpleCookie"/> <!-- 设置session的失效扫描间隔,单位为毫秒 --> <property name="sessionValidationInterval" value="100000"/> <property name="sessionValidationSchedulerEnabled" value="true"/> <property name="sessionDAO" ref="redisSessionDAO"/> </bean> <!-- sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID --> <bean id="simpleCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <!-- 设置Cookie名字, 默认为: JSESSIONID 问题: 与SERVLET容器名冲突, 如JETTY, TOMCAT 等默认JSESSIONID, 当跳出SHIRO SERVLET时如ERROR-PAGE容器会为JSESSIONID重新分配值导致登录会话丢失! --> <property name="name" value="SHIRO-COOKIE"/> <!-- JSESSIONID的path为/用于多个系统共享JSESSIONID --> <property name="path" value="/"/> <!-- 浏览器中通过document.cookie可以获取cookie属性,设置了HttpOnly=true,在脚本中就不能的到cookie,可以避免cookie被盗用 --> <property name="httpOnly" value="true"/> </bean> <!--<bean id="authorizationFilter" class="com.yundun.system.shiro.ShiroAuthorizationFilter"/>--> <!-- <bean id="authenticationFilter" class="com.yundun.system.shiro.ShiroAuthenticationFilter"/> --> <bean id="SSOFilter" class="com.yundun.system.shiro.SSOFilter"> <property name="serverLoginUrl" value="${serverLoginUrl}"/> <!--serverLoginUrl登录服务器地址--> </bean> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> </beans>
2、子系统添加SSOFilter 过滤器(用于监测每次请求是否都是登录状态)
package com.yundun.system.shiro; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import com.user.model.dto.LoginInfo; public class SSOFilter implements Filter{ private String serverLoginUrl; @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; String sessionId = request.getParameter("sessionId"); HttpSession session = request.getSession(); String reqUrl = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getRequestURI(); LoginInfo loginInfo = (LoginInfo) session.getAttribute("loginInfo"); if(loginInfo!=null) { chain.doFilter(servletRequest, servletResponse); return; } //如果loginInfo为空 且 sessionId 不为空则证明已登录,然后创建一个网页传入cookie就能模拟已登录状态会话 if(loginInfo==null && sessionId != null) { Cookie cookie = new Cookie("SHIRO-COOKIE", sessionId); cookie.setPath("/"); response.addCookie(cookie); String html = "<html><head><script type=\"text/javascript\">location.href='"+reqUrl+"'</script></head><body></body></html>"; byte[] bytes = html.getBytes(); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); response.getOutputStream().write(bytes); response.getOutputStream().flush(); response.getOutputStream().close(); return; } //去登录服务器校验是否已登录如果已登录 自然会传回来sessionId,未登录就会直接在登录服务器那边跳转至登录页面 response.sendRedirect(serverLoginUrl + "/interfaceController/verifyIsLogin?redirectUrl=" + reqUrl); } @Override public void destroy() { } public String getServerLoginUrl() { return serverLoginUrl; } public void setServerLoginUrl(String serverLoginUrl) { this.serverLoginUrl = serverLoginUrl; } }
3、登录服务器的校验代码(不解释)
@RestController @RequestMapping("interfaceController") public class InterfaceController { @RequestMapping("verifyIsLogin") public void verifyIsLogin(HttpServletRequest request, HttpServletResponse response, String redirectUrl) { String sessionId = request.getSession().getId(); try { if (redirectUrl != null && redirectUrl != "") { response.sendRedirect(redirectUrl + "?sessionId=" + sessionId); } else { response.sendRedirect(request.getContextPath()); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
4.其他相关代码
4.1 同一时间只能在一个地方登录的代码 package com.yundun.system.shiro; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.session.Session; import org.apache.shiro.session.mgt.DefaultSessionKey; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.AccessControlFilter; import org.apache.shiro.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.user.dao.SysLoginStatusMapper; import com.user.model.entity.SysLoginStatus; import com.user.service.SystemService; import com.user.util.SystemConstant; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.Serializable; import java.util.Deque; import java.util.LinkedList; public class KickoutSessionControlFilter extends AccessControlFilter { private static final Logger log = LoggerFactory.getLogger(KickoutSessionControlFilter.class); @Autowired private SysLoginStatusMapper sysLoginStatusMapper; @Autowired private SystemService systemService; private String kickoutUrl; //踢出后到的地址 private boolean kickoutAfter = false; //踢出之前登录的/之后登录的用户 默认踢出之前登录的用户 private int maxSession = 1; //同一个帐号最大会话数 默认1 private SessionManager sessionManager; private Cache<String, Deque<Serializable>> cache; public void setKickoutUrl(String kickoutUrl) { this.kickoutUrl = kickoutUrl; } public void setKickoutAfter(boolean kickoutAfter) { this.kickoutAfter = kickoutAfter; } public void setMaxSession(int maxSession) { this.maxSession = maxSession; } public void setSessionManager(SessionManager sessionManager) { this.sessionManager = sessionManager; } public void setCacheManager(CacheManager cacheManager) { this.cache = cacheManager.getCache(SystemConstant.KICKOUT_CACHE); } @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { return false; } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { Subject subject = getSubject(request, response); if(!subject.isAuthenticated() && !subject.isRemembered()) { //如果没有登录,直接进行之后的流程 return true; } Session session = subject.getSession(); String username = (String) subject.getPrincipal(); Serializable sessionId = session.getId(); //TODO 同步控制 Deque<Serializable> deque = cache.get(SystemConstant.KICKOUT+username); if(deque == null) { deque = new LinkedList<Serializable>(); } //如果队列里没有此sessionId,且用户没有被踢出;放入队列 if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) { deque.push(sessionId); cache.put(SystemConstant.KICKOUT+username, deque); } //如果队列里的sessionId数超出最大会话数,开始踢人 while(deque.size() > maxSession) { Serializable kickoutSessionId = null; if(kickoutAfter) { //如果踢出后者 kickoutSessionId = deque.removeFirst(); } else { //否则踢出前者 kickoutSessionId = deque.removeLast(); } try { Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId)); if(kickoutSession != null) { //设置会话的kickout属性表示踢出了 kickoutSession.setAttribute("kickout", true); } } catch (Exception e) {//ignore exception } } cache.put(SystemConstant.KICKOUT+username, deque); //如果被踢出了,直接退出,重定向到踢出后的地址 if (session.getAttribute("kickout") != null) { //会话被踢出了 try { subject.logout(); } catch (Exception e) { //ignore log.error("KickoutSessionControlFilter报错====>"+e.toString()); } saveRequest(request); SysLoginStatus loginStatus = new SysLoginStatus(); loginStatus.setSessionId(deque.getFirst().toString()); SysLoginStatus sysLoginStatus = sysLoginStatusMapper.select(loginStatus); String url = ""; if(sysLoginStatus!=null) { url = "&ip="+sysLoginStatus.getLoginIp(); } String basePath = systemService.selectRedisCacheByConfigName("basePath"); WebUtils.issueRedirect(request, response,basePath+kickoutUrl+url); return false; } return true; } } 4.2,重写DefaultSessionManager package com.yundun.system.shiro; import org.apache.shiro.session.ExpiredSessionException; import org.apache.shiro.session.InvalidSessionException; import org.apache.shiro.session.Session; import org.apache.shiro.session.UnknownSessionException; import org.apache.shiro.session.mgt.DefaultSessionManager; import org.apache.shiro.session.mgt.DelegatingSession; import org.apache.shiro.session.mgt.SessionContext; import org.apache.shiro.session.mgt.SessionKey; import org.apache.shiro.web.servlet.Cookie; import org.apache.shiro.web.servlet.ShiroHttpServletRequest; import org.apache.shiro.web.servlet.ShiroHttpSession; import org.apache.shiro.web.servlet.SimpleCookie; import org.apache.shiro.web.session.mgt.WebSessionKey; import org.apache.shiro.web.session.mgt.WebSessionManager; import org.apache.shiro.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.Serializable; /** * * 自定义WebSessionManager,用于替代DefaultWebSessionManager; * 解决: * 在shiro的一次认证过程中会调用10次左右的 doReadSession,如果使用内存缓存这个问题不大。 * 但是如果使用redis,而且子网络情况不是特别好的情况下这就成为问题了。我简单在我的环境下测试了一下。 * 一次redis请求需要80 ~ 100 ms, 一下来10次,我们一次认证就需要10 * 100 = 1000 ms, 这个就是我们无法接受的了。 * * 大部分代码都是从DefaultWebSessionManager中复制过来的,扩展点在 line:200~212、225~229 * * @author * */ public class MyWebSessionManager extends DefaultSessionManager implements WebSessionManager { private static final Logger log = LoggerFactory.getLogger(MyWebSessionManager.class); private Cookie sessionIdCookie; private boolean sessionIdCookieEnabled; public MyWebSessionManager() { Cookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME); cookie.setHttpOnly(true); // more secure, protects against XSS attacks this.sessionIdCookie = cookie; this.sessionIdCookieEnabled = true; } public Cookie getSessionIdCookie() { return sessionIdCookie; } public void setSessionIdCookie(Cookie sessionIdCookie) { this.sessionIdCookie = sessionIdCookie; } public boolean isSessionIdCookieEnabled() { return sessionIdCookieEnabled; } public void setSessionIdCookieEnabled(boolean sessionIdCookieEnabled) { this.sessionIdCookieEnabled = sessionIdCookieEnabled; } private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) { if (currentId == null) { String msg = "sessionId cannot be null when persisting for subsequent requests."; throw new IllegalArgumentException(msg); } Cookie template = getSessionIdCookie(); Cookie cookie = new SimpleCookie(template); String idString = currentId.toString(); cookie.setValue(idString); cookie.saveTo(request, response); log.trace("Set session ID cookie for session with id {}", idString); } private void removeSessionIdCookie(HttpServletRequest request, HttpServletResponse response) { getSessionIdCookie().removeFrom(request, response); } private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) { if (!isSessionIdCookieEnabled()) { log.debug("Session ID cookie is disabled - session id will not be acquired from a request cookie."); return null; } if (!(request instanceof HttpServletRequest)) { log.debug("Current request is not an HttpServletRequest - cannot get session ID cookie. Returning null."); return null; } HttpServletRequest httpRequest = (HttpServletRequest) request; return getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response)); } private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) { String id = getSessionIdCookieValue(request, response); if (id != null) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE); } else { // not in a cookie, or cookie is disabled - try the request URI as a // fallback (i.e. due to URL rewriting): // try the URI path segment parameters first: id = getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME); if (id == null) { // not a URI path segment parameter, try the query parameters: String name = getSessionIdName(); id = request.getParameter(name); if (id == null) { // try lowercase: id = request.getParameter(name.toLowerCase()); } } if (id != null) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, ShiroHttpServletRequest.URL_SESSION_ID_SOURCE); } } if (id != null) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id); // automatically mark it valid here. If it is invalid, the // onUnknownSession method below will be invoked and we'll remove // the attribute at that time. request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); } return id; } // SHIRO-351 // also see // http://cdivilly.wordpress.com/2011/04/22/java-servlets-uri-parameters/ // since 1.2.2 private String getUriPathSegmentParamValue(ServletRequest servletRequest, String paramName) { if (!(servletRequest instanceof HttpServletRequest)) { return null; } HttpServletRequest request = (HttpServletRequest) servletRequest; String uri = request.getRequestURI(); if (uri == null) { return null; } int queryStartIndex = uri.indexOf('?'); if (queryStartIndex >= 0) { // get rid of the query string uri = uri.substring(0, queryStartIndex); } int index = uri.indexOf(';'); // now check for path segment parameters: if (index < 0) { // no path segment params - return: return null; } // there are path segment params, let's get the last one that may exist: final String TOKEN = paramName + "="; uri = uri.substring(index + 1); // uri now contains only the path // segment params // we only care about the last JSESSIONID param: index = uri.lastIndexOf(TOKEN); if (index < 0) { // no segment param: return null; } uri = uri.substring(index + TOKEN.length()); index = uri.indexOf(';'); // strip off any remaining segment params: if (index >= 0) { uri = uri.substring(0, index); } return uri; // what remains is the value } //------------------------------------------------------------------------------------------------------------------ /** * Modify By Goma */ protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException { Serializable sessionId = getSessionId(sessionKey); if (sessionId == null) { log.debug("Unable to resolve session ID from SessionKey [{}]. Returning null to indicate a " + "session could not be found.", sessionKey); return null; } // ***************Add By Goma**************** ServletRequest request = null; if (sessionKey instanceof WebSessionKey) { request = ((WebSessionKey) sessionKey).getServletRequest(); } if (request != null) { Object s = request.getAttribute(sessionId.toString()); if (s != null) { return (Session) s; } } // ***************Add By Goma**************** Session s = retrieveSessionFromDataSource(sessionId); if (s == null) { // session ID was provided, meaning one is expected to be found, but // we couldn't find one: String msg = "Could not find session with ID [" + sessionId + "]"; throw new UnknownSessionException(msg); } // ***************Add By Goma**************** if (request != null) { request.setAttribute(sessionId.toString(),s); } // ***************Add By Goma**************** return s; } //------------------------------------------------------------------------------------------------------------------ // since 1.2.1 private String getSessionIdName() { String name = this.sessionIdCookie != null ? this.sessionIdCookie.getName() : null; if (name == null) { name = ShiroHttpSession.DEFAULT_SESSION_ID_NAME; } return name; } protected Session createExposedSession(Session session, SessionContext context) { if (!WebUtils.isWeb(context)) { return super.createExposedSession(session, context); } ServletRequest request = WebUtils.getRequest(context); ServletResponse response = WebUtils.getResponse(context); SessionKey key = new WebSessionKey(session.getId(), request, response); return new DelegatingSession(this, key); } protected Session createExposedSession(Session session, SessionKey key) { if (!WebUtils.isWeb(key)) { return super.createExposedSession(session, key); } ServletRequest request = WebUtils.getRequest(key); ServletResponse response = WebUtils.getResponse(key); SessionKey sessionKey = new WebSessionKey(session.getId(), request, response); return new DelegatingSession(this, sessionKey); } /** * Stores the Session's ID, usually as a Cookie, to associate with future * requests. * * @param session * the session that was just {@link #createSession created}. */ @Override protected void onStart(Session session, SessionContext context) { super.onStart(session, context); if (!WebUtils.isHttp(context)) { log.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response " + "pair. No session ID cookie will be set."); return; } HttpServletRequest request = WebUtils.getHttpRequest(context); HttpServletResponse response = WebUtils.getHttpResponse(context); if (isSessionIdCookieEnabled()) { Serializable sessionId = session.getId(); storeSessionId(sessionId, request, response); } else { log.debug("Session ID cookie is disabled. No cookie has been set for new session with id {}", session.getId()); } request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE); } @Override public Serializable getSessionId(SessionKey key) { Serializable id = super.getSessionId(key); if (id == null && WebUtils.isWeb(key)) { ServletRequest request = WebUtils.getRequest(key); ServletResponse response = WebUtils.getResponse(key); id = getSessionId(request, response); } return id; } protected Serializable getSessionId(ServletRequest request, ServletResponse response) { return getReferencedSessionId(request, response); } @Override protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) { super.onExpiration(s, ese, key); onInvalidation(key); } @Override protected void onInvalidation(Session session, InvalidSessionException ise, SessionKey key) { super.onInvalidation(session, ise, key); onInvalidation(key); } private void onInvalidation(SessionKey key) { ServletRequest request = WebUtils.getRequest(key); if (request != null) { request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID); } if (WebUtils.isHttp(key)) { log.debug("Referenced session was invalid. Removing session ID cookie."); removeSessionIdCookie(WebUtils.getHttpRequest(key), WebUtils.getHttpResponse(key)); } else { log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " + "pair. Session ID cookie will not be removed due to invalidated session."); } } @Override protected void onStop(Session session, SessionKey key) { super.onStop(session, key); if (WebUtils.isHttp(key)) { HttpServletRequest request = WebUtils.getHttpRequest(key); HttpServletResponse response = WebUtils.getHttpResponse(key); log.debug("Session has been stopped (subject logout or explicit stop). Removing session ID cookie."); removeSessionIdCookie(request, response); } else { log.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " + "pair. Session ID cookie will not be removed due to stopped session."); } } /** * This is a native session manager implementation, so this method returns * {@code false} always. * * @return {@code false} always * @since 1.2 */ public boolean isServletContainerSessions() { return false; } } 4.3 重写shiro cache package com.yundun.system.shiro; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.RedisTemplate; import com.user.util.SystemConstant; import java.io.InputStream; import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.concurrent.TimeUnit; /** * @Author 王俊强 * @Description Cache redis实现 */ public class RedisCache<K, V> implements Cache<K, V>, Serializable { private static long timeout ; private transient static Logger log = LoggerFactory.getLogger(RedisCache.class); static{ Properties prop = new Properties(); InputStream in = RedisSessionDao.class.getResourceAsStream("/env.properties"); try { prop.load(in); timeout = Integer.parseInt(prop.getProperty("shiro.sessionTimeout"))/1000; } catch (Exception e) { log.error("RedisSessionDao获取properties文件内容出错"+e); } } private transient RedisTemplate<K, V> redisTemplate; public RedisCache(RedisTemplate<K, V> redisTemplate) { this.redisTemplate = redisTemplate; } public RedisCache() { } @Override public V get(K key) throws CacheException { log.debug("根据key:{}从redis获取对象", key); log.debug("redisTemplate : {}", redisTemplate); return redisTemplate.opsForValue().get(SystemConstant.SHIRO_CACHE_PREFIX + key); } @Override public V put(K key, V value) throws CacheException { log.debug("根据key:{}从redis添加对象", key); redisTemplate.opsForValue().set((K) (SystemConstant.SHIRO_CACHE_PREFIX + key), value, timeout, TimeUnit.SECONDS); return value; } /** * 执行set操作并且设置生存时间,单位为:秒 * * @param key * @param value * @return */ public void setValueTime(final String key, final String value, final Integer timeOut) { redisTemplate.opsForValue().set((K) (SystemConstant.SHIRO_CACHE_PREFIX + key), (V) value, timeOut, TimeUnit.SECONDS); } @Override public V remove(K key) throws CacheException { log.debug("redis cache remove :{}", key.toString()); V value = redisTemplate.opsForValue().get(SystemConstant.SHIRO_CACHE_PREFIX + key); redisTemplate.delete(key); return value; } @Override public void clear() throws CacheException { log.debug("清除redis所有缓存对象"); Set<K> keys = redisTemplate.keys((K) SystemConstant.SHIRO_CACHE_PREFIX_KEYS); redisTemplate.delete(keys); } @Override public int size() { Set<K> keys = redisTemplate.keys((K) SystemConstant.SHIRO_CACHE_PREFIX_KEYS); log.debug("获取redis缓存对象数量:{}", keys.size()); return keys.size(); } @Override public Set<K> keys() { Set<K> keys = redisTemplate.keys((K)SystemConstant.SHIRO_CACHE_PREFIX_KEYS); log.debug("获取所有缓存对象的key"); if (keys.size() == 0) { return Collections.emptySet(); } return keys; } @Override public Collection<V> values() { Set<K> keys = redisTemplate.keys((K) SystemConstant.SHIRO_CACHE_PREFIX_KEYS); log.debug("获取所有缓存对象的value"); if (keys.size() == 0) { return Collections.emptySet(); } List<V> vs = redisTemplate.opsForValue().multiGet(keys); return Collections.unmodifiableCollection(vs); } public RedisTemplate<K, V> getRedisTemplate() { return redisTemplate; } public void setRedisTemplate(RedisTemplate<K, V> redisTemplate) { this.redisTemplate = redisTemplate; } } 4.4 重写CacheManager package com.yundun.system.shiro; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.RedisTemplate; import com.user.util.SystemConstant; import java.io.Serializable; /** * @Author 王俊强 * @Description 接口实现 */ public class RedisCacheManager implements CacheManager, Serializable { private transient static Logger log = LoggerFactory.getLogger(RedisCacheManager.class); private transient RedisTemplate<Object, Object> redisTemplate; public RedisCacheManager() { } public RedisCacheManager(RedisTemplate<Object, Object> redisTemplate) { this.redisTemplate = redisTemplate; } @Override public <K, V> Cache<K, V> getCache(String name) throws CacheException { if (!StringUtils.hasText(name)) { throw new IllegalArgumentException("Cache name cannot be null or empty."); } log.debug("redis cache manager get cache name is :{}", name); Cache cache = (Cache) redisTemplate.opsForValue().get(name); if (cache == null) { cache = new RedisCache<>(redisTemplate); redisTemplate.opsForValue().set(SystemConstant.SHIRO_CACHE_PREFIX + name, cache); } return cache; } public void setRedisTemplate(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } } 4.5 重写AbstractSessionDAO package com.yundun.system.shiro; import org.apache.shiro.session.Session; import org.apache.shiro.session.UnknownSessionException; import org.apache.shiro.session.mgt.eis.AbstractSessionDAO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import com.user.model.dto.LoginInfo; import com.user.util.SystemConstant; import java.io.InputStream; import java.io.Serializable; import java.util.*; import java.util.concurrent.TimeUnit; /** * @Author 王俊强 * @Description session redis实现 cookie生成 已经redis key存活时间变更 */ public class RedisSessionDao extends AbstractSessionDAO { private static long timeout; private transient static Logger log = LoggerFactory.getLogger(RedisSessionDao.class); static{ Properties prop = new Properties(); InputStream in = RedisSessionDao.class.getResourceAsStream("/env.properties"); try { prop.load(in); timeout = Integer.parseInt(prop.getProperty("shiro.sessionTimeout"))/1000; } catch (Exception e) { // TODO Auto-generated catch block log.error("RedisSessionDao获取properties文件内容出错"+e); } } @Autowired private transient RedisTemplate<Serializable, Session> redisTemplate; //在用户首次访问时创建一个shiro session protected Serializable doCreate(Session session) { Serializable sessionId = SystemConstant.SHIRO_CACHE_PREFIX + UUID.randomUUID().toString(); //往session中指派ID this.assignSessionId(session, sessionId); redisTemplate.opsForValue().set(sessionId, session, timeout, TimeUnit.SECONDS); log.info("create shiro session ,sessionId is :{}", sessionId.toString()); return sessionId; } @Override protected Session doReadSession(Serializable sessionId) { if (sessionId == null) { return null; } return redisTemplate.opsForValue().get(sessionId); } @Override public void update(Session session) throws UnknownSessionException { LoginInfo loginInfo = (LoginInfo) session.getAttribute("loginInfo"); if(loginInfo!=null) { String kickoutName = SystemConstant.SHIRO_CACHE_PREFIX+SystemConstant.KICKOUT+loginInfo.getLoginName(); redisTemplate.expire(kickoutName,timeout, TimeUnit.SECONDS); String catchName = SystemConstant.SHIRO_CACHE_PREFIX+loginInfo.getLoginName(); redisTemplate.expire(catchName,timeout, TimeUnit.SECONDS); } redisTemplate.opsForValue().set(session.getId(), session, timeout, TimeUnit.SECONDS); } @Override public void delete(Session session) { LoginInfo loginInfo = (LoginInfo) session.getAttribute("loginInfo"); //删除登录唯一验证的缓存 String kickoutName = SystemConstant.SHIRO_CACHE_PREFIX + SystemConstant.KICKOUT+loginInfo.getLoginName(); redisTemplate.opsForValue().getOperations().delete(kickoutName); //删除用户缓存 String catchName = SystemConstant.SHIRO_CACHE_PREFIX + loginInfo.getLoginName(); redisTemplate.opsForValue().getOperations().delete(catchName); //删除用户session redisTemplate.opsForValue().getOperations().delete(session.getId()); } @Override public Collection<Session> getActiveSessions() { Set<Serializable> keys = redisTemplate.keys(SystemConstant.SHIRO_CACHE_PREFIX_KEYS); if (keys.size() == 0) { return Collections.emptySet(); } List<Session> sessions = redisTemplate.opsForValue().multiGet(keys); return Collections.unmodifiableCollection(sessions); } } 4.6 重写PassThruAuthenticationFilter package com.yundun.system.shiro; import com.google.gson.Gson; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.SecurityUtils; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.user.model.dto.LoginInfo; import com.user.service.SystemService; import com.user.util.CookieUtils; import com.user.util.ResponseCode; import com.user.util.Result; import com.user.util.SystemConstant; import java.io.Serializable; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * @Author 王俊强 * @Description 登录过滤器 未启用 */ public class ShiroAuthenticationFilter extends PassThruAuthenticationFilter { private static Logger log = LoggerFactory.getLogger(ShiroAuthorizationFilter.class); @Autowired private SystemService systemService; @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { if (isLoginRequest(request, response)) { return true; } else { saveRequest(request); if (((HttpServletRequest) request).getHeader("Accept").contains("application/json")) { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json;charset=UTF-8"); Result result = new Result(ResponseCode.unauthenticated.getCode(), ResponseCode.unauthenticated.getMsg()); response.getWriter().append(new Gson().toJson(result)); response.getWriter().flush(); response.getWriter().close(); } else { response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); String basePath = systemService.selectRedisCacheByConfigName("basePath"); basePath += "?timeOut=1"; ((HttpServletResponse) response).sendRedirect(basePath); } return false; } } } 4.6 重写AuthorizationFilter package com.yundun.system.shiro; import com.google.gson.Gson; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.authz.AuthorizationFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.user.service.SystemService; import com.user.util.ResponseCode; import com.user.util.Result; import com.user.util.StringUtil; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @Author 王俊强 * @Description 权限过滤器 未启用 */ public class ShiroAuthorizationFilter extends AuthorizationFilter { private static Logger log = LoggerFactory.getLogger(ShiroAuthorizationFilter.class); @Autowired private SystemService systemService; @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException { HttpServletResponse response1 = (HttpServletResponse) response; Subject subject = getSubject(request, response); if (subject.getPrincipal() == null) { //未登录跳转至登陆页面 saveRequest(request); if (((HttpServletRequest) request).getHeader("Accept").contains("application/json")) { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json;charset=UTF-8"); Result result = new Result(ResponseCode.unauthenticated.getCode(), ResponseCode.unauthenticated.getMsg()); response.getWriter().append(new Gson().toJson(result)); response.getWriter().flush(); response.getWriter().close(); } else { response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); String basePath = systemService.selectRedisCacheByConfigName("basePath"); ((HttpServletResponse) response).sendRedirect(basePath); } } else { //已登录未授权 if (((HttpServletRequest) request).getHeader("Accept").contains("application/json")) { log.debug("授权认证:未通过:json"+((HttpServletRequest) request).getRequestURL()); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json;charset=UTF-8"); Result result = new Result(ResponseCode.unauthorized.getCode(), ResponseCode.unauthorized.getMsg()); response.getWriter().append(new Gson().toJson(result)); response.getWriter().flush(); response.getWriter().close(); } else { log.debug("授权认证:未通过:web"+((HttpServletRequest) request).getRequestURL()); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); ((HttpServletResponse) response).sendRedirect("/error/unAuthorization"); } } return false; } @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { Subject subject = getSubject(request, response); String[] perms = (String[]) mappedValue; boolean isPermitted = true; if (perms != null && perms.length > 0) { if (perms.length == 1) { if (!subject.isPermitted(perms[0])) { log.debug("授权认证:未通过"); isPermitted = false; } } else { if (!subject.isPermittedAll(perms)) { log.debug("授权认证:未通过"); isPermitted = false; } } } return isPermitted; } } 4.7 重写AuthorizingRealm package com.yundun.system.shiro; import com.user.dao.*; import com.user.model.entity.*; import com.user.util.SystemConstant; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import java.util.HashSet; import java.util.List; import java.util.Set; /** * @Author: 王俊强 * @Description 自定义realm实现 */ public class ShiroRealm extends AuthorizingRealm { private static Logger log = LoggerFactory.getLogger(ShiroRealm.class); @Autowired private SysUserMapper sysUserMapper; @Autowired private SysPermissionMapper sysPermissionMapper; @Autowired private SysRoleMapper sysRoleMapper; @Autowired private RedisTemplate<Object, Object> redisTemplate; /** * 鉴权信息 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { log.debug("开始查询授权信息"); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); String loginStr = (String) principalCollection.getPrimaryPrincipal(); SysUser user = sysUserMapper.selectUserByLoginName(loginStr); //获取用户对应的角色 SysRole sysRole = sysRoleMapper.selectByUserId(user.getId()); Set<String> roles = new HashSet<>(); roles.add(sysRole.getName()); info.addRoles(roles); //获取用户对应的权限 List<SysPermission> sysPermissionList = sysPermissionMapper.selectByRoleId(sysRole.getId()); Set<String> permissions = new HashSet<>(); for (SysPermission sysPermission : sysPermissionList) { permissions.add(sysPermission.getCode()); } info.addStringPermissions(permissions); log.debug("角色信息: \n {}", roles.toString()); log.debug("权限信息: \n{}", permissions.toString()); return info; } /** * 登录验证 * * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { log.debug("登录验证"); String loginName = (String) authenticationToken.getPrincipal(); SysUser sysUser = sysUserMapper.selectUserByLoginName(loginName); AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(loginName, sysUser.getPassword(), ByteSource.Util.bytes(sysUser.getPasswordSalt()), getName()); return authenticationInfo; } @Override protected void doClearCache(PrincipalCollection principals) { redisTemplate.delete(SystemConstant.SHIRO_CACHE_PREFIX + principals.getPrimaryPrincipal().toString()); } @Override protected void clearCachedAuthorizationInfo(PrincipalCollection principals) { log.debug("clearCachedAuthorizationInfo"); } }
