前后端分離應(yīng)用接入CAS單點(diǎn)登錄處理方式

這段時(shí)間一直在處理單點(diǎn)登錄的問(wèn)題,元旦前對(duì)接了基于SAML的單點(diǎn)登錄認(rèn)證,這幾天又對(duì)接了一個(gè)基于CAS認(rèn)證的,認(rèn)證中心提供的對(duì)接文檔都默認(rèn)接入的client應(yīng)用是前后端不分離的應(yīng)用,所以踩了很多坑,過(guò)程中也找到一些前后端分離認(rèn)證的共性問(wèn)題。在此記錄一下處理過(guò)程。

cas認(rèn)證大致流程,簡(jiǎn)單畫了個(gè)圖


未命名文件.png

前后端不分離的應(yīng)用集成很簡(jiǎn)單,springboot方式與springsecurity的集成官方文檔都有很詳細(xì)的說(shuō)明
https://github.com/apereo/java-cas-client

前后端分離的認(rèn)證,看了別人寫的一些方案都不太適合我的場(chǎng)景,要么改動(dòng)涉及認(rèn)證中心,要么比較丑陋,比如使用iframe嵌套傳遞登錄信息、后端代碼中加一個(gè)JSP文件中專等方式,簡(jiǎn)單嘗試了一下就放棄了。

我的處理思路:

1.先對(duì)接后臺(tái)服務(wù)。把后臺(tái)程序單獨(dú)拿出來(lái),看做是一個(gè)前后端不分離的應(yīng)用,按照前后端不分離的方式對(duì)接,瀏覽器地址欄直接請(qǐng)求后臺(tái)接口(如訂單列表接口),是否對(duì)接成功也很容易驗(yàn)證,認(rèn)證中心登錄后,該后臺(tái)地址的接口在瀏覽器界面返回了訂單列表的數(shù)據(jù)即認(rèn)為認(rèn)證成功了,也就是后端代碼對(duì)接成功了CAS。這樣的好處是可以快速先把CAS集成進(jìn)來(lái),如果直接在前后端分離的體系下對(duì)接,因?yàn)楸旧碛星昂蠖朔蛛x的問(wèn)題在里面,很難確認(rèn)CAS集成是否有問(wèn)題。

2.接入前端頁(yè)面。即通過(guò)前端頁(yè)面再來(lái)嘗試調(diào)用該后臺(tái)接口,啟動(dòng)前端程序,比如點(diǎn)擊訂單列表頁(yè)面的查詢按鈕調(diào)用該訂單列表接口。后臺(tái)CAS filter攔截到前端發(fā)出的請(qǐng)求,認(rèn)證校驗(yàn)未通過(guò),返回302重定向的狀態(tài)碼,瀏覽器拿到302嘗試跳轉(zhuǎn),但是跳轉(zhuǎn)失敗了。原因是該請(qǐng)求是前端代碼發(fā)起的ajax請(qǐng)求,ajax請(qǐng)求無(wú)法302跳轉(zhuǎn),前端代碼也無(wú)法捕捉跳轉(zhuǎn),這種情況不用考慮在前端代碼中實(shí)現(xiàn)跳轉(zhuǎn)了。

3.考慮后端處理。前后端不分離的應(yīng)用中頁(yè)面跳轉(zhuǎn)可以直接在后端java代碼中用response.sendRedirect()直接跳轉(zhuǎn)到指定地址,這種方式在以前的前后端不分離的JSP項(xiàng)目中沒(méi)問(wèn)題,但是前后端分離的項(xiàng)目步行。查看CAS源碼,發(fā)現(xiàn)CAS認(rèn)證不通過(guò),跳轉(zhuǎn)到登錄頁(yè)最終也是response.sendRedirect()來(lái)實(shí)現(xiàn),所以考慮能否攔截或者重寫覆蓋CAS跳轉(zhuǎn)相關(guān)的代碼,讓這里的處理不走默認(rèn)的跳轉(zhuǎn)邏輯,自定義處理方式,返回401狀態(tài)碼給調(diào)用方(也就是前端代碼),并且?guī)弦D(zhuǎn)的鏈接,前端可以捕獲401狀態(tài)碼的返回結(jié)果,獲取要跳轉(zhuǎn)的鏈接后跳轉(zhuǎn)過(guò)去。

