Spring MVC integrates cas and resolves front-end and back-end separation

Keywords: Programming Session angular Java JSON

1. Recent projects need to integrate existing cas systems. But all the integrated systems are jsp. Our project is front-end and back-end separate development (pseudo), without separate deployment.

2.cas principle does not introduce many examples on the Internet. Basically, they all use 302 redirection.

Here's how to solve the problem of cas integration with front-end and back-end separation.

Spring MVC integrated cas configuration:

<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 No need ROLE_Start
		<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"> Single sign-out address. 
		<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}://The ${cas.service.host}/${cas.service.web}/ mvc/dispatch/login/cas "p: authenticated AllArtifacts=" true "/> CAS callback verification address version 5.0 defaults to intercept/login/cas path. If you want to rewrite it, you need to define it in accordance with filterProcessesUrl

	<bean id="casEntryPoint" class="com.cas.XXCasAuthenticationEntryPoint" p:serviceProperties-ref="serviceProperties" p:loginUrl="${cas.server.protocol}://${cas.server.host}/ cas/login "/> CAS login address

	<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 Callback Validation Failure Address
		</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>

Focus on several configurations. Because front-end and back-end separation uses json interaction, cas is 302 redirection. Then we need to change the cas entry point instead of using the default point. Rewrite CasAuthentication Entry Point to XXCasAuthentication Entry Point.



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;

/**
 * Rewrite the default implementation and add session Id to the CAS registration service. Session Id is mainly used for compatibility with cluster mode when single sign-out.
 */
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{
            //Setting redirect url plus session Id
            //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;
    }

}

As above, if the CAS jump is triggered, the json data is returned. The front-end judges the specified status code and then performs corresponding operations. Look specifically at the front-end introduction below. This excuse is provided in version 3.5.0 of cas-client. I use version 3.2.1 here.  

RequSingleLogoutFilter is the logout processing filter. Mainly used to filter the client to initiate logout requests. XXUrlLogoutSuccessHandler is mainly used to deal with the problem of login jump address after logout.



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 is mainly used to handle single-point logout. The official default implementation is to abolish the current session. I integrated spring session. Use the official default. Here is a record of a cluster session deletion idea. (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;

/**
 * The filter is used to configure to determine whether the request is an exit system request, and if it is an exit system request, the session cache will be emptied.
 */
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);

                /**
                 * With session sharing mechanism, session Id recorded by cas server is used to delete the cache of session in redis. This step is mainly used for cas server to log out random requests after business cluster and in the case of reverse proxy.
                 **/

                String logoutMessage = CommonUtils.safeGetParameter(request, "logoutRequest");
                String sessionId = XmlUtils.getTextForElement(logoutMessage, "SessionId");
                System.out.println("Single point deletion-----------------------------------");
                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;
    }

}

The cas configuration is basically the same. Record the spring security login section.


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("Query User Information--------------------------");
			User user = userService.queryUserByAccount(username);
			// Copy the queried user information to the implementation class
			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;
		}
	}
}

Query the user information according to the user name returned by cas. The role name must be ROLE_.

Session DTOs are self-assembled because they capture user information in a project. It is also obtained directly from httpsession. Therefore, we need to set up interceptors to inject user information.

servlet.xml Middle configuration
 <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);
				//Construct session users according to userId.
				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 Interceptor Query User Information Error"+e);
            }finally{
            	return true;
            }
	        
		}
		
		return true;	
	}
}

This ensures that cas callbacks back to access the controller are the same as session values obtained by normal login.

Here comes the point! How to deal with the front end

As mentioned earlier, we returned json data. Then the front end must intercept the request return value and handle cas single sign-on before returning.

My front end uses angular js. So set up an http request interceptor. Angular also has interceptors.

