使用Spring Security 集成 CAS 完成单点登录

一、企业单一登录(CAS)

1.Java(Spring Webflow / MVC servlet)服务器组件

2.可插拔认证支持(LDAP,数据库,X.509,双因素)

3.支持多种协议(CAS,SAML,OAuth,OpenID)

4.跨平台的客户端支持(Java,.Net,PHP,Perl,Apache等)

5.与uPortal,Liferay,BlueSocket,Moodle和Google Apps集成,仅举几例

CAS提供了一个友好的开源社区,积极支持和贡献项目。虽然该项目植根于更高级的开放源代码,但已经发展成为世界500强企业和小型专用设施的国际用户。

二、如何部署您的CAS

在项目中安装CAS服务器,需要去官方github下载CAS标准WAR文件,在WAR文件中有标准的单点登录登出页面。当然您还需要对deployerConfigContext.xml中指定AuthenticationHandler进行简单的修改,已满足您对数据库的操作需求。CAS 本身包含大量的AuthenticationHandler,可以协助解决相应的问题。

除CAS服务器本身之外,其他关键角色当然是在企业中部署的安全Web应用程序,这些Web应用程序被称为“服务“。有三种类型的服务:验证服务票据,获得代理票据,验证代理票据。验证代理票据的不同之处在于代理列表必须经过验证,并且通常可以重用代理。

CAS本身设计在HTTPS环境下,在本地测试以及个人学习情况下可以对CAS做些相应修改,使它支持HTTP访问。在CAS的WAR文件目录:WEB-INF\classes\services下修改HTTPSandIMAPS-10000001.json配置文件,将serviceId属性的值修改为:

"serviceId":"^(https|imaps|http)://.*"

在CAS 4.2版本后,CAS的所有配置都放在cas.properties文件中,所以为了可以自定义cas.properties的路径,您可以修改WEB-INF\spring-configuration\propertyFileConfigurer.xml文件中的:

<util:properties id="casProperties" location="classpath:cas.properties" />

为了让CAS能够通过数据库鉴定用户凭证,需要配置Database Authentication。官方文档详见:https://apereo.github.io/cas/4.2.x/installation/Database-Authentication.html。数据库认证有四种:

1.QueryDatabaseAuthenticationHandler,通过用户名和明文密码进行验证。
首先在cas.properties中配置:
# cas.jdbc.authn.query.sql=select password from users where username=?
在deployerConfigContext.xml中配置
<alias name="queryDatabaseAuthenticationHandler" alias="primaryAuthenticationHandler" />
<alias name="dataSource" alias="queryDatabaseDataSource" />

2.SearchModeSearchDatabaseAuthenticationHandler,通过查询用户名和密码来搜索用户记录; 如果至少有一个结果被发现,用户将被认证。
首先在cas.properties中配置
# cas.jdbc.authn.search.password=
# cas.jdbc.authn.search.user=
# cas.jdbc.authn.search.table=
在deployerConfigContext.xml中配置
<alias name="searchModeSearchDatabaseAuthenticationHandler" alias="primaryAuthenticationHandler" />
<alias name="dataSource" alias="searchModeDatabaseDataSource" />

 3.BindModeSearchDatabaseAuthenticationHandler,尝试使用用户名和(散列)密码创建数据库连接来对用户进行身份验证。
在deployerConfigContext.xml中配置
<alias name="bindModeSearchDatabaseAuthenticationHandler" alias="primaryAuthenticationHandler" />
<alias name="dataSource" alias="bindSearchDatabaseDataSource" />

4.QueryAndEncodeDatabaseAuthenticationHandler,一个JDBC查询处理程序,它将撤回用户的密码和私有salt值,并使用公共salt值验证编码的密码。 假设一切都在同一个数据库表内。 支持迭代次数和私盐的设置。
首先在cas.properties中配置
# cas.jdbc.authn.query.encode.sql=
# cas.jdbc.authn.query.encode.alg=
# cas.jdbc.authn.query.encode.salt.static=
# cas.jdbc.authn.query.encode.password=表字段名
# cas.jdbc.authn.query.encode.salt=表字段名
# cas.jdbc.authn.query.encode.iterations.field=表字段名
# cas.jdbc.authn.query.encode.iterations=
在deployerConfigContext.xml中配置
<alias name="queryAndEncodeDatabaseAuthenticationHandler" alias="primaryAuthenticationHandler" />
<alias name="dataSource" alias="queryEncodeDatabaseDataSource" />