4.檢查是否有新問(wèn)題引入。實(shí)踐證明上述思路是可行的,CAS本身的代碼設(shè)計(jì)也非常優(yōu)雅,提供給了我們覆蓋相關(guān)邏輯的方式,具體方式請(qǐng)看后面的實(shí)現(xiàn)。重寫CAS認(rèn)證失敗頁(yè)面跳轉(zhuǎn)的相關(guān)代碼,前后端分離應(yīng)用就可以正常對(duì)接CAS了,可能有一些小的細(xì)節(jié)問(wèn)題處理,但是沒(méi)有新的流程阻塞問(wèn)題引入,如果有新問(wèn)題,比如跨域,針對(duì)性解決即可。

以上是我在處理此類問(wèn)題時(shí)總結(jié)出的大致方式,下面說(shuō)說(shuō)關(guān)鍵步驟,也就是跳轉(zhuǎn)的處理方式

先說(shuō)跳轉(zhuǎn)處理方式,再分析

一、未集成springsecurity

代碼包版本:

springboot:2.1.6.RELEASE
cas-server: 5.3.x
cas-client: cas-client-support-springboot:3.6.0

1.定義一個(gè)跳轉(zhuǎn)處理類,實(shí)現(xiàn)AuthenticationRedirectStrategy接口
import lombok.SneakyThrows;
import org.jasig.cas.client.authentication.AuthenticationRedirectStrategy;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;

@Component
public class CustomAuthRedirectStrategy implements AuthenticationRedirectStrategy {

    @SneakyThrows
    @Override
    public void redirect(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, String s) throws IOException {
        // 自定義一個(gè)后臺(tái)接口,該controller接口內(nèi)只寫一個(gè)response.sendRedirect(應(yīng)用首頁(yè))
        String dealUrl = "http://cas.app.com/api/1.0/users/loginRedirect";
        String encodeUrl = URLEncoder.encode(dealUrl, "utf-8");
        // cas認(rèn)證中心登錄頁(yè)地址
        String loginUrl = "http://cas.proaim.com:8080/cas/login" + "?service=" + encodeUrl;
        httpServletResponse.setStatus(401);
        PrintWriter out = httpServletResponse.getWriter();
        // 格式自定義,前端能獲取到loginUrl即可
        out.write("{\"errors\":[" + "\"" + loginUrl + "\"" + "]}");
    }
}
2.修改cas filter初始化參數(shù)
import com.proaimltd.web.casclient.filter.CustomAuthRedirectStrategy;
import org.jasig.cas.client.boot.configuration.CasClientConfigurer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CasAuthConfig implements CasClientConfigurer {
    @Override
    public void configureAuthenticationFilter(FilterRegistrationBean authenticationFilter) {
        // 源碼中使用反射初始化authenticationRedirectStrategyClass, 用自定義的跳轉(zhuǎn)類覆蓋默認(rèn)的authenticationRedirectStrategyClass
        authenticationFilter.getInitParameters().put("authenticationRedirectStrategyClass", CustomAuthRedirectStrategy.class.getName());
    }
}

注意點(diǎn):此方式實(shí)現(xiàn)的跳轉(zhuǎn),如果ticket認(rèn)證成功后,跳轉(zhuǎn)回的地址帶有;jsessionId=xxxxx,在配置中加上

server.servlet.session.tracking-modes=cookie

原因是應(yīng)用不確定瀏覽器是否禁用了cookie,所以用這種方式來(lái)傳遞session到服務(wù)端,加上配置等于告訴應(yīng)用session可以通過(guò)cookie來(lái)傳遞

分析:

此方式直接引用了官方提供的springboot client包

<dependency>
    <groupId>org.jasig.cas.client</groupId>
    <artifactId>cas-client-support-springboot</artifactId>
    <version>3.6.0</version>
</dependency>

代碼跟蹤到org.jasig.cas.client.authentication.AuthenticationFilter的doFilter方法,可以看到跳轉(zhuǎn)的代碼 this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo),是一個(gè)接口

