cas 3.5.3服務器搭建+spring boot集成+shiro模擬登陸(不修改現有shiro認證架構)。由於咱們屬於供應商,因此有些客戶那裏會須要接對方的CAS,因此沒有使用shiro和cas的直接集成模式,若是是這種狀況,能夠參考:https://blog.csdn.net/catoop/article/details/50534006。html
Cas Client主要有四個核心過濾器:java
l AuthenticationFilterspring
l TicketValidationFilterapi
l HttpServletRequestWrapperFilter服務器
l AssertionThreadLocalFiltersession
他們的做用解釋以下:架構
AuthenticationFilter用來攔截全部的請求,用以判斷用戶是否須要經過Cas Server進行認證,若是須要則將跳轉到Cas Server的登陸頁面。若是不須要進行登陸認證,則請求會繼續往下執行。oracle
AuthenticationFilter有兩個用戶必須指定的參數,一個是用來指定Cas Server登陸地址的casServerLoginUrl,另外一個是用來指定認證成功後須要跳轉地址的serverName或service。service和serverName只須要指定一個就能夠了。當二者都指定了,參數service將具備更高的優先級,即將以service指定的參數值爲準。service和serverName的區別在於service指定的是一個肯定的URL,認證成功後就會確切的跳轉到service指定的URL;而serverName則是用來指定主機名,其格式爲{protocol}:{hostName}:{port},如:https://localhost:8443,當指定的是serverName時,AuthenticationFilter將會把它附加上當前請求的URI,以及對應的查詢參數來構造一個肯定的URL,如指定serverName爲「http://localhost」,而當前請求的URI爲「/app」,查詢參數爲「a=b&b=c」,則對應認證成功後的跳轉地址將爲「http://localhost/app?a=b&b=c」。app
除了上述必須指定的參數外,AuthenticationFilter還能夠指定以下可選參數:oop
l renew:當指定renew爲true時,在請Cas Server時將帶上參數「renew=true」,默認爲false。
l gateway:指定gateway爲true時,在請求Cas Server時將帶上參數「gateway=true」,默認爲false。
l artifactParameterName:指定ticket對應的請求參數名稱,默認爲ticket。
l serviceParameterName:指定service對應的請求參數名稱,默認爲service。
在請求經過AuthenticationFilter的認證以後,若是請求中攜帶了參數ticket則將會由TicketValidationFilter來對攜帶的ticket進行校驗。TicketValidationFilter只是對驗證ticket的這一類Filter的統稱,其並不對應Cas Client中的一個具體類型。Cas Client中有多種驗證ticket的Filter,都繼承自AbstractTicketValidationFilter,它們的驗證邏輯都是一致的,都有AbstractTicketValidationFilter實現,所不一樣的是使用的TicketValidator不同。默認是Cas10TicketValidationFilter。
可選參數包括:
l redirectAfterValidation :表示是否驗證經過後從新跳轉到該URL,可是不帶參數ticket,默認爲true。
l useSession :在驗證ticket成功後會生成一個Assertion對象,若是useSession爲true,則會將該對象存放到Session中。若是爲false,則要求每次請求都須要攜帶ticket進行驗證,顯然useSession爲false跟redirectAfterValidation爲true是衝突的。默認爲true。
l exceptionOnValidationFailure :表示ticket驗證失敗後是否須要拋出異常,默認爲true。
l renew:當值爲true時將發送「renew=true」到Cas Server,默認爲false。
HttpServletRequestWrapperFilter用於將每個請求對應的HttpServletRequest封裝爲其內部定義的CasHttpServletRequestWrapper,該封裝類將利用以前保存在Session或request中的Assertion對象重寫HttpServletRequest的getUserPrincipal()、getRemoteUser()和isUserInRole()方法。這樣在咱們的應用中就能夠很是方便的從HttpServletRequest中獲取到用戶的相關信息。
AssertionThreadLocalFilter是爲了方便用戶在應用的其它地方獲取Assertion對象,其會將當前的Assertion對象存放到當前的線程變量中,那麼之後用戶在程序的任何地方均可以從線程變量中獲取當前Assertion,無需再從Session或request中進行解析。該線程變量是由AssertionHolder持有的,咱們在獲取當前的Assertion時也只須要經過AssertionHolder的getAssertion()方法獲取便可,如:
Assertion assertion = AssertionHolder.getAssertion();
cas client/shiro過濾器順序,集成cas的狀況下,cas客戶端老是第一個過濾器。
org.jasig.cas.client.validation.AbstractTicketValidationFilter#doFilter
org.jasig.cas.client.util.CommonUtils#safeGetParameter
org.jasig.cas.client.authentication.AuthenticationFilter#doFilter --在這裏修改源碼,拿到assertion、且爲空後須要判斷token是否存在且有效,若是存在且有效,說明已經登陸過,則直接返回
org.jasig.cas.client.util.CommonUtils#constructRedirectUrl
javax.servlet.http.HttpServletResponse#sendRedirect --此時請求去了CAS
最後進入shiro過濾器UserFilter.isAccessAllowed
由於在userfilter裏面標準的cas登陸後,是能夠經過UserPrincipal拿到當前的用戶信息的,可是當咱們是集羣模式的時候由於直接在AuthenticationFilter#doFilter中攔截返回了,且沒有明確設置,天然就拿不到了,servletrequest中沒有提供設置UserPrincipal的入口,cas的org.jasig.cas.client.validation.Cas20ServiceTicketValidator#parseResponseFromServer是建立了,可是org.jasig.cas.client.validation.Cas20ServiceTicketValidator#customParseResponse的實現體是空的。以下:
protected final Assertion parseResponseFromServer(final String response) throws TicketValidationException { final String error = XmlUtils.getTextForElement(response, "authenticationFailure"); if (CommonUtils.isNotBlank(error)) { throw new TicketValidationException(error); } final String principal = XmlUtils.getTextForElement(response, "user"); final String proxyGrantingTicketIou = XmlUtils.getTextForElement(response, "proxyGrantingTicket"); final String proxyGrantingTicket = this.proxyGrantingTicketStorage != null ? this.proxyGrantingTicketStorage.retrieve(proxyGrantingTicketIou) : null; if (CommonUtils.isEmpty(principal)) { throw new TicketValidationException("No principal was found in the response from the CAS server."); } final Assertion assertion; final Map<String,Object> attributes = extractCustomAttributes(response); if (CommonUtils.isNotBlank(proxyGrantingTicket)) { final AttributePrincipal attributePrincipal = new AttributePrincipalImpl(principal, attributes, proxyGrantingTicket, this.proxyRetriever); assertion = new AssertionImpl(attributePrincipal); } else { assertion = new AssertionImpl(new AttributePrincipalImpl(principal, attributes)); } customParseResponse(response, assertion); return assertion; }
stackoverflow也沒搜到。以下:
https://stackoverflow.com/questions/25885747/when-and-where-java-security-set-userprincipal
https://docs.oracle.com/javase/7/docs/jre/api/security/jaas/spec/com/sun/security/auth/UserPrincipal.html
此時若是設置UserPrincipal須要的話,須要本身經過HttpServletRequestWrapperFilter重寫一遍,以下:
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { final AttributePrincipal principal = retrievePrincipalFromSessionOrRequest(servletRequest); filterChain.doFilter(new CasHttpServletRequestWrapper((HttpServletRequest) servletRequest, principal), servletResponse); } protected AttributePrincipal retrievePrincipalFromSessionOrRequest(final ServletRequest servletRequest) { final HttpServletRequest request = (HttpServletRequest) servletRequest; final HttpSession session = request.getSession(false); final Assertion assertion = (Assertion) (session == null ? request.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION) : session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION)); return assertion == null ? null : assertion.getPrincipal(); }
這樣的話,shiro UserFilter裏面就能夠經過
AttributePrincipal principal = (AttributePrincipal)((HttpServletRequest) request).getUserPrincipal();
拿到登陸用戶的信息了。