前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >springboot 集成CAS 实现单点登录

springboot 集成CAS 实现单点登录

作者头像
Mshu
发布2020-06-22 19:24:07
8.6K3
发布2020-06-22 19:24:07
举报
文章被收录于专栏:咸鱼不闲咸鱼不闲

最近新参与的项目用到了cas单点登录,我还不会,这怎么能容忍!空了学习并搭建了一个spring-boot 集成CAS 的demo。实现了单点登录与登出。

单点登录英文全称是:Single Sign On,简称SSO。 含义:在多个相互信任的系统中,只要登录一个系统其他系统均可访问。

CAS 是一种使用广泛的单点登录实现,分为客户端CAS Client和服务端 CAS Service,客户端就是我们的系统,服务端是认证中心,由CAS提供,我们需要稍作修改,启动起来就可以用。~~~~

效果演示

https证书

CAS Service 需要用https的方式,那么就需要证书,可以买也可以自己生成一个。 其实这一步也可以省略,访问的时候使用http即可,只是cas 会给警告。

步骤和把大象装进冰箱一样简单,总共三步:

  1. 生成密钥
  2. 生成证书
  3. 导入证书
1. 生成密钥

keytool -genkey -alias cainiao -keyalg RSA -keystore E:sslcainiao.keystore

参数说明:

  • -genkey 生成密钥
  • -keyalg 指定密钥算法,这时指定RSA
  • -alias 指定别名
  • -keystore 指定密钥库存储位置,这里存在 E:/ssl/目录下

在执行中会问你很多问题,当问到 :您的名字与姓氏是什么? 此时需要填写域名,作为之后的访问地址,其他随意。 执行完后生成一个密钥文件 cainiao.keystore

2. 生成证书

keytool -export -alias cainiao -storepass 123456 -file E:/ssl/cainiao.cer -keystore E:/ssl/cainiao.keystore

参数说明:

  • -storepass 刚刚生成密钥文件时候的设置的密码
  • -file指定导出证书的文件名为cainiao.cer
  • -keystore指定之前生成的密钥文件的文件名

执行完后目录下会生成一个cainiao.cer证书

3. 导入证书

keytool -import -alias cainiao -keystore C:/"Program Files"/Java/jdk1.8.0_181/jre/lib/security/cacerts -file E:/ssl/cainiao.cer -trustcacerts

将证书导入到JDK信任库 把原来的$JAVA_HOME/jre/lib/security/cacerts文件要先删掉,否则会报出 Keystore was tampered with, or password was incorrect.

下面是整个过程:

代码语言:javascript
复制
PS E:\ssl> keytool -genkey -alias cainiao -keyalg RSA -keystore E:\ssl\cainiao.keystore
输入密钥库口令:
再次输入新口令:
您的名字与姓氏是什么?
  [Unknown]:  www.cainiao.com
您的组织单位名称是什么?
  [Unknown]:  cainian
您的组织名称是什么?
  [Unknown]:  cainiao
您所在的城市或区域名称是什么?
  [Unknown]:  wx
您所在的省/市/自治区名称是什么?
  [Unknown]:  js
该单位的双字母国家/地区代码是什么?
  [Unknown]:  CN
CN=www.cainiao.com, OU=cainian, O=cainiao, L=wx, ST=js, C=CN是否正确?
  [否]:  y

输入 <cainiao> 的密钥口令
        (如果和密钥库口令相同, 按回车):
再次输入新口令:

------------------------------------------------------------------------------------

PS E:\ssl> keytool -export -alias cainiao -storepass 123456 -file E:/ssl/cainiao.cer -keystore E:/ssl/cainiao.keystore
存储在文件 <E:/ssl/cainiao.cer> 中的证书

------------------------------------------------------------------------------------

PS E:\ssl> keytool -import -alias cainiao -keystore C:/"Program Files"/Java/jdk1.8.0_181/jre/lib/security/cacerts -file E:/ssl/cainiao.cer -trustcacerts
输入密钥库口令:
所有者: CN=www.cainiao.com, OU=cainian, O=cainiao, L=wx, ST=js, C=CN
发布者: CN=www.cainiao.com, OU=cainian, O=cainiao, L=wx, ST=js, C=CN
序列号: 509d1aea
有效期为 Wed Jun 17 22:02:55 CST 2020 至 Tue Sep 15 22:02:55 CST 2020
证书指纹:
         MD5:  5B:B2:7C:D7:B7:31:C5:7C:1C:BC:F7:DA:A8:2D:1C:B2
         SHA1: F6:76:55:55:D7:48:E3:9F:3A:B6:EE:68:1F:BE:DC:DE:51:B1:33:E5
         SHA256: 24:53:18:CD:E8:95:65:D8:6E:6A:7B:8E:79:CB:91:BD:F4:2E:C3:99:59:D1:76:12:A8:95:45:2A:4B:03:E4:AD