// 省略非關(guān)鍵代碼
public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest)servletRequest;
    HttpServletResponse response = (HttpServletResponse)servletResponse;
    if (this.isRequestUrlExcluded(request)) {...} else {
        if (assertion != null) {...} else {
            ...
            if (!CommonUtils.isNotBlank(ticket) && !wasGatewayed) {
                ....
                String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, this.getProtocol().getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway, this.method);
                this.logger.debug("redirecting to \"{}\"", urlToRedirectTo);
                // 此處跳轉(zhuǎn)
                this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);
            } else {
                filterChain.doFilter(request, response);
            }
        }
    }
}

構(gòu)造函數(shù)中,authenticationRedirectStrategy接口的默認(rèn)實(shí)現(xiàn)是 DefaultAuthenticationRedirectStrategy

protected AuthenticationFilter(Protocol protocol) {
    super(protocol);
    this.renew = false;
    this.gateway = false;
    this.gatewayStorage = new DefaultGatewayResolverImpl();
    this.authenticationRedirectStrategy = new DefaultAuthenticationRedirectStrategy();
    this.ignoreUrlPatternMatcherStrategyClass = null;
}

DefaultAuthenticationRedirectStrategy方法內(nèi)僅有跳轉(zhuǎn)相關(guān)的代碼,所以可以放心替代

public final class DefaultAuthenticationRedirectStrategy implements AuthenticationRedirectStrategy {
    public DefaultAuthenticationRedirectStrategy() {
    }

    public void redirect(HttpServletRequest request, HttpServletResponse response, String potentialRedirectUrl) throws IOException {
        response.sendRedirect(potentialRedirectUrl);
    }
}

繼續(xù)看該filter的代碼,找到initInternal方法,該方法在client程序啟動(dòng)時(shí)初始化了AuthenticationRedirectStrategy的實(shí)現(xiàn),可以看到此處通過(guò)getClass方法獲取AuthenticationRedirectStrategy的實(shí)現(xiàn)類,

protected void initInternal(FilterConfig filterConfig) throws ServletException {
        // 省略前面 
        .....
        Class<? extends AuthenticationRedirectStrategy> authenticationRedirectStrategyClass = this.getClass(ConfigurationKeys.AUTHENTICATION_REDIRECT_STRATEGY_CLASS);
        if (authenticationRedirectStrategyClass != null) {
            this.authenticationRedirectStrategy = (AuthenticationRedirectStrategy)ReflectUtils.newInstance(authenticationRedirectStrategyClass, new Object[0]);
        }
    }

}

getClass方法獲取ConfigurationKeys.AUTHENTICATION_REDIRECT_STRATEGY_CLASS中配置的類名,通過(guò)反射獲取實(shí)現(xiàn)類信息

public <T> Class<? extends T> getClass(final ConfigurationKey<Class<? extends T>> configurationKey) {
    return (Class)this.getValue(configurationKey, new BaseConfigurationStrategy.Parser<Class<? extends T>>() {
        public Class<? extends T> parse(String value) {
            try {
                return ReflectUtils.loadClass(value);
            } catch (IllegalArgumentException var3) {
                return (Class)configurationKey.getDefaultValue();
            }
        }
    });
}

官方提供了cas filter參數(shù)初始化的方式,大致意思是:
官方并沒(méi)有在配置文件中提供所有的配置選項(xiàng),只提供了最常用的,但是未提供的屬性也可以實(shí)現(xiàn)配置,可以在@EnableCasClient注解下實(shí)現(xiàn)CasClientConfigurer類,并為相關(guān)的Filter覆蓋適當(dāng)?shù)呐渲梅椒ā?/p>

cas_config.png

按照示例,即可覆蓋原有的

如下

authenticationFilter.getInitParameters().put("authenticationRedirectStrategyClass", CustomAuthRedirectStrategy.class.getName());

可以將AuthenticationFilter#doFilter中的this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo)指向我們自定義的跳轉(zhuǎn)方法,返回401

二、集成了springsecurity

代碼包版本:

springboot:2.1.6.RELEASE
cas-server: 5.3.x
cas-client: spring-security-cas:5.1.5.RELEASE

