spring boot 2.0 集成 shiro 和 pac4j cas单点登录(解决单点登出失效问题)

一、CAS简介

CAS 是 Central Authentication Service 的缩写 —— 中央认证服务,一种独立开放指令协议,是 Yale 大学发起的一个企业级开源项目,旨在为 Web 应用系统提供一种可靠的 SSO 解决方案。

img

CAS 支持以下特性:

  • CAS v1, v2 和 v3 协议
  • SAML v1 和 v2 协议
  • OAuth v2 协议
  • OpenID & OpenID Connect 协议
  • WS-Federation Passive Requestor 协议
  • 通过 JAAS, LDAP, RDBMS, X.509, Radius, SPNEGO, JWT, Remote, Trusted, BASIC, Apache Shiro, MongoDb, Pac4J 等组件进行身份验证
  • 将身份验证委派至 WS-FED, Facebook, Twitter, SAML IdP, OpenID, OpenID Connect, CAS 等地方
  • 通过 ABAC, Time/Date, REST, Internet2 的 Grouper 等因子进行身份验证
  • 通过 Hazelcast, Ehcache, JPA, Memcached, Apache Ignite, MongoDb, Redis, DynamoDb, Couchbase 等工具进行 HA 集群部署
  • 由 JSON, LDAP, YAML, JPA, Couchbase, MongoDb, DynamoDb, Redis 等工具支持的应用程序注册
  • 通过 Duo Security, YubiKey, RSA, Google Authenticator 等因子进行多因子身份验证
  • 用于管理日志记录、监控、统计、配置和客户端注册等的管理UI
  • 密码管理和密码策略实施

二、spring boot 2.0 集成 shiro 和 pac4j cas单点登录

参照
spring boot 2.0 集成 shiro 和 pac4j cas单点登录

若依集成cas客户端

网上的资料比较少,都是互相复制、粘贴,本人懒得粘贴,自己去原网址去阅读!

注意:以上参考资料本人尝试了下,单点登出无效!!!

三、解决单点登出失效问题

以下截取部分ShiroConfig配置类代码

 //cas 资源认证拦截器
 SecurityFilter securityFilter = new SecurityFilter();
 securityFilter.setConfig(config);
 securityFilter.setClients(clientName);

 //cas 认证后回调拦截器
 CallbackFilter callbackFilter = new CallbackFilter();
 callbackFilter.setDefaultUrl(projectUrl);
 callbackFilter.setConfig(config);

 // 注销 拦截器
 LogoutFilter logoutFilter = new LogoutFilter();
 logoutFilter.setConfig(config);
 // CAS服务端登出
 logoutFilter.setCentralLogout(true);
 logoutFilter.setLocalLogout(true);
 logoutFilter.setDefaultUrl(projectUrl + "/callback?client_name=" + clientName);

1.单点登出流程

CAS单点登录 登出流程图
单点登录

单点登出

2.排查单点登出客户端cas.callbackUrl是否出现localhost

------------------------------------------------划重点(开始)------------------------------------------------

  • client-host-url支持域名、主机名、IP地址。CAS服务端对client-host-url有严格的校验方法(包括正则校验、顶级域名校验、IPv4、IPv6校验)。只要搞清楚原理,你会发现官方的考虑还是很周全的,一般情况下,根本不需要自己重写具体实现类。
// url校验入口方法
org.apereo.cas.logout.DefaultSingleLogoutServiceLogoutUrlBuilder#determineLogoutUrl
  • 域名校验(含顶级域名校验)
// cas.callbackUrl的默认校验方法,
org.apereo.cas.web.SimpleUrlValidator#isValid
// SimpleUrlValidator对应的工厂类bean(cas-server-core-web包下的CasCoreWebConfiguration.java配置类注入该bean)
org.apereo.cas.web.SimpleUrlValidatorFactoryBean
// 域名校验
org.apache.commons.validator.routines.DomainValidator#isValid
// 顶级域名校验(以点号分割取最后一部分,常见的顶级域名有.com、.cn、.gov、.org、.edu)
// 本人测试时使用cas.example.client,可想而知肯定验证不通过!!!建议使用client1.example.org来测试。
org.apache.commons.validator.routines.DomainValidator#isValidTld
  • 主机名校验
    对于localhost和localhost.localdomain,要想验证通过,一定要在CAS服务端配置以下属性。否则配置cas.callbackUrl配置为localhost会导致单证登出不生效!