签名算法名称: SHA256withRSA
主体公共密钥算法: 2048 位 RSA 密钥
版本: 3

扩展:

#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 70 B3 D5 76 36 EA 54 BA   75 C1 A1 5C DA 76 82 0E  p..v6.T.u..\.v..
0010: 4D F4 C9 05                                        M...
]
]

是否信任此证书? [否]:  y
证书已添加到密钥库中

最后,hosts 配置 127.0.0.1 www.cainiao.com

搭建CAS service

需要从github上拉取模板 https://github.com/apereo/cas... 5.3之后的都是gradle项目,5.3以之前都是maven 项目,我下载5.3版本的。

1.> 把pom 里面的<repositories>仓库地址去掉,国外的仓库地址比较慢。你懂得。 2.> 在根目录下建/src/main/resources目录 3.> 将生成的密钥文件复制到/src/main/resources目录下 4.> 将overlays/org.apereo.cas.cas-server-webapp-tomcat-5.3.14/WEB-INF/classes/application.properties文件复制到第二步建的目录下。 5.> 修改复制过来的/src/main/resources/application.properties文件,根据上面的证书信息如实填写。

代码语言:javascript
复制
server.ssl.key-store=classpath:cainiao.keystore
server.ssl.key-store-password=123456
server.ssl.key-password=123456

6.> 连接mysql数据库,在pom 中添加依赖

代码语言:javascript
复制
<dependency>
     <groupId>org.apereo.cas</groupId>
     <artifactId>cas-server-support-jdbc</artifactId>
     <version>${cas.version}</version>
</dependency>
<dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <version>5.1.21</version>
</dependency>

或许你会发现有个xmlsectool-2.0.0.jar包下不下来,需要到maven中央仓库下载,后安装到本地仓库,可不是直接放到本地仓库,jar包都是必须使用命令安装到本地仓库。

mvn install:install-file -Dfile="E:下载xmlsectool-2.0.0.jar" "-DgroupId=net.shibboleth.tool" "-DartifactId=xmlsectool" "-Dversion=2.0.0" "-Dpackaging=jar"

安装jar包到本地仓库笔记

7.> 在复制过来的/src/main/resources/application.properties文件中在添加如下信息

代码语言:javascript
复制
#查询账号密码sql,必须包含密码字段
cas.authn.jdbc.query[0].sql=select * from sys_user where username=?
#指定上面的sql查询字段名(必须)
cas.authn.jdbc.query[0].fieldPassword=password
#指定过期字段,1为过期,若过期需要修改密码
cas.authn.jdbc.query[0].fieldExpired=expired
#为不可用字段段,1为不可用,
cas.authn.jdbc.query[0].fieldDisabled=disabled
#数据库方言hibernate的知识
cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQLDialect
#数据库驱动
cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver
#数据库连接 
cas.authn.jdbc.query[0].url=jdbc:mysql://localhost:3306/cas?useUnicode=true&characterEncoding=UTF-8
#数据库用户名
cas.authn.jdbc.query[0].user=root
#数据库密码
cas.authn.jdbc.query[0].password=123456
#默认加密策略,通过encodingAlgorithm来指定算法,默认NONE不加密
cas.authn.jdbc.query[0].passwordEncoder.type=DEFAULT
cas.authn.jdbc.query[0].passwordEncoder.characterEncoding=UTF-8
cas.authn.jdbc.query[0].passwordEncoder.encodingAlgorithm=MD5

附上数据库sql,用户信息表

代码语言:javascript
复制
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `expired` int(11) DEFAULT NULL,
  `disabled` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES ('1', 'admin', '21232f297a57a5a743894a0e4a801fc3', '0', '1');
INSERT INTO `sys_user` VALUES ('2', 'cainiao', '6b757206058785025cd90c8d865c8e43', '1', '0');
INSERT INTO `sys_user` VALUES ('3', 'mashu', 'd1f21ceb3f710ebbd9f408274aee1193', '0', '0');

用户名和密码一样,密码在数据库中是MD5加密的。 这样就完成了CAS service 的搭建,在根目录使用 build.cmd run 命令启动 出现 READY 的branner就启动好了 访问地址 https://www.cainiao.com:8443/cas/login

mashu正常登录,cainiao需要修改密码,admin被禁用,符合预期。

搭建CAS client

创建一个spring boot 项目. 1.加入 cas 客户端 的依赖, 我选择目前最新的 2.3.0-GA 版本

