1.最近项目须要集成已经存在的cas系统。 可是目前已集成的系统都是jsp。而咱们项目是先后端分离开发(伪),没有分开部署。javascript
2.cas原理就不介绍了 网上例子不少。基本都是使用302重定向实现的。html
下面介绍一下本身是怎么解决先后端分离的cas集成方式。前端
springmvc集成cas配置:java
<security:http create-session="always" auto-config='false' entry-point-ref="casEntryPoint" use-expressions="true"> <security:intercept-url pattern="/index.html" access="hasRole('APP_USER')" /> <security:intercept-url pattern="/redirect.html" access="hasRole('APP_USER')" />//由于是伪先后端分离 这2个url拦截是为了用户直接刷新页面时触发未登陆状况跳转cas登陆页使用。 index.html是个人主页 <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。jquery
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 { System.out.println(servletRequest.getRequestURI()+"-"+servletRequest.getRequestURL()); HttpSession session=servletRequest.getSession(); DefaultSavedRequest saveReq=(DefaultSavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST"); 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{ if("/index.html".equals(saveReq.getServletPath())){//若是是主页则跳转redirect.html获取token信息 response.sendRedirect(saveReq.getRequestURL()+"redirect.html"); }else if("/redirect.html".equals(saveReq.getServletPath())){ //若是当前是redirect且未登陆则直接跳转cas登陆页。这样cas登陆成功后跳转到redirect.html会直接触发页面ajax请求获取到token,而后跳转主页 response.sendRedirect(redirectUrl + "?" + servletRequest.getSession().getId()); }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版本。 angularjs
requestSingleLogoutFilter是登出处理过滤器。主要是用来过滤客户端发起登出请求。XXUrlLogoutSuccessHandler 主要是用来处理登出后,再次登陆跳转地址问题。web
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)ajax
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 登陆部分。redis
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_.spring
因为项目中获取用户信息时本身组装的 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) 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="./redirect.html"; } //若是不是登陆页我这里直接正则匹配了返回的请求回调地址。 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
由于我是测试可行性故我在前端主页前加了一层。正常应该在主页中配置如上方法。由于个人redirect.html被拦截了 因此跳转会触发302重定向至cas。而后cas登陆后又会重定向至redirect.html 。最后页面ajax请求加载token,即实现了登陆。
若是是先后端分开部署应该在ajax请求中再加一层去设置登陆后的跳转主页。个人想法是:因为cas-client是将上一次访问页面都存储在了 session中 (SPRING_SECURITY_SAVED_REQUEST这么个key值中)。能够写一个接口让security不拦截 去设置主页 这样也能够实现。(DefaultSavedRequest没有set跳转路径的方法,不推荐重写)或者本身去重写AuthenticationSuccessHandler 实现本身的回调成功函数。默认是SavedRequestAwareAuthenticationSuccessHandler,拿到以前保存的路径重定向。
或者直接在后台定义一个重定一个重定向前端主页的接口。前端判断cas没有登陆后访问这个接口。让该接口被拦截。而后再point中特殊判断是该接口请求的话直接重定向至cas。这样cas跳转回来该接口又能重定向至前端。大功告成
为了解决跨域问题。我对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+cas的集成方式。