前后端不分離應(yīng)用集成springsecurity的方式很簡(jiǎn)單,代碼可以參考 https://github.com/leslie1015/security_cas

同樣,先來(lái)說(shuō)說(shuō)如何修改跳轉(zhuǎn),返回401

1.定義一個(gè)CustomAuthenticationEntryPoint,實(shí)現(xiàn) AuthenticationEntryPoint, InitializingBean
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean {
    // 如果系統(tǒng)已有JWT等認(rèn)證,此處可以定義一個(gè)開關(guān),控制不影響原先認(rèn)證邏輯
    private final Boolean isCasLogin;
    // 配置信息
    private final CasProvider casProvider;

    public CustomAuthenticationEntryPoint(Boolean isCasLogin, CasProvider casProvider) {
        this.isCasLogin = isCasLogin;
        this.casProvider = casProvider;
    }

    @Override
    public final void commence(HttpServletRequest servletRequest, HttpServletResponse response, AuthenticationException authenticationException) throws IOException {
        if (!isCasLogin) {
            // 原先JWT或者其他認(rèn)證方式的代碼,如果沒(méi)有則忽略
            ...
            return;
        }
        // 構(gòu)造未登錄情況需要跳轉(zhuǎn)的login頁(yè)面url
        // 登錄地址(指定的一個(gè)后臺(tái)controller接口)
        String encodeUrl = URLEncoder.encode(casProvider.getAppServerUrl() + casProvider.getAppLoginUrl(), "utf-8");
        // CAS認(rèn)證中心頁(yè)面地址,參數(shù)service帶上登錄地址,登錄成功后會(huì)帶上ticket跳轉(zhuǎn)回service指定的地址
        String redirectUrl = casProvider.getCasServerLoginUrl() + "?service=" + encodeUrl;
        // 返回401
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        PrintWriter out = response.getWriter();
        // 返回與前端約定的格式,前端能獲取到redirectUrl跳轉(zhuǎn)即可
        out.write("{\"errors\":[" + "\"" + redirectUrl + "\"" + "]}");
    }
}
2.SecurityConfig.java#configure中配置authenticationEntryPoint,指向自定義的CustomAuthenticationEntryPoint
@Override
public void configure(HttpSecurity http) throws Exception {
    http
            .cors()
            .and()
            .csrf().disable()
            .exceptionHandling()
            // 配置自定義的CustomAuthenticationEntryPoint(主要看這里,其他按需配置)
            .authenticationEntryPoint(new CustomAuthenticationEntryPoint(AuthUtils.isCasLogin, casProvider))
            .and()
            .authorizeRequests()
            .regexMatchers(PermitUrlsConfig.permitUrlArray()).permitAll()
            .antMatchers(HttpMethod.OPTIONS).permitAll()
            .antMatchers(appConfigBean.getAuthenticatedUrls()).permitAll()
            .anyRequest().authenticated();
    
    configureJwtFilter(http);
    if (AuthUtils.isCasLogin) {
        casProvider.configureCasFilter(http, authenticationManager());
    }
}
分析

此處引用的代碼包是spring-security-cas,方便我們將cas的各種filter直接配置到security框架中

<dependency>
   <groupId>org.springframework.security</groupId>
   <artifactId>spring-security-cas</artifactId>
   <version>5.1.5.RELEASE</version>
</dependency>

我們先了解一下springsecurity框架的入口點(diǎn)entry-point,這個(gè)入口點(diǎn)其實(shí)是被ExceptionTranslationFilter引用的,ExceptionTranslationFilter過(guò)濾器的作用的異常翻譯,出現(xiàn)認(rèn)證、訪問(wèn)異常的時(shí)候,通過(guò)入口點(diǎn)決定redirect、forward的操作。異常情況下調(diào)用handleSpringSecurityException

public class ExceptionTranslationFilter extends GenericFilterBean {
    
    // 前面省略
        ...
       public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        try {
            chain.doFilter(request, response);

            logger.debug("Chain processed normally");
        }
        catch (IOException ex) {
            throw ex;
        }
        catch (Exception ex) {
            ....
            RuntimeException ase = (AuthenticationException) throwableAnalyzer
                    .getFirstThrowableOfType(AuthenticationException.class, causeChain);
            
            if (ase == null) {
                ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
                        AccessDeniedException.class, causeChain);
            }
            if (ase != null) {
                if (response.isCommitted()) {
                    throw new ServletException("des.", ex);
                }
                // 異常情況下處理
                handleSpringSecurityException(request, response, chain, ase);
            }
            else {
                ...
            }
        }
    } 
    
}

