天天看点

Springmvc+CAS前后端完全分离权限认证备忘

备忘代码,个人用:

这个功能经常用,每次都从头写太麻烦,记下来备用。

spring-security配置:

<beans:beans xmlns="http://www.springframework.org/schema/security"
	xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

	<debug />

	<http entry-point-ref="casEntryPoint">
		<custom-filter ref="casFilter" position="CAS_FILTER" />
		<logout logout-success-url="/logout/cas" />
		<custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER" />
		<custom-filter ref="singleLogoutFilter" before="CAS_FILTER" />
		<cors />
		<csrf disabled="true" />

		<!-- 登录用URL -->
		<intercept-url pattern="/getlogin" access="isAuthenticated()" />
		<!-- 管理员接口URL -->
		<intercept-url pattern="/api/adm/**"
			access="hasAnyAuthority('ROLE_ADMINISTRATOR','ROLE_BUSINESS','ROLE_TECHNICAL')" />
		<!-- 需权接口 -->
		<intercept-url pattern="/api/auth/**" access="isAuthenticated()" />
		<!-- 管理员页面URL -->
		<intercept-url pattern="/adm/**" access="hasAnyAuthority('ROLE_ADMINISTRATOR','ROLE_BUSINESS','ROLE_TECHNICAL')" />

		<!-- 其余URL无需权限 -->
		<intercept-url pattern="/**" access="permitAll" />
		<headers>
			<frame-options disabled="true" />
		</headers>
	</http>

	<context:property-placeholder location="classpath:spring-config.properties" />
	<beans:bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">
		<beans:property name="service" value="${spring.security.serviceProperties.service}" />
		<beans:property name="sendRenew" value="false" />
	</beans:bean>
	<beans:bean id="casEntryPoint" class="com.s.ad.springcomponent.CustomedCasAuthenticationEntryPoint">
		<beans:property name="loginUrl" value="${spring.security.casEntryPoint.loginUrl}" />
		<beans:property name="serviceProperties" ref="serviceProperties" />
		<beans:property name="noNeedToFilterUrlPrefix">
			<beans:list>
				<beans:value>/getlogin</beans:value>
				<beans:value>/api/adm</beans:value>
			</beans:list>
		</beans:property>
	</beans:bean>
	<beans:bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter">
		<beans:property name="authenticationManager" ref="authenticationManager" />
		<beans:property name="authenticationSuccessHandler">
			<beans:bean class="com.s.ad.springcomponent.CustomedSavedRequestAwareAuthenticationSuccessHandler">
				<beans:property name="alwaysUseDefaultTargetUrl" value="false" />
				<beans:property name="targetUrlParameter" value="redirectUrl" />
			</beans:bean>
		</beans:property>
	</beans:bean>
	<authentication-manager alias="authenticationManager">
		<authentication-provider ref="casAuthenticationProvider" />
	</authentication-manager>
	<beans:bean id="casAuthenticationProvider"
		class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
		<beans:property name="authenticationUserDetailsService">
			<beans:bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
				<beans:constructor-arg ref="userService" />
			</beans:bean>
		</beans:property>
		<beans:property name="serviceProperties" ref="serviceProperties" />
		<beans:property name="ticketValidator">
			<beans:bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
				<beans:constructor-arg index="0"
					value="${spring.security.casAuthenticationProvider.ticketValidator.arg0}" />
			</beans:bean>
		</beans:property>
		<beans:property name="key" value="an_id_for_this_auth_provider_only" />
	</beans:bean>
	<ldap-user-service id="userService" user-search-filter="(uid={0})" user-search-base="ou=People"
		group-search-filter="(memberUid={1})" group-search-base="ou=Group" />
	<ldap-server url="${spring.security.ldap-server.url}" manager-dn="cn=Manager,dc=s,dc=com"
		manager-password="43b2f162-ea30-42c6-9a87-d67ea630d118" />
	<beans:bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter">
		<beans:property name="casServerUrlPrefix" value="${spring.security.singleLogoutFilter.casServerUrlPrefix}" />
	</beans:bean>
	<beans:bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
		<beans:constructor-arg value="${spring.security.requestSingleLogoutFilter.arg0}" />
		<beans:constructor-arg>
			<beans:bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />
		</beans:constructor-arg>
		<beans:property name="filterProcessesUrl" value="/logout/cas" />
	</beans:bean>

</beans:beans>
           

将CasAuthenticationEntryPoint的代码copy过来修改一下,spring自带的CasAuthenticationEntryPoint有点限制,不好用,修改后代码如下:

package com.s.ad.springcomponent;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.jasig.cas.client.Protocol;
import org.jasig.cas.client.util.CommonUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.HttpStatus;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.util.Assert;

public class CustomedCasAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean {
    // ~ Instance fields
    // ================================================================================================
    private ServiceProperties serviceProperties;

    private String loginUrl;

    /**
     * 以这些前缀开头的URL会走正常登录逻辑,而不是向客户端返回403提示 
     */
    private String[] noNeedToFilterUrlPrefix;

    /**
     * Determines whether the Service URL should include the session id for the specific
     * user. As of CAS 3.0.5, the session id will automatically be stripped. However,
     * older versions of CAS (i.e. CAS 2), do not automatically strip the session
     * identifier (this is a bug on the part of the older server implementations), so an
     * option to disable the session encoding is provided for backwards compatibility.
     *
     * By default, encoding is enabled.
     */
    private boolean encodeServiceUrlWithSessionId = true;