代码语言:javascript
复制
<dependency>
    <groupId>net.unicon.cas</groupId>
    <artifactId>cas-client-autoconfig-support</artifactId>
    <version>2.3.0-GA</version>      
</dependency>

2.在启动类上加上注解 @EnableCasClient 3.在application.properties中添加配置

代码语言:javascript
复制
#cas服务端的地址
cas.server-url-prefix=https://www.cainiao.com:8443/cas
#cas服务端的登录地址
cas.server-login-url=https://www.cainiao.com:8443/cas/login
#客户端访问地址
cas.client-host-url=http://www.mashu.com:8080
cas.validation-type=CAS3

4.添加hosts 配置,把客户端的访问地址配置到hosts

代码语言:javascript
复制
127.0.0.1 www.mashu.com

这样就客户端就配置好了。

单点登录

我写一个controller,访问一下。

代码语言:javascript
复制
@RestController
public class TestController {

    @RequestMapping("/hello")
    public String hello() {
        return "word";
    }
}

访问 http://www.mashu.com:8080/hello

未认证授权的服务

喜提报错:

原因是服务端不允许客户端的http协议的请求。需要对服务端做以下修改,让他妥协。 1.>修改overlays/org.apereo.cas.cas-server-webapp-tomcat-5.3.14/WEB-INF/classes/services/HTTPSandIMAPS-10000001.json文件

代码语言:javascript
复制
"serviceId" 由原来的"^(https|imaps)://.*"改成 "^(https|imaps|http)://.*"

2.>在application.properties文件中添加:

代码语言:javascript
复制
#允许http
cas.tgc.secure=false
cas.serviceRegistry.initFromJson=true

再次访问 http://www.mashu.com:8080/hello,可以看到已经带着地址转发到服务端的登录页。

输入账号密码mashu/mashu,登录成功后又回来了!哈哈哈哈嗝,并携带了登录凭证。

多系统登录

再启动一个客户端,打开idea 的edit configurations设置。勾选 Allow parallel run

修改application.properties,服务的端口等信息,再点击启动,就可以同时启动了(8081/8080)两个客户端

代码语言:javascript
复制
server.port=8081
#cas服务端的地址
cas.server-url-prefix=https://www.cainiao.com:8443/cas
#cas服务端的登录地址
cas.server-login-url=https://www.cainiao.com:8443/cas/login
#客户端访问地址
cas.client-host-url=http://www.mshu.com:8081
cas.validation-type=CAS3

访问第二个客户端 http://www.mshu.com:8081/hello,(需要先配置host),就直接登录了,到此完成了单点登录。

点单登出

添加两个配置文件;

1. CasProperties.java

代码语言:javascript
复制
import org.springframework.boot.context.properties.ConfigurationProperties;
import javax.validation.constraints.NotNull;

@ConfigurationProperties(prefix = "cas",ignoreUnknownFields = true)
public class CasProperties {

    /**
     * CAS server URL E.g. https://example.com/cas or https://cas.example. Required.
     * CAS 服务端 url 不能为空
     */
    @NotNull
    private String serverUrlPrefix;

    /**
     * CAS server login URL E.g. https://example.com/cas/login or https://cas.example/login. Required.
     * CAS 服务端登录地址  上面的连接 加上/login 该参数不能为空
     */
    @NotNull
    private String serverLoginUrl;

    /**
     * CAS-protected client application host URL E.g. https://myclient.example.com Required.
     * 当前客户端的地址
     */
    @NotNull
    private String clientHostUrl;

    /**
     * 忽略规则,访问那些地址 不需要登录
     */
    private String ignorePattern;

    /**
     * 自定义UrlPatternMatcherStrategy验证
     */
    private String ignoreUrlPatternType;

    public String getServerUrlPrefix() {
        return serverUrlPrefix;
    }

    public void setServerUrlPrefix(String serverUrlPrefix) {
        this.serverUrlPrefix = serverUrlPrefix;
    }

    public String getServerLoginUrl() {
        return serverLoginUrl;
    }

    public void setServerLoginUrl(String serverLoginUrl) {
        this.serverLoginUrl = serverLoginUrl;
    }

    public String getClientHostUrl() {
        return clientHostUrl;
    }

    public void setClientHostUrl(String clientHostUrl) {
        this.clientHostUrl = clientHostUrl;
    }

    public String getIgnorePattern() {
        return ignorePattern;
    }

    public void setIgnorePattern(String ignorePattern) {
        this.ignorePattern = ignorePattern;
    }

    public String getIgnoreUrlPatternType() {
        return ignoreUrlPatternType;
    }

    public void setIgnoreUrlPatternType(String ignoreUrlPatternType) {
        this.ignoreUrlPatternType = ignoreUrlPatternType;
    }
}