該方法最終調(diào)用了sendStartAuthentication方法處理

private void handleSpringSecurityException(HttpServletRequest request,
      HttpServletResponse response, FilterChain chain, RuntimeException exception)
      throws IOException, ServletException {
   if (exception instanceof AuthenticationException) {
      logger.debug(
            "Authentication exception occurred; redirecting to authentication entry point",
            exception);

      sendStartAuthentication(參數(shù)1...);

   }
   else if (exception instanceof AccessDeniedException) {
      Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
      if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
         logger.debug("des", exception);

         sendStartAuthentication(參數(shù)2...);
      }
      else {
         ....
      }

   }
}

sendStartAuthentication方法調(diào)用了authenticationEntryPoint.commence(request, response, reason);

protected void sendStartAuthentication(HttpServletRequest request,
      HttpServletResponse response, FilterChain chain,
      AuthenticationException reason) throws ServletException, IOException {
   // SEC-112: Clear the SecurityContextHolder's Authentication, as the
   // existing Authentication is no longer considered valid
   SecurityContextHolder.getContext().setAuthentication(null);
   requestCache.saveRequest(request, response);
   logger.debug("Calling Authentication entry point.");
   authenticationEntryPoint.commence(request, response, reason);
}

關(guān)鍵的代碼就是authenticationEntryPoint.commence(request, response, reason),其實(shí)這時(shí)候已經(jīng)很明顯了,authenticationEntryPoint是一個(gè)接口,有多個(gè)官方實(shí)現(xiàn)類,包括引入的spring-security-cas中實(shí)現(xiàn)的CasAuthenticationEntryPoint,commence方法最終也是response.sendRedirect(redirectUrl)方式跳轉(zhuǎn)

public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean {
        ...
        public final void commence(HttpServletRequest servletRequest, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        String urlEncodedService = this.createServiceUrl(servletRequest, response);
        String redirectUrl = this.createRedirectUrl(urlEncodedService);
        this.preCommence(servletRequest, response);
        response.sendRedirect(redirectUrl);
    }
}


SecurityConfig中指定我們自定義的authenticationEntryPoint即可,但是為什么在這里指定就可以呢?
ExceptionTranslationFilter中authenticationEntryPoint的定義來(lái)自構(gòu)造函數(shù),我們?cè)赟ecurityConfig#configure(HttpSecurity http)方法中有如下配置:

@Override
public void configure(HttpSecurity http) throws Exception {
    http
            ...
            .exceptionHandling()
            .authenticationEntryPoint(new CustomAuthenticationEntryPoint(AuthUtils.isCasLogin, casProvider))
            ...
}

exceptionHandling()方法初始化了ExceptionHandlingConfigurer類,

public ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling() throws Exception {
   return getOrApply(new ExceptionHandlingConfigurer<>());
}

SecurityConfig#configure(HttpSecurity http)中接著又調(diào)用了ExceptionHandlingConfigurer的authenticationEntryPoint方法,該方法指定了authenticationEntryPoint的具體實(shí)現(xiàn)類

public ExceptionHandlingConfigurer<H> authenticationEntryPoint(
      AuthenticationEntryPoint authenticationEntryPoint) {
   this.authenticationEntryPoint = authenticationEntryPoint;
   return this;
}

SecurityConfig上的注解@EnableWebSecurity初始化了WebSecurityConfiguration.class,該配置中定義了springSecurityFilterChain,通過(guò)層層調(diào)用,最終調(diào)用了ExceptionHandlingConfigurer#configure(H http)方法,該方法中獲取了我們?cè)赟ecurityConfig中指定的authenticationEntryPoint,然后調(diào)用ExceptionTranslationFilter的構(gòu)造函數(shù)初始化,最終系統(tǒng)認(rèn)證失敗的時(shí)候,ExceptionTranslationFilter的doFilter就會(huì)調(diào)用我們自己定義的authenticationEntryPoint