一般选择第四种数据认证方式,修改完成后丢到tomcat下运行即可。
cas的访问地址:ip:port/cas/login
cas的登出地址:ip:port/cas/logout

三、Spring Security和CAS的集成

Web浏览器,CAS服务器和Spring安全服务之间的基本交互如下:

CAS或Spring Security不管理公共页面的处理,当用户请求一个安全的页面或者它使用的一个安全的页面。 Spring Security的ExceptionTranslationFilter将检测到AccessDeniedException或AuthenticationException。

由于用户的Authentication对象(或缺少)导致AuthenticationException,因此ExceptionTranslationFilter将调用已配置的AuthenticationEntryPoint。如果使用CAS,这将是CasAuthenticationEntryPoint类。

CasAuthenticationEntryPoint将把用户的浏览器重定向到CAS服务器。它还会显示一个服务参数,它是Spring Security服务(您的应用程序)的回调URL。例如,浏览器重定向到的URL可能是

https://my.company.com/cas/login?service= HTTPS%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin / CAS。

用户的浏览器重定向到CAS后,系统会提示用户输入用户名和密码。如果用户提交了一个表示他们以前登录过的会话cookie,他们将不会再被提示重新登录(这个过程是个例外,我们将在后面介绍)。 CAS将使用上述的PasswordHandler(或使用CAS 3.0的AuthenticationHandler)来决定用户名和密码是否有效。

CAS成功登录后,会将用户浏览器重定向到原始服务。它还将包含一个票据参数,这是一个不透明的字符串,代表“服务票据”。继续前面的例子,浏览器被重定向到的URL可能是

https://server3.company.com/webapp/login/cas?ticket=ST-0-ER94xMJmn6pha35CQRoZ。

回到服务Web应用程序,CasAuthenticationFilter总是监听/ login / cas的请求(这是可配置的,但是我们将使用这个介绍中的默认值)。处理过滤器将构建代表服务票据的UsernamePasswordAuthenticationToken。主体将等于CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER,而凭证将是服务票证不透明值。这个认证请求将被交给配置的AuthenticationManager。

AuthenticationManager实现将是ProviderManager,它又被配置了CasAuthenticationProvider。 CasAuthenticationProvider只响应包含CAS特定主体(如CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER)和CasAuthenticationToken(稍后讨论)的UsernamePasswordAuthenticationToken。

CasAuthenticationProvider将使用TicketValidator实现来验证服务票证。这通常是一个Cas20ServiceTicketValidator,它是包含在CAS客户端库中的一个类。如果应用程序需要验证代理票证,则使用Cas20ProxyTicketValidator。 TicketValidator向CAS服务器发出HTTPS请求,以验证服务票据。它也可能包含一个代理回调URL,它包含在这个例子中:

https://my.company.com/cas/proxyValidate?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin/cas&ticket= ST-0-ER94xMJmn6pha35CQRoZ&pgtUrl = HTTPS://server3.company.com/webapp/login/cas/proxyreceptor。

回到CAS服务器,验证请求将被接收。如果所提供的服务票据与发行票据的服务URL相匹配,则CAS将以XML表示用户名的肯定响应。如果任何代理参与了身份验证(如下所述),那么代理列表也会包含在XML响应中。

[可选]如果对CAS验证服务的请求包含代理回调URL(在pgtUrl参数中),则CAS将在XML响应中包含一个pgtIou字符串。这pgtIou代表代理授予票借条。然后,CAS服务器将创建自己的HTTPS连接回pgtUrl。这是为了相互认证CAS服务器和声称的服务URL。 HTTPS连接将用于将授权票据的代理发送到原始Web应用程序。例如,

  https://server3.company.com/webapp/login/cas/proxyreceptor?pgtIou=PGTIOU-0-R0zlgrl4pdAQwBvJWO3vnNpevwqStbSGcq3vKB2SqSFFRnjPHt&pgtId=PGT-1-si9YkkHLrtACBo64rmsi3v2nf7cpCResXg5MpESZFArbaZiOKH。

Cas20TicketValidator将解析从CAS服务器收到的XML。它将返回CasAuthenticationProvider TicketResponse,其中包括用户名(强制),代理列表(如果有任何涉及),和代理授予票证IOU(如果代理回调被请求)。

接下来,CasAuthenticationProvider将调用已配置的CasProxyDecider。 CasProxyDecider指示TicketResponse中的代理列表是否可以被服务接受。 Spring Security提供了几个实现:RejectProxyTickets,AcceptAnyCasProxy和NamedCasProxyDecider。这些名称在很大程度上是不言而喻的,除了NamedCasProxyDecider允许提供可信代理列表。