var MetronicApp = angular.module("MetronicApp", [ "ui.router", "ui.bootstrap",
		"pascalprecht.translate",// Internationalization
		'mgcrea.ngStrap' // Frame insert
]);
/*
 *Add http interception
 */
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'){ //The front-end first decides whether to log in or not, and if it does not log in to get the back-end url, it makes cas jump judgment. 4111 is the back-end-defined return code
				$.ajax({
            //Several parameters need to be noted.
                type: "GET",//Method type
                dataType: "html",//The expected data type returned by the server
                async: false, //Synchronization is to return data to the current business request.
                url: response.data.redirect+"&method=POST" ,//The url plus method=POST allows CAS to return ST as a page. Note that I've done cross-domain processing on CAS server. Credit certificates must be established. Otherwise, ajax is sometimes blocked by browsers and cannot access cas.
				xhrFields: {
				withCredentials: true // Here we set up withCredentials to bring cookie s
				},
				success: function (result) {
				    console.log(result);//Print the data returned by the server (for debugging)
					if(result.indexOf("id=\"fm1\"")>0){ //Determine whether the cas login page is available. If it is a login page, jump to the login page.
						alert("The current account is not logged in. Please log in first.");
						window.location.href=response.data.portalUrl;
					}
      //If it's not the login page, I've matched the return request callback address directly.
					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);
//The return value of the business request can be obtained by accessing the callback address stitching ticket again.
					$.ajax({
				            //Several parameters need to be noted.
				     type: "POST",//Method type
				     dataType: "json",//The expected data type returned by the server
				     async: false,
				     url: url+"&ticket="+st_val ,//url
					 xhrFields: {
				       withCredentials: true // Here you set up withCredentials
				     },
				     success: function (result) {
				     console.log(result);//Print the data returned by the server (for debugging)
				     response.data=result; //Returns the return value of the business request. Response is the response to the current business request.
                },
                error : function(error) {
                    alert("Abnormal!");
                }
            });                    

                },
                error : function(error) {
                    alert("Abnormal!");
                }
            });
			}
			

			return response;
		},
		'request' : function(config) {
			//Processing AJAX requests (otherwise background IsAjaxRequest() is always false)
			config.headers['X-Requested-With'] = 'XMLHttpRequest';
			return config || $q.when(config);
		},
		'requestError' : function(config) {
			return $q.reject(config);
		}
	}
	return httpInterceptor;
} ]);
// Sign in
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);
			// After successful login, the operation of storing user information into storage is put into main.js, and the page is initialized.
			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);
			// Store it when you log in
			setCookie("userId",userinfo.userId);
			$window.location.href = "./#/home.html";

	},function(error) {
		windowAlert(JSON.parse(error).errMsg);		
	});					
					
});

// # sourceURL=RedirectController.js

Because I was testing the feasibility, I added a layer to the front page. Normally it should be configured in the home page as above. I'm using the front end to decide whether cas is logged in or not because we have a portal system. All systems are accessed from the portal. So I jump to the portal when cas is not logged in. Because portals are grammatical. Te cas and portals are not front-end and back-end separation. Therefore, cas login page can be jumped normally. After successful login, it can also return to the home page of the portal system correctly, and then enter our system.

If there is no portal, you should add another layer to the ajax request to set up the jump home page after login. My idea is: because cas-client stores the last page accessed in session (such as SPRING_SECURITY_SAVED_REQUEST). You can also write an interface so that security does not intercept to set up the home page. Or rewrite Authentication Success Handler to implement its own callback success function. The default is SavedRequest Aware Authentication Success Handler, which retrieves the previously saved path redirection. To solve cross-domain problems. I do cross-domain processing on CAS server

 

The jump page is as follows:

<!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>Jumping</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 cross-domain processing: add a filter

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

	}

}

Thank you for the ideas provided by https://gogo1217.iteye.com/blog/2425080. Although the code is incomplete, the method=POST method is provided.

Summary: cas server is 3.X. method=POST, HEADER, GET official website said that 5.x provides the method. But 3.X is also available through ajax, so cas is not upgraded. 3.x does not support HEADER

cas5.x sets the POST to return the ST jump page. Browsers are not visible suggestions to use postman and other debugging. When setting up HEADER, ST information is returned in the header information. But when I debugged, all the crazy redirections were confused. cas5.x official website says to set the response type in response type. Types can be specified in the configuration file.

Next time, we will introduce the integration mode of spring boot+spring security+cas.

Posted by nosti on Thu, 25 Apr 2019 13:18:35 -0700