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="/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。



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版本。 

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) 
				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的集成方式。

转载于:https://my.oschina.net/u/3065626/blog/3042486

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值