    // ~ Methods
    // ========================================================================================================

    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(final HttpServletRequest servletRequest, final HttpServletResponse response, final AuthenticationException authenticationException)
            throws IOException, ServletException {

        final String urlEncodedService = createServiceUrl(servletRequest, response);
        final String redirectUrl = createRedirectUrl(urlEncodedService);

        preCommence(servletRequest, response);

        /**
         * 判断当前URL是否匹配无需过滤的前缀,若匹配,则走正常登录逻辑,否则向客户端返回提示信息
         */
        boolean c = true;
        if (noNeedToFilterUrlPrefix != null) {
            f: for (String urlPrefix : noNeedToFilterUrlPrefix) {
                if (servletRequest.getServletPath().startsWith(urlPrefix)) {
                    response.sendRedirect(redirectUrl);
                    c = false;
                    break f;
                }
            }
        }
        if (c) {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(HttpStatus.OK.value());
            String s = "{\"code\":403,\"msg\":\"forbidden\",\"err_msg\":\"Login expired\"}";
            PrintWriter writer = response.getWriter();
            writer.write(s);
            writer.close();
            response.flushBuffer();
        }
    }

    /**
     * Constructs a new Service Url. The default implementation relies on the CAS client
     * to do the bulk of the work.
     * @param request the HttpServletRequest
     * @param response the HttpServlet Response
     * @return the constructed service url. CANNOT be NULL.
     */
    protected String createServiceUrl(final HttpServletRequest request, final HttpServletResponse response) {
        Set<String> serviceParameterSet = new HashSet<String>(4);
        for (final Protocol protocol : Protocol.values()) {
            serviceParameterSet.add(protocol.getServiceParameterName());
        }
        String serviceParameterName = serviceParameterSet.toString().replaceAll("\\[|\\]", "").replaceAll("\\s", "");
        return CommonUtils.constructServiceUrl(null, response, this.serviceProperties.getService(), null, serviceParameterName, this.serviceProperties.getArtifactParameter(),
                this.encodeServiceUrlWithSessionId);
    }

    /**
     * Constructs the Url for Redirection to the CAS server. Default implementation relies
     * on the CAS client to do the bulk of the work.
     *
     * @param serviceUrl the service url that should be included.
     * @return the redirect url. CANNOT be NULL.
     */
    protected String createRedirectUrl(final String serviceUrl) {
        return CommonUtils.constructRedirectUrl(this.loginUrl, this.serviceProperties.getServiceParameter(), serviceUrl, this.serviceProperties.isSendRenew(), false);
    }

    /**
     * Template method for you to do your own pre-processing before the redirect occurs.
     *
     * @param request the HttpServletRequest
     * @param response the HttpServletResponse
     */
    protected void preCommence(final HttpServletRequest request, final HttpServletResponse response) {

    }

    /**
     * The enterprise-wide CAS login URL. Usually something like
     * <code>https://www.mycompany.com/cas/login</code>.
     *
     * @return the enterprise-wide CAS login URL
     */
    public final String getLoginUrl() {
        return this.loginUrl;
    }

    public String[] getNoNeedToFilterUrlPrefix() {
        return noNeedToFilterUrlPrefix;
    }

    public final ServiceProperties getServiceProperties() {
        return this.serviceProperties;
    }

    public final void setLoginUrl(final String loginUrl) {
        this.loginUrl = loginUrl;
    }

    public void setNoNeedToFilterUrlPrefix(String[] noNeedToFilterUrlPrefix) {
        this.noNeedToFilterUrlPrefix = noNeedToFilterUrlPrefix;
    }

    public final void setServiceProperties(final ServiceProperties serviceProperties) {
        this.serviceProperties = serviceProperties;
    }

    /**
     * Sets whether to encode the service url with the session id or not.
     *
     * @param encodeServiceUrlWithSessionId whether to encode the service url with the
     * session id or not.
     */
    public final void setEncodeServiceUrlWithSessionId(final boolean encodeServiceUrlWithSessionId) {
        this.encodeServiceUrlWithSessionId = encodeServiceUrlWithSessionId;
    }

    /**
     * Sets whether to encode the service url with the session id or not.
     * @return whether to encode the service url with the session id or not.
     *
     */
    protected boolean getEncodeServiceUrlWithSessionId() {
        return this.encodeServiceUrlWithSessionId;
    }
}

           

SavedRequestAwareAuthenticationSuccessHandler也要copy然后自定义下,如下:

if (isAlwaysUseDefaultTargetUrl()
				|| (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
			requestCache.removeRequest(request, response);
			super.onAuthenticationSuccess(request, response, authentication);

			return; 
		}
		clearAuthenticationAttributes(request);
		{ // 根据是否有targetUrlParameter代表的参数进行响应的处理,将用户重定向到不同targetUrl
			String targetUrl = null;
			String[] targetUrls = savedRequest.getParameterMap().get(targetUrlParameter);
			if (targetUrls != null && targetUrls.length > 0) { // 可从savedRequest取到targetUrl
				targetUrl = targetUrls[0];
			} else { // 不能从savedRequest取到targetUrl,则使用默认值
				targetUrl = savedRequest.getRedirectUrl();
			}
			getRedirectStrategy().sendRedirect(request, response, targetUrl);
		}
           

原创备忘代码 ----- shizhongqi