2. Configs.java

代码语言:javascript
复制
import org.jasig.cas.client.authentication.AuthenticationFilter;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.jasig.cas.client.util.HttpServletRequestWrapperFilter;
import org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.EventListener;
import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableConfigurationProperties(CasProperties.class)
public class Configs {

    @Autowired
    private CasProperties configProps;

    /**
     * 配置登出过滤器
     * @return
     */
    @Bean
    public FilterRegistrationBean filterSingleRegistration() {
        final FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new SingleSignOutFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");
        Map<String,String>  initParameters = new HashMap<String, String>();
        initParameters.put("casServerUrlPrefix", configProps.getServerUrlPrefix());
        registration.setInitParameters(initParameters);
        // 设定加载的顺序
        registration.setOrder(1);
        return registration;
    }

    /**
     * 配置过滤验证器 这里用的是Cas30ProxyReceivingTicketValidationFilter
     * @return
     */
    @Bean
    public FilterRegistrationBean filterValidationRegistration() {
        final FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new Cas30ProxyReceivingTicketValidationFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");
        Map<String,String> initParameters = new HashMap<String, String>();
        initParameters.put("casServerUrlPrefix", configProps.getServerUrlPrefix());
        initParameters.put("serverName", configProps.getClientHostUrl());
        initParameters.put("useSession", "true");
        registration.setInitParameters(initParameters);
        // 设定加载的顺序
        registration.setOrder(2);
        return registration;
    }

    /**
     * 配置授权过滤器
     * @return
     */
    @Bean
    public FilterRegistrationBean filterAuthenticationRegistration() {
        final FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new AuthenticationFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");
        Map<String,String>  initParameters = new HashMap<String, String>();
        initParameters.put("casServerLoginUrl", configProps.getServerLoginUrl());
        initParameters.put("serverName", configProps.getClientHostUrl());

        if(configProps.getIgnorePattern() != null && !"".equals(configProps.getIgnorePattern())){
            initParameters.put("ignorePattern", configProps.getIgnorePattern());
        }

        //自定义UrlPatternMatcherStrategy 验证规则
        if(configProps.getIgnoreUrlPatternType() != null && !"".equals(configProps.getIgnoreUrlPatternType())){
            initParameters.put("ignoreUrlPatternType", configProps.getIgnoreUrlPatternType());
        }

        registration.setInitParameters(initParameters);
        // 设定加载的顺序
        registration.setOrder(3);
        return registration;
    }

    /**
     * request wraper过滤器
     * @return
     */
    @Bean
    public FilterRegistrationBean filterWrapperRegistration() {
        final FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new HttpServletRequestWrapperFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");
        // 设定加载的顺序
        registration.setOrder(4);
        return registration;
    }

    /**
     * 添加监听器
     * @return
     */
    @Bean
    public ServletListenerRegistrationBean<EventListener> singleSignOutListenerRegistration(){
        ServletListenerRegistrationBean<EventListener> registrationBean = new ServletListenerRegistrationBean<EventListener>();
        registrationBean.setListener(new SingleSignOutHttpSessionListener());
        registrationBean.setOrder(1);
        return registrationBean;
    }
}

登出地址: https://www.cainiao.com:8443/cas/logout,退出服务端。 再次访问客户端发现自动跳到了登录页面,即客户端也自动退出成功。

一些问题

最开始我想把客户端也加一个证书,用https访问。免得在服务端做修改去支持http, 当我添加证书后,单点登录正常,但是登出功能总是失败,表现为服务端退出,客户端没有退出。 我一直以为客户端配置的登出有问题,搞了半天都没成功,后来我把客户端的证书去掉,就成功了。想了想大概是因为我们自己生成的证书不能被服务端认可,因为登出的时候需要服务端向客户端发起广播,而我们之前修改的HTTPSandIMAPS-10000001.json文件只是作用于客户端向服务端的请求。和登出相反。

在我使用springboot配置证书的时候,2.1.0.RELEASE以上版本的spring-boot-starter-parent都不行。会报错。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 效果演示
  • https证书
  • 搭建CAS service
  • 搭建CAS client
  • 单点登录
    • 未认证授权的服务
      • 多系统登录
      • 点单登出
        • 1. CasProperties.java
          • 2. Configs.java
          • 一些问题
          相关产品与服务
          访问管理
          访问管理(Cloud Access Management,CAM)可以帮助您安全、便捷地管理对腾讯云服务和资源的访问。您可以使用CAM创建子用户、用户组和角色,并通过策略控制其访问范围。CAM支持用户和角色SSO能力,您可以根据具体管理场景针对性设置企业内用户和腾讯云的互通能力。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档