# 官网属性配置:https://apereo.github.io/cas/5.3.x/installation/Configuration-Properties.html
cas:
  httpClient:
    hostNameVerifier: NONE
    # 启用域名本地验证,默认false
    allowLocalLogoutUrls: true
    # 自定义校验(为空时忽略该属性,默认即为空)
    authorityValidationRegEx:
    authorityValidationRegExCaseSensitive: false
  • IP校验
    以IPv4为例,可配置127.0.0.1,没什么可讲的!

客户端示例:

# 默认不支持域名本地验证,需CAS服务端启用
cas:
  server-login-url: https://cas.example.org:8443/cas/login
  callbackUrl: http://client1.example.org:80/callback
  client-name: client1

总结域名配置要按规矩来,不要像我一样配置成cas.example.client,查错查半天!!!

------------------------------------------------划重点(结束)------------------------------------------------
如果想禁用url校验,可以通过自己实现接口SingleLogoutServiceLogoutUrlBuilder,代码如下:

package com.example.cas.config;

import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.logout.SingleLogoutServiceLogoutUrlBuilder;
import org.apereo.cas.web.UrlValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @version 1.0
 * @date :Created in 2020/11/11 14:50
 * 功能描述:单点登出逻辑 {@link org.apereo.cas.logout.config.CasCoreLogoutConfiguration}
 */
@Configuration("CustomLogoutConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CustomLogoutConfiguration {

    @Autowired
    private UrlValidator urlValidator;


    @Bean
    public SingleLogoutServiceLogoutUrlBuilder singleLogoutServiceLogoutUrlBuilder() {
        return new CustomSingleLogoutServiceLogoutUrlBuilder(this.urlValidator);
    }

}

package com.example.cas.config;

import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apereo.cas.authentication.principal.WebApplicationService;
import org.apereo.cas.logout.SingleLogoutServiceLogoutUrlBuilder;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.util.CollectionUtils;
import org.apereo.cas.web.UrlValidator;

import java.net.URL;
import java.util.Collection;

/**
 * @version 1.0
 * @date :Created in 2020/11/11 14:59
 * 功能描述:
 */
@Slf4j
@RequiredArgsConstructor
public class CustomSingleLogoutServiceLogoutUrlBuilder implements SingleLogoutServiceLogoutUrlBuilder {

    private final UrlValidator urlValidator;

    @Override
    @SneakyThrows
    public Collection<URL> determineLogoutUrl(final RegisteredService registeredService, final WebApplicationService singleLogoutService) {
        final URL serviceLogoutUrl = registeredService.getLogoutUrl();
        if (serviceLogoutUrl != null) {
            log.debug("Logout request will be sent to [{}] for service [{}]", serviceLogoutUrl, singleLogoutService);
            return CollectionUtils.wrap(serviceLogoutUrl);
        }
        final String originalUrl = singleLogoutService.getOriginalUrl();
        // TODO 通过禁用url校验,使得CAS服务端发送退出请求给CAS客户端
//        if (this.urlValidator.isValid(originalUrl)) {
            log.debug("Logout request will be sent to [{}] for service [{}]", originalUrl, singleLogoutService);
            final URL url = new URL(originalUrl);
            return CollectionUtils.wrap(url);
//        }
//        log.debug("Logout request will not be sent; The URL [{}] for service [{}] is not valid", originalUrl, singleLogoutService);
//        return new ArrayList<>();
    }
}

3.排查Pac4jConfig文件SessionStore类型