CasAuthenticationProvider接下来将请求一个AuthenticationUserDetailsS​​ervice来加载适用于Assertion中包含的用户的GrantedAuthority对象。
如果没有问题,CasAuthenticationProvider构造一个CasAuthenticationToken,包括TicketResponse和GrantedAuthoritys中包含的细节。

控制然后返回到CasAuthenticationFilter,它将创建的CasAuthenticationToken放置在安全上下文中。

用户的浏览器被重定向到导致AuthenticationException的原始页面(或根据配置的自定义目标)。

四、Spring Boot +Spring Security+CAS开发(代理票据认证)

CasAuthenticationProvider区分有状态和无状态客户端。 有状态的客户端被认为是提交给CasAuthenticationFilter的filterProcessUrl的。 无状态客户端是指向除FilterProcessUrl以外的URL向CasAuthenticationFilter提交身份验证请求的任何客户端。

由于远程协议无法在HttpSession的上下文中呈现,因此不可能依赖于在请求之间的会话中存储安全上下文的默认实践。 此外,由于CAS服务器在TicketValidator验证之后使其无效,因此在后续请求中显示相同的代理票证将不起作用。

CasConfing配置:
//客户端配置
public static String casServiceHost="http://127.0.0.1:8080";
public static String casServiceLogin=casServiceHost+"/login/cas";
public static String casServiceLogout=casServiceHost+"/logout/cas";
public static String casServiceProxyCallbackUrl="/login/cas/proxyreceptor";
public static String casServiceFailureHandler="/cas/casfailed";

//cas服务端配置
@Value("${cas.server.host:http://127.0.0.1:8081/cas}")
public static String casServerUrlPrefix="http://127.0.0.1:8081/cas";
public static String casServerUrlLogin=casServerUrlPrefix+"/login";
public static String casServerUrlLogout=casServerUrlPrefix+"/logout";

@Autowired
public static ProxyGrantingTicketStorageImpl pgtStorage;

@Bean
public ServiceProperties serviceProperties(){
    ServiceProperties serviceProperties=new ServiceProperties();
    serviceProperties.setService(casServiceLogin);
    serviceProperties.setAuthenticateAllArtifacts(true);
    return serviceProperties;
}

@Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint(@Qualifier("serviceProperties") ServiceProperties serviceProperties){
    CasAuthenticationEntryPoint entryPoint=new CasAuthenticationEntryPoint();
    entryPoint.setServiceProperties(serviceProperties);
    entryPoint.setLoginUrl(casServerUrlLogin);

    return entryPoint;
}

@Bean("pgtStorage")
public ProxyGrantingTicketStorageImpl proxyGrantingTicketStorageImpl(){
    return new ProxyGrantingTicketStorageImpl();
}

@Bean("casAuthenticationProvider")
public CasAuthenticationProvider casAuthenticationProvider(@Qualifier("serviceProperties") ServiceProperties serviceProperties,
        @Qualifier("customCasUserDetailsService") CustomCasUserDetailsService customCasUserDetailsService){
    CasAuthenticationProvider authenticationProvider=new CasAuthenticationProvider();
    authenticationProvider.setKey("casProvider") ;
    authenticationProvider.setServiceProperties(serviceProperties);
    Cas20ProxyTicketValidator ticketValidator=new Cas20ProxyTicketValidator(casServerUrlPrefix);
    ticketValidator.setAcceptAnyProxy(true);//允许所有代理回调链接
    ticketValidator.setProxyGrantingTicketStorage(pgtStorage);
    authenticationProvider.setTicketValidator(ticketValidator);
    authenticationProvider.setAuthenticationUserDetailsService(customCasUserDetailsService);
    //无状态缓存
    EhCacheBasedTicketCache ticketCache=new EhCacheBasedTicketCache();
    ticketCache.setCache(new Cache("casTickets", 50, true, false, 3600, 900));
    authenticationProvider.setStatelessTicketCache(ticketCache);
    
    return authenticationProvider;
}

//单点登出,跳转到客户端的登出链接
@Bean("requestSingleLogoutFilter")
public LogoutFilter logoutFilter() {
    LogoutFilter logoutFilter = new LogoutFilter(casServerUrlLogout, new SecurityContextLogoutHandler());
    logoutFilter.setFilterProcessesUrl(casServiceLogout);
    return logoutFilter;
}

WebSecurityCasConfig配置:

    @Autowired
CasAuthenticationProvider casAuthenticationProvider;