三、另外一種修改跳轉(zhuǎn)的思路

通過(guò)源碼可以發(fā)現(xiàn),實(shí)現(xiàn)跳轉(zhuǎn)的代碼最終都是通過(guò)response.sendRedirect(redirectUrl)實(shí)現(xiàn),該方法在前后端分離的應(yīng)用下是無(wú)法實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)的,那能否重寫HttpServletResponse的sendRedirect方法呢?

可以定義CasResponseWrapper,繼承HttpServletResponseWrapper,在原先傳入HttpServletResponse的地方傳入

new CasResponseWrapper(httpServletResponse, httpServletRequest, casProperties)

系統(tǒng)中再有調(diào)用response.sendRedirect方法的地方就會(huì)調(diào)用到我們自定義的跳轉(zhuǎn)方法。無(wú)法重寫跳轉(zhuǎn)方法的時(shí)候可以嘗試。

該方式在之前對(duì)接客戶方提供的封裝好的saml client認(rèn)證包的時(shí)候使用過(guò),親測(cè)有效。

public class CasResponseWrapper extends HttpServletResponseWrapper {

    private final HttpServletResponse httpServletResponse;

    private final HttpServletRequest httpServletRequest;

    private final CasProperties casProperties;

    public CasResponseWrapper(HttpServletResponse response, HttpServletRequest request, CasProperties casProperties) {
        super(response);
        this.httpServletResponse = response;
        this.httpServletRequest = request;
        this.casProperties = casProperties;
    }

    /**
     *
     *
     * @param redirectPath
     */
    @SneakyThrows
    @Override
    public void sendRedirect(String redirectPath) {
        if (前后端不分離) {
            httpServletResponse.sendRedirect(redirectPath);
            return;
        }
        
        httpServletRequest.setAttribute("redirectPath", redirectPath);
        // 跳轉(zhuǎn)到指定的controller,并且在請(qǐng)求頭中帶上要跳轉(zhuǎn)的地址,在該controller返回401,或者拋出特定異常,定義異常攔截器返回401
        httpServletRequest.getRequestDispatcher("/loginRedirect")
                .forward(httpServletRequest, httpServletResponse);
    }
}

可能遇到的問(wèn)題:

1.跨域。

我的處理方式是在接入前端應(yīng)用時(shí)啟了個(gè)nginx代理,前端打包放入指定目錄,前后端的訪問(wèn)都由nginx代理

2.瀏覽器循環(huán)跳轉(zhuǎn)或者跳到空白頁(yè)。

這種情況可能有很多種原因,需要跟蹤代碼到源碼包里面具體查看,因?yàn)檎J(rèn)證失敗可能有多重情況,但是返回到應(yīng)用端異常查看的時(shí)候都一樣,比如我在調(diào)試的時(shí)候遇到service與ticket不匹配,ticket校驗(yàn)不通過(guò)的情況,需要檢查登錄頁(yè)面url的service參數(shù),與調(diào)用認(rèn)證中心校驗(yàn)時(shí)的service是否一致,因?yàn)樵诖a調(diào)試過(guò)程中,可能有配置錯(cuò)誤或者手動(dòng)修改過(guò)配置,校驗(yàn)ticket的時(shí)候需要傳入service參數(shù)與ticket參數(shù),如果不一致,則不能通過(guò),這種情況下可能就會(huì)直接認(rèn)證失敗跳到空白頁(yè)。
集成springsecurity時(shí),登錄后如果未授權(quán),檢查授權(quán)信息為空,可能就會(huì)循環(huán)跳轉(zhuǎn),因?yàn)闊o(wú)授權(quán)信息,框架會(huì)認(rèn)為校驗(yàn)失敗,跳到首頁(yè),但是實(shí)際上已經(jīng)登陸過(guò)將session寫入cookie了,發(fā)起請(qǐng)求又認(rèn)為是認(rèn)證過(guò)的,然后又無(wú)權(quán)限導(dǎo)致跳轉(zhuǎn)...

好像還有別的情況會(huì)循環(huán)重定向...總之debug進(jìn)去找到原因,然后解決

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容