以下截取Pac4jConfig配置文件部分内容

    /**
     * 自定义存储
     * 注意:不可使用{@link io.buji.pac4j.context.ShiroSessionStore},否则无法建立票据与session之间联系,导致单点退出失效!
     * @return
     */
    @Bean
    public SessionStore sessionStore(){
        return new J2ESessionStore();
    }

:为什么不能用ShiroSessionStore实现类?
:因为io.buji.pac4j.context.ShiroSessionStore#getTrackableSession方法返回值始终为null,CAS客户端源码打断点查看你就明白了!

io.buji.pac4j.context.ShiroSessionStore#getTrackableSession
org.pac4j.cas.logout.DefaultCasLogoutHandler#recordSession
org.pac4j.cas.logout.DefaultCasLogoutHandler#destroySessionBack

  • 1.cas.callbackUrl配置成localhost时错误日志
2020-11-11 09:11:22,362 INFO [org.apereo.cas.logout.DefaultLogoutManager] - <Performing logout operations for [TGT-1-********************************************************8c-MzwaUSIQLAPTOP-O5TBUP89]>
2020-11-11 09:11:22,368 DEBUG [org.apereo.cas.logout.DefaultLogoutManager] - <Handling single logout callback for [AbstractWebApplicationService(id=http://localhost:80/callback?client_name=client1, originalUrl=http://localhost:80/callback?client_name=client1, artifactId=null, principal=9, source=service, loggedOutAlready=false, format=XML, attributes={})]>
2020-11-11 09:11:22,368 DEBUG [org.apereo.cas.logout.DefaultSingleLogoutServiceMessageHandler] - <Processing logout request for service [AbstractWebApplicationService(id=http://localhost:80/callback?client_name=client1, originalUrl=http://localhost:80/callback?client_name=client1, artifactId=null, principal=9, source=service, loggedOutAlready=false, format=XML, attributes={})]...>
2020-11-11 09:11:22,368 DEBUG [org.apereo.cas.logout.DefaultSingleLogoutServiceMessageHandler] - <Service [AbstractWebApplicationService(id=http://localhost:80/callback?client_name=client1, originalUrl=http://localhost:80/callback?client_name=client1, artifactId=null, principal=9, source=service, loggedOutAlready=false, format=XML, attributes={})] supports single logout and is found in the registry as [AbstractRegisteredService(serviceId=^(https|imaps|http)://(localhost|cas.example.org).*, name=自定义主题, theme=simple, informationUrl=null, privacyUrl=null, responseType=null, id=1000, description=this is a localhost service, expirationPolicy=DefaultRegisteredServiceExpirationPolicy(deleteWhenExpired=false, notifyWhenDeleted=false, expirationDate=null), proxyPolicy=org.apereo.cas.services.RefuseRegisteredServiceProxyPolicy@1, evaluationOrder=1000, usernameAttributeProvider=org.apereo.cas.services.DefaultRegisteredServiceUsernameProvider@87297e2, logoutType=BACK_CHANNEL, requiredHandlers=[], attributeReleasePolicy=ReturnAllAttributeReleasePolicy(super=AbstractRegisteredServiceAttributeReleasePolicy(attributeFilter=null, principalAttributesRepository=DefaultPrincipalAttributesRepository(), consentPolicy=DefaultRegisteredServiceConsentPolicy(enabled=true, excludedAttributes=null, includeOnlyAttributes=null), authorizedToReleaseCredentialPassword=false, authorizedToReleaseProxyGrantingTicket=false, excludeDefaultAttributes=false, authorizedToReleaseAuthenticationAttributes=true, principalIdAttribute=null)), multifactorPolicy=DefaultRegisteredServiceMultifactorPolicy(multifactorAuthenticationProviders=[], failureMode=NOT_SET, principalAttributeNameTrigger=null, principalAttributeValueToMatch=null, bypassEnabled=false), logo=null, logoutUrl=null, accessStrategy=DefaultRegisteredServiceAccessStrategy(order=0, enabled=true, ssoEnabled=true, unauthorizedRedirectUrl=null, delegatedAuthenticationPolicy=DefaultRegisteredServiceDelegatedAuthenticationPolicy(allowedProviders=[]), requireAllAttributes=true, requiredAttributes={}, rejectedAttributes={}, caseInsensitive=false), publicKey=null, properties={}, contacts=[])]. Proceeding...>
2020-11-11 09:11:22,457 DEBUG [org.apereo.cas.logout.DefaultSingleLogoutServiceLogoutUrlBuilder] - <Logout request will not be sent; The URL [http://localhost:80/callback?client_name=client1] for service [AbstractWebApplicationService(id=http://localhost:80/callback?client_name=client1, originalUrl=http://localhost:80/callback?client_name=client1, artifactId=null, principal=9, source=service, loggedOutAlready=false, format=XML, attributes={})] is not valid>
2020-11-11 09:11:22,457 DEBUG [org.apereo.cas.logout.DefaultSingleLogoutServiceMessageHandler] - <Prepared logout url [[]] for service [AbstractWebApplicationService(id=http://localhost:80/callback?client_name=client1, originalUrl=http://localhost:80/callback?client_name=client1, artifactId=null, principal=9, source=service, loggedOutAlready=false, format=XML, attributes={})]>
2020-11-11 09:11:22,457 DEBUG [org.apereo.cas.logout.DefaultSingleLogoutServiceMessageHandler] - <Service [AbstractWebApplicationService(id=http://localhost:80/callback?client_name=client1, originalUrl=http://localhost:80/callback?client_name=client1, artifactId=null, principal=9, source=service, loggedOutAlready=false, format=XML, attributes={})] does not support logout operations given no logout url could be determined.>
2020-11-11 09:11:22,457 DEBUG [org.apereo.cas.logout.DefaultLogoutManager] - <Invoking logout handler [CasCoreLogoutConfiguration$$Lambda$217/1552758845] to process ticket [TGT-1-********************************************************8c-MzwaUSIQLAPTOP-O5TBUP89]>
2020-11-11 09:11:22,458 INFO [org.apereo.cas.logout.DefaultLogoutManager] - <[0] logout requests were processed>
  • 使用io.buji.pac4j.context.ShiroSessionStore类作为session存储实现类时
9:26:46.870 [http-nio-80-exec-9] ERROR o.p.c.l.DefaultCasLogoutHandler - [destroySessionBack,99] - No trackable session found for back channel logout. Either the session store does not support to track session or it has expired from the store and the store settings must be updated (expired data)
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
单点登录(Single Sign-On,简称SSO)是一种身份验证和授权机制,允许用户使用一组凭据登录到多个应用程序和系统,而不需要在每个应用程序中单独输入用户名和密码。 Shiro是一个开源的Java安全框架,提供身份验证、授权、加密、会话管理等功能。Spring Security OAuth 2.0Spring Security的一个扩展,用于实现OAuth 2.0协议,提供基于令牌的身份验证和授权。 集成ShiroSpring Security OAuth 2.0可以实现单点登录功能。首先,需要配置一个认证中心作为身份提供者,其他应用程序和系统将依赖该认证中心进行身份验证和用户信息获取。在集成过程中,需要在认证中心和其他应用程序中配置Shiro的过滤器链,并将相关的OAuth 2.0配置添加到Spring Security的配置中。 当用户在一个应用程序中登录时,该应用程序将重定向至认证中心,用户在认证中心完成身份验证后,会生成一个访问令牌(Access Token)。该访问令牌可以在其他应用程序之间传递,并由Shiro验证器进行验证。在其他应用程序中,用户使用访问令牌向认证中心进行验证,通过后就可以无需重新输入用户名和密码,直接访问其他应用程序。 集成ShiroSpring Security OAuth 2.0实现单点登录的好处是可以减少用户登录的次数,提高用户体验,同时也可以提高系统的安全性,减少密码泄露的风险。这样的集成方案可以适用于多个系统和应用程序之间的跨域身份验证和授权需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬山境KL攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值