@Autowired
CasAuthenticationEntryPoint casAuthenticationEntryPoint;

@Autowired
LogoutFilter requestSingleLogoutFilter;

@Autowired
ServiceProperties serviceProperties;
    
public CasAuthenticationFilter casAuthenticationFilter() throws Exception{
    CasAuthenticationFilter casAuthenticationFilter=new CasAuthenticationFilter();
    casAuthenticationFilter.setAuthenticationManager(authenticationManager());
    casAuthenticationFilter.setServiceProperties(serviceProperties);
    casAuthenticationFilter.setProxyGrantingTicketStorage(CasConfing.pgtStorage);
    casAuthenticationFilter.setProxyReceptorUrl(CasConfing.casServiceProxyCallbackUrl);
    casAuthenticationFilter.setAuthenticationDetailsSource(new ServiceAuthenticationDetailsSource(serviceProperties));
    casAuthenticationFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(CasConfing.casServiceFailureHandler));

    return casAuthenticationFilter;
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    // TODO Auto-generated method stub
    http
    .authorizeRequests()
    .antMatchers("/cas/casfailed").permitAll()
    .antMatchers("/secure/extreme/").access("hasRole('ROLE_SUPERVISOR')")
    .antMatchers("/secure/**").access("hasRole('ROLE_USER')")
    .anyRequest().authenticated()
    .and()
    .logout()
    .logoutUrl("/logout/cas")
    .logoutSuccessUrl(CasConfing.casServerUrlLogout+"?service="+CasConfing.casServiceHost+"/index")
    .permitAll()
    .and()
    .csrf().disable();

    //CAS服务器的单点登录
    SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
    singleSignOutFilter.setCasServerUrlPrefix(CasConfing.casServerUrlPrefix);
    http
    .exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint)
    .and()
    .addFilter(casAuthenticationFilter())
    .addFilterBefore(requestSingleLogoutFilter, LogoutFilter.class)
    .addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class);
}


@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    // TODO Auto-generated method stub
    auth.authenticationProvider(casAuthenticationProvider);
    super.configure(auth);
}

CustomCasUserDetailsService自定义认证用户信息处理配置:

@Service

public class CustomCasUserDetailsService implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken>{

@Override
public UserDetails loadUserDetails(CasAssertionAuthenticationToken token) throws UsernameNotFoundException {
    // TODO Auto-generated method stub
    System.err.println("当前认证成功的用户名:"+token.getName());
    List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
    GrantedAuthority grantedAuthority=new SimpleGrantedAuthority("ROLE_SUPERVISOR");
    grantedAuthorities.add(grantedAuthority);
    grantedAuthority=new SimpleGrantedAuthority("ROLE_USER");
    grantedAuthorities.add(grantedAuthority);
    
    return new User(token.getName(), "a52302c58f4a60f49b1ad2f36add6d0a-000000", grantedAuthorities);
}

}

至此,Spring Security+CAS集成配置以完成。

您可以通过访问客户端Security安全页面:
http://127.0.0.1:8080/index,
security会转到CAS服务器登录链接
http://127.0.0.1:8081/cas/login?service=http%3A%2F%2F127.0.0.1%3A8080%2Flogin%2Fcas
登录认证通过后即可访问安全页面。

多站点:
分别部署两个站点:serviceCas01,serviceCas02
 http://127.0.0.1:8080/serviceCas01/index,
 http://127.0.0.1:8082/serviceCas02/index,
serviceCas01登录认证成功后,直接通过访问  http://127.0.0.1:8082/serviceCas02/index,即可无需登录访问。通过http://127.0.0.1:8080/serviceCas01/logout/cas成功登出后,  重新刷新页面http://127.0.0.1:8082/serviceCas02/index,也会登出。

后续有时间,再配图啦。不足之处,谢谢指教。

铭言:  
        吾等前方,再无对手
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容

  • 1. CAS 简介 1.1. What is CAS ? CAS ( Central Authenti...
    人在码途阅读 9,715评论 3 51
  • 简介 Cas介绍 CAS ( Central Authentication Service ),最初由耶鲁大学的S...
    但莫阅读 14,503评论 0 10
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,103评论 18 139
  • 以下是官网直译:https://oauth.net/ 1. 首页 OAuth是一种开放协议(注:协议是公开的,任何...
    KennethChen93阅读 10,745评论 0 20
  • [00:10.00]Lyrics by @MPlover [00:22.45]connection [00:24....
    猴大猫阅读 179评论 0 0