shiro + spring + redis單點登錄


公司很多系統用的都是同一個架構 , 現在要將多個服務整合在一起 ,需要做單點登錄,下面是百度了很多抽離出來一些有用的自己組裝起來的,由於很多代碼都是不同的博主寫的,時間關系沒有記錄,還望各位原博主見諒

該配置實現了各種情況的單點登錄,后續使用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");
    }

}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM