springmvc整合cas,並解決前後端分離情況
1.最近專案需要整合已經存在的cas系統。 但是目前已整合的系統都是jsp。而我們專案是前後端分離開發(偽),沒有分開部署。
2.cas原理就不介紹了 網上例子很多。基本都是使用302重定向實現的。
下面介紹一下自己是怎麼解決前後端分離的cas整合方式。
springmvc整合cas配置:
<security:http create-session="always" auto-config='false' entry-point-ref="casEntryPoint" use-expressions="true"> <security:intercept-url pattern="/mvc/dispatch/**" access="hasRole('APP_USER')" /> --5.0不用帶ROLE_開頭 <security:csrf disabled="true" /> <security:custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" /> <security:custom-filter ref="casFilter" position="CAS_FILTER" /> <security:custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER" /> <security:custom-filter ref="singleLogoutFilter" before="CAS_FILTER" /> <security:session-management session-authentication-strategy-ref="sas" /> </security:http> <!-- <security:global-method-security pre-post-annotations="enabled" /> --> <bean id="redirectSessionInformationExpiredStrategy" class="org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy"> <constructor-arg name="invalidSessionUrl" value="/goLogin.html" /> </bean> <bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter"> <constructor-arg name="sessionRegistry" ref="sessionRegistry" /> <constructor-arg name="sessionInformationExpiredStrategy" ref="redirectSessionInformationExpiredStrategy" /> </bean> <bean id="sas" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy"> <constructor-arg> <list> <bean class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy"> <constructor-arg ref="sessionRegistry" /> <property name="maximumSessions" value="1" /> </bean> <!-- <bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy"> </bean> --> <bean class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy"> <constructor-arg ref="sessionRegistry" /> </bean> </list> </constructor-arg> </bean> <bean id="sessionRegistry" class="org.springframework.session.security.SpringSessionBackedSessionRegistry"> <constructor-arg ref="sessionRepository" /> </bean> <security:authentication-manager alias="authManager"> <security:authentication-provider ref="casAuthProvider" /> </security:authentication-manager> <bean id="singleLogoutFilter" class="com.cas.XXSingleSignOutFilter" /> <bean id="XXUrlLogoutSuccessHandler" class="com.cas.XXUrlLogoutSuccessHandler"> <constructor-arg value="${cas.server.protocol}://${cas.server.host}/cas/logout?service=" /> </bean> <bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter" p:filterProcessesUrl="/j_spring_cas_security_logout"> 單點登出地址。 <constructor-arg ref="XXUrlLogoutSuccessHandler" /> <constructor-arg> <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" /> </constructor-arg> </bean> <bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties" p:sendRenew="false" p:service="${cas.service.protocol}://${cas.service.host}/${cas.service.web}/mvc/dispatch/login/cas" p:authenticateAllArtifacts="true" /> cas回撥驗證地址 5.0版本預設是攔截 /login/cas路徑。如果要重寫 需定義與filterProcessesUrl一致 <bean id="casEntryPoint" class="com.cas.XXCasAuthenticationEntryPoint" p:serviceProperties-ref="serviceProperties" p:loginUrl="${cas.server.protocol}://${cas.server.host}/cas/login" /> cas登入地址 <bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter" p:authenticationManager-ref="authManager" p:serviceProperties-ref="serviceProperties" p:filterProcessesUrl="/mvc/dispatch/login/cas"> <property name="authenticationDetailsSource"> <bean class="org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource"> <constructor-arg ref="serviceProperties" /> </bean> </property> <property name="authenticationFailureHandler"> <bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler" p:defaultFailureUrl="/casfailed.html" /> cas回撥驗證失敗地址 </property> </bean> <bean id="casAuthProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider" p:serviceProperties-ref="serviceProperties" p:key="an_id_for_this_auth_provider_only"> <property name="authenticationUserDetailsService"> <bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> <constructor-arg ref="casUserDetailsService" /> </bean> </property> <property name="ticketValidator"> <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator"> <constructor-arg value="${cas.server.protocol}://${cas.server.host}/cas" /> </bean> </property> </bean>
重點講幾個配置。由於前後端分離採用json互動,而cas是302重定向。 則我們需要改變cas入口點,不能使用預設的point。重寫 CasAuthenticationEntryPoint為XXCasAuthenticationEntryPoint。
import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jasig.cas.client.util.CommonUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.cas.ServiceProperties; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.util.Assert; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.net.URLEncoder; /** * 重寫預設的實現,添加了向CAS註冊服務時候引數帶上sessionId,sessionId主要用於配合單點登出時候相容叢集模式 */ public class XXCasAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean { private static final Log logger = LogFactory.getLog(XXCasAuthenticationEntryPoint.class); private ServiceProperties serviceProperties; private String loginUrl; @Value("${cas.portal.url}") private String casPortalUrl; /** * @deprecated */ @Deprecated private boolean encodeServiceUrlWithSessionId = true; public HisCasAuthenticationEntryPoint() { } public void afterPropertiesSet() throws Exception { Assert.hasLength(this.loginUrl, "loginUrl must be specified"); Assert.notNull(this.serviceProperties, "serviceProperties must be specified"); Assert.notNull(this.serviceProperties.getService(), "serviceProperties.getService() cannot be null."); } public final void commence(HttpServletRequest servletRequest, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException { String urlEncodedService = this.createServiceUrl(servletRequest, response); String redirectUrl = this.createRedirectUrl(urlEncodedService); this.preCommence(servletRequest, response); System.out.println("hisCAsAUTH---------------------------------------------"); if(servletRequest.getParameter("sso_ticket")!=null){ response.sendRedirect(redirectUrl + "&ticket=" + servletRequest.getParameter("sso_ticket")); }else{ //設定重定向url加上sessionId //response.sendRedirect(redirectUrl + "?" + servletRequest.getSession().getId()); response.setContentType("application/json"); response.setStatus(200); PrintWriter writer = response.getWriter(); //URLEncoder.encode(serviceUrl, "UTF-8") writer.write("{\"casError\":\"4111\",\"redirect\":\"" + redirectUrl + "?" + servletRequest.getSession().getId() + "\",\"portalUrl\":\""+casPortalUrl+ "?" + servletRequest.getSession().getId() +"\"}"); } } protected String createServiceUrl(HttpServletRequest request, HttpServletResponse response) { return CommonUtils.constructServiceUrl((HttpServletRequest) null, response, this.serviceProperties.getService(), (String) null, this.serviceProperties.getArtifactParameter(), this.encodeServiceUrlWithSessionId); } protected String createRedirectUrl(String serviceUrl) { return CommonUtils.constructRedirectUrl(this.loginUrl, this.serviceProperties.getServiceParameter(), serviceUrl, this.serviceProperties.isSendRenew(), false); } protected void preCommence(HttpServletRequest request, HttpServletResponse response) { } public final String getLoginUrl() { return this.loginUrl; } public final ServiceProperties getServiceProperties() { return this.serviceProperties; } public final void setLoginUrl(String loginUrl) { this.loginUrl = loginUrl; } public final void setServiceProperties(ServiceProperties serviceProperties) { this.serviceProperties = serviceProperties; } /** * @deprecated */ @Deprecated public final void setEncodeServiceUrlWithSessionId(boolean encodeServiceUrlWithSessionId) { this.encodeServiceUrlWithSessionId = encodeServiceUrlWithSessionId; } /** * @deprecated */ @Deprecated protected boolean getEncodeServiceUrlWithSessionId() { return this.encodeServiceUrlWithSessionId; } }
如上,如果觸發cas跳轉改成返回json資料 。前端判斷指定 狀態碼,然後進行對應操作。具體 看下方前端介紹。 cas-client 3.5.0版本提供了這種藉口。我這裡使用的是 3.2.1版本。
requestSingleLogoutFilter是登出處理過濾器。主要是用來過濾客戶端發起登出請求。XXUrlLogoutSuccessHandler 主要是用來處理登出後,再次登入跳轉地址問題。
import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.util.StringUtils; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; /** * Handles the navigation on logout by delegating to the * {@link org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler} base class logic. * */ public class HisUrlLogoutSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler implements LogoutSuccessHandler { @Value("${cas.portal.url}") private String casPortalUrl; private String casUrl; public HisUrlLogoutSuccessHandler(String casUrl) { this.casUrl = casUrl; } public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { String result = casUrl + URLEncoder.encode(casPortalUrl, "UTF-8"); if (StringUtils.hasText(result)) { setDefaultTargetUrl(result); } super.handle(request, response, authentication); } }
XXSingleSignOutFilter主要用來處理單點登出。官方預設實現是 作廢當前session。我集成了spring session。使用官方預設就可以。這裡記錄一下一種叢集session刪除思路。(redis)
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.cas.client.session.SessionMappingStorage;
import org.jasig.cas.client.session.SingleSignOutHandler;
import org.jasig.cas.client.util.AbstractConfigurationFilter;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.XmlUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import com.asdc.jbp.hisLogin.util.CommonHelper;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 過濾器用於配置判斷請求是否是退出系統請求,如果是退出系統請求將清空session快取。
*/
public class XXSingleSignOutFilter extends AbstractConfigurationFilter {
private static final Log logger = LogFactory.getLog(HisSingleSignOutFilter.class);
@Autowired
private RedisOperationsSessionRepository repository;
@Value("${local.ip:000.000.000.000}")
private String localIp;
private static final SingleSignOutHandler handler = new SingleSignOutHandler();
private AtomicBoolean handlerInitialized = new AtomicBoolean(false);
public HisSingleSignOutFilter() {
}
public void init(FilterConfig filterConfig) throws ServletException {
if (!this.isIgnoreInitConfiguration()) {
handler.setArtifactParameterName(this.getPropertyFromInitParams(filterConfig, "artifactParameterName", "ticket"));
handler.setLogoutParameterName(this.getPropertyFromInitParams(filterConfig, "logoutParameterName", "logoutRequest"));
}
handler.init();
}
public void setArtifactParameterName(String name) {
handler.setArtifactParameterName(name);
}
public void setLogoutParameterName(String name) {
handler.setLogoutParameterName(name);
}
public void setSessionMappingStorage(SessionMappingStorage storage) {
handler.setSessionMappingStorage(storage);
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
if (handler.isTokenRequest(request)) {
handler.recordSession(request);
} else {
if (handler.isLogoutRequest(request)) {
handler.destroySession(request);
/**
* 配合session共享機制,通過cas server記錄的sessionId刪除session在redis中的快取,這一步驟主要用於業務集群后,在反向代理情況下,單點登出時候cas server登出請求隨機請求.
**/
String logoutMessage = CommonUtils.safeGetParameter(request, "logoutRequest");
String sessionId = XmlUtils.getTextForElement(logoutMessage, "SessionId");
System.out.println("單點刪除-----------------------------------");
repository.delete(sessionId);
logger.info("logout session id is:" + sessionId +";local is is :" + this.localIp + ";cas server ip is:" + CommonHelper.analyzeClientIpAddress(request) + ";logout project is:" + ((HttpServletRequest) servletRequest).getContextPath());
return;
}
this.log.trace("Ignoring URI " + request.getRequestURI());
}
filterChain.doFilter(servletRequest, servletResponse);
}
public void destroy() {
}
protected static SingleSignOutHandler getSingleSignOutHandler() {
return handler;
}
}
cas配置基本如上。記錄一下spring security 登入部分。
import java.util.Collection;
import java.util.LinkedList;
import javax.annotation.Resource;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service("casUserDetailsService")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Transactional(readOnly = false)
public class CasUserDetailsServiceImpl implements UserDetailsService {
@Resource
private UserService userService;
@SuppressWarnings("finally")
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetailImpl userDetail = new UserDetailImpl();
try {
System.out.println("查詢使用者資訊--------------------------");
User user = userService.queryUserByAccount(username);
// 複製查詢到的使用者資訊到實現類
userDetail.setUserId(user.getUserId());
userDetail.setPassword(user.getPasswd());
userDetail.setUserName(username);
Collection<GrantedAuthority> authorities = new LinkedList<GrantedAuthority>();
/* List<GrantedAuthority> authorities = authorizationService.getAuthorities(token.getFunc());*/
authorities.add(new SimpleGrantedAuthority("ROLE_APP_USER"));
userDetail.setAuthorities(authorities);
/*Token token = authorizationService.getUserTokenByUserId(user);
Authentication auth = new PreAuthenticatedAuthenticationToken(token, token.getUser(), authorities);
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);*/
} catch (ServiceException e) {
e.printStackTrace();
} finally {
return userDetail;
}
}
}
根據cas返回的使用者名稱查詢 使用者資訊。 此處角色名稱必須帶上 ROLE_.
由於專案中獲取使用者資訊時自己組裝的 sessionDTO。而且是直接從httpsession中獲取。故需要設定攔截器,注入使用者資訊。
servlet.xml中配置
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/dispatch/**" />
<bean class="com.xx.interceptor.UserSessionInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
public class UserSessionInterceptor extends HandlerInterceptorAdapter {
@Resource
private AuthorizationService authorizationService;
@Resource(name = "")
private UserService userService;
@Autowired
private CompositeSessionAuthenticationStrategy sas;
static Logger log=LoggerFactory.getLogger(UserSessionInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
HttpSession session = request.getSession();
Object user = session.getAttribute("tUser");
if(user==null){
UserDetailImpl userDetail=(UserDetailImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
User originalUser =new User();
originalUser.setUserCode(userDetail.getUsername());
originalUser.setPasswd(userDetail.getPassword());
try {
originalUser = userService.queryTUserbyUserCodeAndPwd(originalUser);
userService.queryAllDeptsAndAreas(originalUser);
//根據userId構造session使用者。
Token token = authorizationService.getUserTokenByUserId(originalUser);
long loginTime = System.currentTimeMillis();
session.setAttribute("tUser", token.getUser());
session.setAttribute("loginUserCode", token.getUser().getUserCode());
session.setAttribute("token", token);
session.setAttribute("loginTime", loginTime);
Authentication auth = new PreAuthenticatedAuthenticationToken(token.getUser().getAccount(), token.getUser(),
null);
auth.setAuthenticated(true);
sas.onAuthentication(auth, request, response);
} catch (Exception e) {
log.error("session攔截器查詢使用者資訊出錯"+e);
}finally{
return true;
}
}
return true;
}
}
這樣就能保證 cas回調回來訪問 controller能跟普通登入獲取的session值一樣。
重點來了!!前端如何處理
前面說了是返回json資料。那麼前端必須攔截請求返回值,並且在返回之前 處理好cas單點登入。
我前端使用的是angularjs。故設定一個http請求攔截器即可。angular也有對應的攔截器。
var MetronicApp = angular.module("MetronicApp", [ "ui.router", "ui.bootstrap",
"pascalprecht.translate",// 國際化
'mgcrea.ngStrap' // 彈框外掛
]);
/*
*新增http攔截
*/
MetronicApp.config([ '$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
$httpProvider.defaults.headers.post = {
'Content-Type' : 'application/x-www-form-urlencoded'
}
if (!$httpProvider.defaults.headers.get) {
$httpProvider.defaults.headers.get = {};
}
;
$httpProvider.defaults.headers.common["X-Requeste-with"] = "XMLHttpRequest";
$httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache';
$httpProvider.defaults.headers.get['pragma'] = 'no-cache'
} ]);
MetronicApp.factory('httpInterceptor', [ '$q', '$injector', '$rootScope', '$window', '$timeout', function($q, $injector, $rootScope, $window, $timeout) {
var httpInterceptor = {
'responseError' : function(response) {
var sign = 1;
if (response.status == 404) {
var rootScope = $injector.get('$rootScope');
console.info(rootScope);
var state = $injector.get('$rootScope').$state.current.name;
console.info(state);
rootScope.stateBeforLogin = state;
rootScope.$state.go("home");
return $q.reject(response);
} else if (response.status == 417) {
return $q.reject(response);
} else if (response.status == 401) {
$window.location.href = "./#/403.html";
return $q.reject(response);
} else if (response.status == 500) {
$window.location.href = "./login.html";
return $q.reject(response);
}
;
return $q.reject(response);
},
'response' : function(response) {
if(response.status==200 && response.data.casError=='4111'){ //前端先判斷當前是否登入,如果未登入獲取後端url進行cas跳轉判斷。 4111是後端定義的返回碼
$.ajax({
//幾個引數需要注意一下
type: "GET",//方法型別
dataType: "html",//預期伺服器返回的資料型別
async: false, //同步是為了給當前業務請求返回資料。
url: response.data.redirect+"&method=POST" ,//url 加上method=POST 可以讓cas將ST以頁面形式返回。 注意我這裡將 cas服務端做了跨域處理。 必須設定授信證書。要不ajax有時候被瀏覽器攔截無法訪問cas。
xhrFields: {
withCredentials: true // 這裡設定了withCredentials 為了帶上cookie
},
success: function (result) {
console.log(result);//列印服務端返回的資料(除錯用)
if(result.indexOf("id=\"fm1\"")>0){ //判斷是否cas登入頁。如果是登入頁則跳轉登入頁。
alert("當前賬號未登入,請先登入");
window.location.href=response.data.portalUrl;
}
//如果不是登入頁我這裡直接正則匹配了返回的請求回撥地址。
var reg=/action=\"(.*?)\"/g;
console.log(result.match(reg));
var url=result.match(reg)[0].replace(/action=\"/,"").replace(/\"/,"");
console.log(url);
var st_reg=/ST(.*?)\<\/textarea\>/g;
var st_val=result.match(st_reg)[0].replace(/\<\/textarea\>/,"");
console.log(st_val);
//拿到回撥地址 拼接ticket 再次訪問即可拿到 業務請求的返回值。
$.ajax({
//幾個引數需要注意一下
type: "POST",//方法型別
dataType: "json",//預期伺服器返回的資料型別
async: false,
url: url+"&ticket="+st_val ,//url
xhrFields: {
withCredentials: true // 這裡設定了withCredentials
},
success: function (result) {
console.log(result);//列印服務端返回的資料(除錯用)
response.data=result; //將業務請求返回值返回。 response是當前業務請求的響應。
},
error : function(error) {
alert("異常!");
}
});
},
error : function(error) {
alert("異常!");
}
});
}
return response;
},
'request' : function(config) {
//處理AJAX請求(否則後臺IsAjaxRequest()始終false)
config.headers['X-Requested-With'] = 'XMLHttpRequest';
return config || $q.when(config);
},
'requestError' : function(config) {
return $q.reject(config);
}
}
return httpInterceptor;
} ]);
// 登入
MetronicApp.controller('redirectCtrl',function($scope, $http, $window, $q, $interval, $rootScope,$timeout) {
var storage = window.sessionStorage;
var storageLocal = window.localStorage;
var loginParams = {};
var loginData = mergeReauestData('LoginController','getToken',loginParams);
var loginResult = sendPost($http,loginData, $q);
loginResult.then(function(success) {
var loginResult = JSON.parse(success);
// 登入成功後,儲存使用者資訊到storage的操作放到main.js中,頁面初始化時
var token = loginResult.token;
var userinfo = loginResult.token.user;
storage.setItem('token',JSON.stringify(token));
storage.setItem('userinfo',JSON.stringify(userinfo));
storageLocal.setItem('loginTime',loginResult.loginTime);
// 登入的時候存放下
setCookie("userId",userinfo.userId);
$window.location.href = "./#/home.html";
},function(error) {
windowAlert(JSON.parse(error).errMsg);
});
});
// # sourceURL=RedirectController.js
因為我是測試可行性故我在前端主頁前加了一層。正常應該在主頁中配置如上方法。我這裡在前端判斷cas是否登入時取巧 因為我們有一個門戶系統。所有系統均從門戶進入。故我cas未登入則跳轉門戶。因為門戶集成了cas 且門戶不是前後端分離。故可正常跳轉cas登入頁。登入成功後也能正確返回到門戶系統主頁,再進入我們系統即可。
如果沒有門戶的話 應該在ajax請求中再加一層去設定登入後的跳轉主頁。我的想法是:由於cas-client是將上一次訪問頁面都儲存在了 session中 (SPRING_SECURITY_SAVED_REQUEST這麼個key值中)。可以寫一個介面讓security不攔截 去設定主頁 這樣也可以實現。或者自己去重寫AuthenticationSuccessHandler 實現自己的回撥成功函式。預設是SavedRequestAwareAuthenticationSuccessHandler,拿到之前儲存的路徑重定向。為了解決跨域問題。我對cas服務端做了跨域處理
跳轉頁如下:
<!DOCTYPE html>
<!--[if IE 8]> <html lang="en" class="ie8 no-js" data-ng-app="MetronicApp" > <![endif]-->
<!--[if IE 9]> <html lang="en" class="ie9 no-js" data-ng-app="MetronicApp"> <![endif]-->
<html lang="en" data-ng-app="MetronicApp">
<head>
<meta charset="utf-8" />
<title>正在跳轉</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta content="width=device-width, initial-scale=1" name="viewport" />
<meta content="" name="description" />
<meta content="" name="author" />
<link rel="shortcut icon" href="favicon.ico" />
</head>
<body class="login" >
<div ng-controller="redirectCtrl"></div>
<script src="resources/global/plugins/respond.min.js"></script>
<script src="resources/global/plugins/excanvas.min.js"></script>
<script src="resources/global/plugins/jquery.min.js"></script>
<script src="resources/global/plugins/bootstrap/js/bootstrap.min.js"></script>
<script
src="resources/global/plugins/bootstrap-hover-dropdown/bootstrap-hover-dropdown.min.js"></script>
<script src="resources/global/plugins/angularjs/angular.min.js"></script>
<script
src="resources/global/plugins/angularjs/angular-sanitize.min.js"></script>
<script src="resources/global/plugins/angularjs/angular-touch.min.js"></script>
<script
src="resources/global/plugins/angularjs/plugins/angular-ui-router.min.js"></script>
<script
src="resources/global/plugins/angularjs/plugins/ocLazyLoad.min.js"></script>
<script
src="resources/global/plugins/angularjs/plugins/ui-bootstrap-tpls.min.js"></script>
<script src="packages/index/js/common/CommonService.js"></script>
<script src="packages/index/js/main.js"></script>
<script src="packages/index/js/directives.js"></script>
<script src="resources/global/scripts/app.min.js"></script>
<script src="resources/layouts/layout/scripts/layout.min.js"></script>
<script src="resources/layouts/global/scripts/quick-sidebar.min.js"></script>
<script src="packages/pages/js/controllers/RedirectController.js"></script>
<script src="resources/global/plugins/angular-translate.min.js"></script>
<script
src="resources/global/plugins/angular-translate-loader-static-files.min.js"></script>
<script src="packages/index/js/common/json2.js"></script>
<script src="packages/index/js/common/MultiLanguage.js"></script>
<script
src="resources/global/plugins/angularjs/plugins/angular-strap/angular-strap.js"></script>
<script
src="resources/global/plugins/angularjs/plugins/angular-strap/angular-strap.tpl.js"></script>
</body>
</html>
cas跨域處理:加個過濾器
package org.jasig.cas.web;
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.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class cascorfFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse response1 = (HttpServletResponse) response;
HttpServletRequest request1=(HttpServletRequest)request;
response1.setHeader("Access-Control-Allow-Origin", request1.getHeader("Origin"));
response1.setHeader("Access-Control-Allow-Credentials", "true");
response1.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response1.setHeader("Access-Control-Max-Age", "3600");
response1.setHeader("Access-Control-Allow-Headers",
"Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,token");
chain.doFilter(request, response);
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
感謝https://gogo1217.iteye.com/blog/2425080提供的思路。雖然程式碼不全 但是提供了 method=POST的方法思路
總結: cas服務端是3.X。 method=POST、HEADER、GET 官網上說是5.x提供的方法。但是3.X也能通過ajax獲取,故沒有升級cas。3.x不支援HEADER
cas5.x 設定了POST 返回的是ST跳轉頁面。瀏覽器是看不出來的 建議自己用postman等除錯。 設定HEADER時確實在頭資訊裡返回了 ST資訊等。但是我除錯時瘋狂重定向都懵逼了。 cas5.x官網說是在responseType中設定響應型別。可以在配置檔案中指定型別。
下次介紹一下spring boot+spring security+