天天看點

Springboot 內建 Shiro 和 CAS 實作單點登入(服務端篇CAS5)

什麼是單點登入?

先說一個需求場景,比如:一個企業的内部有N多個子系統,每個子系統都有一套自己的使用者名和密碼,那麼企業的員工要登入N個子系統,這樣一個員工 就要記住N個使用者名和密碼,就算各個子系統的使用者名和密碼都是統一的,登入每個子系統都要輸入使用者名和密碼進行登入也是一個繁瑣的操作過程,那麼單點登入功能由此便應運而生了。

單點登入(Single Sign On),簡稱為 SSO,是目前比較流行的企業業務整合的解決方案之一。SSO的定義是在多個應用系統中,使用者隻需要登入一次就可以通路所有互相信任的應用系統。

CAS架構圖

這裡直接說CAS整體架構是因為這不是一個入門教程,我已經預設你知道什麼是SSO、什麼是CAS,以及你已經裝好了開發工具IDEA和Java環境Jdk1.8,并且你也知道什麼是Web容器,知道Tomcat8.5+該從哪下載下傳并啟動部署,你也知道什麼是Maven項目該怎麼引入相關的jar包等等,是以如果你不具備以上這些知識,那麼CAS服務端的學習我建議你還是先不要開始,弄清楚前面的知識點,才能暢通無阻的學習CAS服務端。

好了,這裡先放架構圖:

Springboot 內建 Shiro 和 CAS 實作單點登入(服務端篇CAS5)

現在來講解這個架構圖:

首先搭建好CAS服務端後,服務端會自定義一個資料庫和使用者表,使用者表中存放的是使用者名和密碼,通過通路子系統的URL位址,如果CAS系統判定你沒有登入,就會将URL重定向到CAS的服務端登入界面,使用者通過輸入使用者資料庫的使用者名和密碼來進行登入,待登入成功後,CAS服務端會給CAS用戶端(子系統)發送登入的使用者名,CAS用戶端接到使用者名後,會從CAS用戶端的使用者表中尋找對應使用者名的userid,并通過該userid擷取到該名使用者在CAS用戶端的相關權限。

我們還可以基于CAS服務端配置的使用者資料庫做一個使用者管理界面,該界面可以對CAS服務端的使用者進行一些基本的業務操作,比如新增、修改、删除等,新增使用者的時候,需要同時在各個子系統的使用者表中插入該使用者的相關資訊,然後各個子系統的管理者就可以通過子系統的角色權限設定,給該使用者配置相關的角色權限了。

安裝CAS服務端

講了這麼多,現在開始進入正題,我們要實作我們的目的首先要做的就是安裝CAS服務端,現在網上的很多Springboot整合CAS的教程基本上都是隻講用戶端的配置,看完之後雲裡霧裡的,是以我這個教程要從CAS的服務端開始講起,如果你們公司有人專門搭建CAS服務端,那就可以跳過本篇教程繼續下一篇了。

  1. 下載下傳Overlay

    通過閱讀官網文檔(https://apereo.github.io/cas/5.1.x/planning/Getting-Started.html)了解到官方建議我們:

    通過使用一個名叫Overlay的項目來生成一個可以直接用的war包,來部署服務端,于是我們先下載下傳這個項

    目,我這裡使用Maven的,下載下傳位址:https://github.com/apereo/cas-overlay-template。

  2. 搭建Overlay項目

    将項目下好後解壓縮并放入我們的工作空間,然後打開IDEA,然後選擇File–Open打開我們項目所在路徑,然後我們隻要靜靜的等待Maven将項目構築好就可以了。這時你看到的項目是這個樣子的:

Springboot 內建 Shiro 和 CAS 實作單點登入(服務端篇CAS5)

這是我的項目,你的項目中并沒有src目錄和target目錄,src目錄是使用者自己建立的,至于為什麼後邊會講,target目錄是導出war時才出現的,新導入的項目是沒有的。

  1. 制成可用的CAS服務端

    現在的CAS服務端基本上是不可用的,為什麼說基本上呢?因為如果你現在講項目打成war包,并将它部署到Tomcat中去後是可以啟動服務的,并且通路本地位址:http://localhost:8080/cas/login還可以看到如下界面:

Springboot 內建 Shiro 和 CAS 實作單點登入(服務端篇CAS5)

使用 預設賬号:casuser 預設密碼:Mellon還可以登入成功,不過這個CAS服務端目前并沒有什麼卵用,隻是可以看看而已。

這兩個紅色警告,一個是說沒有使用Https,另一個是說你隻有一個靜态的權限,一個寫死的使用者,其實就是告訴你現在的服務端是不安全的。

  • 第一個問題:關于https請求

    服務端是http的其實也可以使用,我在這個教程中不會講解如何搭建https請求的服務端,因為我們的子項目都是http請求的,是以服務端我也不打算用https請求了,如果你想學習https請求,請參看别人的參考教程,這裡不做講解。

  • 第二個問題:關于靜态使用者
  1. 我們先打開項目的pom.xml檔案,引入相關的jar包,注釋掉用不到的jar包,我的資料庫是mysql的,如果你的資料庫不是,請找到對應的jar包進行引入即可:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd ">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.apereo.cas</groupId>
    <artifactId>cas-overlay</artifactId>
    <packaging>war</packaging>
    <version>1.0</version>

    <build>
        <plugins>
            <!--第一步:注釋無用元件
             <plugin>
                <groupId>com.rimerosolutions.maven.plugins</groupId>
                <artifactId>wrapper-maven-plugin</artifactId>
                <version>0.0.4</version>
                <configuration>
                    <verifyDownload>true</verifyDownload>
                    <checksumAlgorithm>MD5</checksumAlgorithm>
                </configuration>
            </plugin>-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${springboot.version}</version>
                <configuration>
                    <mainClass>org.springframework.boot.loader.WarLauncher</mainClass>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <warName>cas</warName>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                    <recompressZippedFiles>false</recompressZippedFiles>
                    <archive>
                        <compress>false</compress>
                        <manifestFile>${project.build.directory}/war/work/org.apereo.cas/cas-server-webapp${app.server}/META-INF/MANIFEST.MF
                        </manifestFile>
                    </archive>
                    <overlays>
                        <overlay>
                            <groupId>org.apereo.cas</groupId>
                            <artifactId>cas-server-webapp${app.server}</artifactId>
                        </overlay>
                    </overlays>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
            </plugin>
        </plugins>
        <finalName>cas</finalName>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.apereo.cas</groupId>
            <artifactId>cas-server-webapp${app.server}</artifactId>
            <version>${cas.version}</version>
            <type>war</type>
            <scope>runtime</scope>
        </dependency>

        <!--第二步:引入資料庫認證相關 start-->
        <dependency>
            <groupId>org.apereo.cas</groupId>
            <artifactId>cas-server-support-jdbc</artifactId>
            <version>${cas.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apereo.cas</groupId>
            <artifactId>cas-server-support-jdbc-drivers</artifactId>
            <version>${cas.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.36</version>
        </dependency>
    </dependencies>

    <properties>
        <cas.version>5.1.4</cas.version>
        <springboot.version>1.5.3.RELEASE</springboot.version>
        <!-- app.server could be -jetty, -undertow, -tomcat, or blank if you plan to provide appserver -->
        <app.server>-tomcat</app.server>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <repositories>
        <repository>
            <id>sonatype-releases</id>
            <url>http://oss.sonatype.org/content/repositories/releases/</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
        <repository>
            <id>sonatype-snapshots</id>
            <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
        <repository>
            <id>shibboleth-releases</id>
            <url>https://build.shibboleth.net/nexus/content/repositories/releases</url>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>

    <!--第三步:注釋掉無用的元件
    <profiles>
        <profile>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>
            <id>pgp</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>com.github.s4u.plugins</groupId>
                        <artifactId>pgpverify-maven-plugin</artifactId>
                        <version>1.1.0</version>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>check</goal>
                                </goals>
                            </execution>
                        </executions>
                        <configuration>
                            <pgpKeyServer>hkp://pool.sks-keyservers.net</pgpKeyServer>
                            <pgpKeysCachePath>${settings.localRepository}/pgpkeys-cache</pgpKeysCachePath>
                            <scope>test</scope>
                            <verifyPomFiles>true</verifyPomFiles>
                            <failNoSignature>false</failNoSignature>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>-->
</project>
           

複制

  1. 修改配置檔案

    我們在cas-overly這個項目的根目錄下建立一個src目錄,并在src目錄下建立一個resource目錄,在resource目錄下再建立一個application.properties檔案,該檔案中要寫的就是我們的配置檔案内容了:

#STEP 3 在TOMCAT8.5中跑一個模闆然後将其war包中解壓出來的的application.properties複制出來,放到手動建立的src下的resources裡面

# CAS Server Context Configuration
server.context-path=/cas
server.port=9092

#STEP 5添加認證服務
cas.serviceRegistry.initFromJson=true

#STEP 4簽發證書,如果是用spring boot之類嵌入式的容器,則需要改這裡的配置,如果是直接部在tomcat中,則需要把tomcat改成https的
#server.ssl.key-store=file:/etc/cas/thekeystore
#server.ssl.key-store-password=changeit
#server.ssl.key-password=changeit
# server.ssl.ciphers=
# server.ssl.client-auth=
# server.ssl.enabled=
# server.ssl.key-alias=
# server.ssl.key-store-provider=
# server.ssl.key-store-type=
# server.ssl.protocol=
# server.ssl.trust-store=
# server.ssl.trust-store-password=
# server.ssl.trust-store-provider=
# server.ssl.trust-store-type=

#server.max-http-header-size=2097152
#server.use-forward-headers=true
#server.connection-timeout=20000
#server.error.include-stacktrace=NEVER

#server.tomcat.max-http-post-size=2097152
#server.tomcat.basedir=build/tomcat
#server.tomcat.accesslog.enabled=true
#server.tomcat.accesslog.pattern=%t %a "%r" %s (%D ms)
#server.tomcat.accesslog.suffix=.log
#server.tomcat.max-threads=10
#server.tomcat.port-header=X-Forwarded-Port
#server.tomcat.protocol-header=X-Forwarded-Proto
#server.tomcat.protocol-header-https-value=https
#server.tomcat.remote-ip-header=X-FORWARDED-FOR
#server.tomcat.uri-encoding=UTF-8

spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true

# CAS Cloud Bus Configuration
spring.cloud.bus.enabled=false
# spring.cloud.bus.refresh.enabled=true
# spring.cloud.bus.env.enabled=true
# spring.cloud.bus.destination=CasCloudBus
# spring.cloud.bus.ack.enabled=true

endpoints.enabled=false
endpoints.sensitive=true

endpoints.restart.enabled=false
endpoints.shutdown.enabled=false

management.security.enabled=true
management.security.roles=ACTUATOR,ADMIN
management.security.sessions=if_required
management.context-path=/status
management.add-application-context-header=false

security.basic.authorize-mode=role
security.basic.enabled=false
security.basic.path=/cas/status/**

# CAS Web Application Session Configuration
server.session.timeout=300
server.session.cookie.http-only=true
server.session.tracking-modes=COOKIE

# CAS Thymeleaf View Configuration
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.cache=false
spring.thymeleaf.mode=HTML
##
# CAS Log4j Configuration
# logging.config=file:/etc/cas/log4j2.xml
server.context-parameters.isLog4jAutoInitializationDisabled=true

# CAS AspectJ Configuration
spring.aop.auto=true
spring.aop.proxy-target-class=true

# CAS Authentication Credentials
#STEP4 注釋掉寫死的使用者 改用jdbc的使用者 START
#cas.authn.accept.users=casuser::Mellon

cas.authn.jdbc.query[0].sql=select * from s_user where username=?
cas.authn.jdbc.query[0].healthQuery=
cas.authn.jdbc.query[0].isolateInternalQueries=false
cas.authn.jdbc.query[0].url=jdbc:mysql://172.18.18.25:3306/pa_db?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
cas.authn.jdbc.query[0].failFast=true
cas.authn.jdbc.query[0].isolationLevelName=ISOLATION_READ_COMMITTED
cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQLDialect
cas.authn.jdbc.query[0].leakThreshold=10
cas.authn.jdbc.query[0].propagationBehaviorName=PROPAGATION_REQUIRED
cas.authn.jdbc.query[0].batchSize=1
cas.authn.jdbc.query[0].user=root
#cas.authn.jdbc.query[0].ddlAuto=create-drop
cas.authn.jdbc.query[0].maxAgeDays=180
cas.authn.jdbc.query[0].password=dhcc
cas.authn.jdbc.query[0].autocommit=false
cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver
cas.authn.jdbc.query[0].idleTimeout=5000
# cas.authn.jdbc.query[0].credentialCriteria=
# cas.authn.jdbc.query[0].name=
# cas.authn.jdbc.query[0].order=0
# cas.authn.jdbc.query[0].dataSourceName=
# cas.authn.jdbc.query[0].dataSourceProxy=false
cas.authn.jdbc.query[0].fieldPassword=password

# cas.authn.jdbc.query[0].fieldExpired=
# cas.authn.jdbc.query[0].fieldDisabled=
# cas.authn.jdbc.query[0].principalAttributeList=sn,cn:commonName,givenName

#cas.authn.jdbc.query[0].passwordEncoder.type=DEFAULT
#cas.authn.jdbc.query[0].passwordEncoder.type=com.example.CustomPasswordEncoder
#cas.authn.jdbc.query[0].passwordEncoder.characterEncoding=UTF-8
#cas.authn.jdbc.query[0].passwordEncoder.encodingAlgorithm=MD5

#cas.authn.jdbc.query[0].passwordEncoder.secret=
#cas.authn.jdbc.query[0].passwordEncoder.strength=16

# cas.authn.jdbc.query[0].principalTransformation.suffix=
# cas.authn.jdbc.query[0].principalTransformation.caseConversion=NONE|UPPERCASE|LOWERCASE
# cas.authn.jdbc.query[0].principalTransformation.prefix=
# STEP4 END


# CAS Delegated Authentication
#cas.authn.pac4j.bitbucket.clientName=Bitbucket
#cas.authn.pac4j.dropbox.clientName=Dropbox
#cas.authn.pac4j.facebook.clientName=Facebook
#cas.authn.pac4j.foursquare.clientName=Foursquare
#cas.authn.pac4j.github.clientName=Github
#cas.authn.pac4j.google.clientName=Google
#cas.authn.pac4j.linkedIn.clientName=LinkedIn
#cas.authn.pac4j.paypal.clientName=PayPal
#cas.authn.pac4j.twitter.clientName=Twitter
#cas.authn.pac4j.yahoo.clientName=Yahoo
#cas.authn.pac4j.windowsLive.clientName=Windows Live
#cas.authn.pac4j.wordpress.clientName=WordPress

#多屬性
cas.authn.attributeRepository.jdbc[0].singleRow=true
cas.authn.attributeRepository.jdbc[0].order=0
cas.authn.attributeRepository.jdbc[0].url=jdbc:mysql://172.18.18.25:3306/pa_db?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
cas.authn.attributeRepository.jdbc[0].username=username
cas.authn.attributeRepository.jdbc[0].user=root
cas.authn.attributeRepository.jdbc[0].password=dhcc
cas.authn.attributeRepository.jdbc[0].sql=select * from s_user where {0}
cas.authn.attributeRepository.jdbc[0].dialect=org.hibernate.dialect.MySQLDialect
cas.authn.attributeRepository.jdbc[0].ddlAuto=none
cas.authn.attributeRepository.jdbc[0].driverClass=com.mysql.jdbc.Driver
cas.authn.attributeRepository.jdbc[0].leakThreshold=10
cas.authn.attributeRepository.jdbc[0].propagationBehaviorName=PROPAGATION_REQUIRED
cas.authn.attributeRepository.jdbc[0].batchSize=1
cas.authn.attributeRepository.jdbc[0].healthQuery=SELECT 1
cas.authn.attributeRepository.jdbc[0].failFast=trueyeshi
#http通路
cas.tgc.secure=false
           

複制

看上面的配置檔案你會發現我配置了mysql的jdbc連接配接請求,其實就是為了給CAS服務端配置使用者名和密碼驗證的,你自己做的話,需要在你的資料庫中建立一個資料庫空間和一張使用者表,并且保證該表中最少有三個字段:id,username和password,這個配置檔案中寫的是我的測試伺服器的配置,你要改成自己的哦。

這裡有幾個點要注意下:

cas.authn.accept.users=casuser::Mellon這個配置記得删掉,這就是那個寫死的使用者

cas.authn.jdbc.query[0]這些配置就是資料庫驗證相關的内容

在cas.authn.jdbc.query[0].sql中,程式會把你登入時輸入的使用者名作為參數傳進去

cas.authn.jdbc.query[0].fieldPassword則是指明那一列對應的是你輸入的密碼,目前沒有做MD5

如果使用http通路 必須加入 cas.tgc.secure=false 否則登入狀态不能共享。

  1. 加入http通路服務

請在系統的C:\WINDOWS\System32\drivers\etc\hosts 檔案中加入:

127.0.0.1 com.dhcc.cas

這麼做的目的是将這個IP位址指向這個域名,其實本意是為了https通路做的工作,如果想知道其目的可以檢視cas https通路相關的資料,本教程裡就不再細說了。

這裡可能不好了解,我先來講一個我遇到的問題,你就可以了解了:

配置好CAS的用戶端後,我就開始測試我的用戶端子系統,在浏覽器中輸入我的子系統項目位址:

http://com.dhcc.cas:9093

後由于我的子系統的配置,通路界面會重定向到

http://com.dhcc.cas:9092/cas/login?service:http://com.dhcc.cas:9093/

但是這個界面理論上來說應該是跳轉到CAS服務端的登入界面的,可是我的這個路徑卻無法跳轉,

并展示如下界面:

Springboot 內建 Shiro 和 CAS 實作單點登入(服務端篇CAS5)

我百思不得其解,嘗試了各種方法也不行,直到我發現了将路徑改為:

http://com.dhcc.cas:9092/cas/login?service=https://com.dhcc.cas:9093/

界面會自動跳轉到CAS服務端的登入界面,這時我才開始回憶起CAS服務端是不是哪裡我沒有配置對,于是我在網上查資料才知道CAS4.2以上光加入cas.tgc.secure=false 這個參數配置是不夠的,還要對HTTPSandIMAPS-10000001.json這個檔案中進行修改,大家可能會問了,我怎麼沒看到這個檔案?

因為預設的項目架構是沒有這個檔案的,我們要将導出的war包解壓,然後在WEB-INF\classes\services這個路徑下找到這個檔案,然後在我們的項目中的resources目錄下再建立一個services目錄,并将該檔案拷貝到這個路徑下即可。

現在你的項目架構應該是這個樣子的:

Springboot 內建 Shiro 和 CAS 實作單點登入(服務端篇CAS5)

引入這個檔案肯定是為了修改它,我們可以看到這個json中有行代碼,其實是個正規表達式:

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

複制

了解json的都知道,這裡其實是說serviceId的值是所有帶https和imaps的url請求都會被當成已經注冊的服務,我們隻要将它改成下面這樣就可以了:

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

複制

這樣我們就可以安心的使用CAS的http請求服務了。

結語

至此,我們的CAS服務端基本上就搭建好了,隻要将它打成war包,并放到tomcat中啟動起來,便可以作為們的單點登入的服務端使用了。

這裡需要注意一下幾點:

  1. 注意自定義的配置檔案是否寫對了,尤其是資料庫的配置。
  2. 一定要加入并修改HTTPSandIMAPS-10000001.json檔案,否則後期你的CAS用戶端肯定不會自動跳轉到登入界面。

隻要你的服務端可以正常啟用,你就成功了一半了,下一篇我們就開始講解CAS用戶端的配置了。

source: //jasoncool.github.io/2017/11/29/Springboot內建Shiro和Cas實作單點登入-服務端篇CAS5
           

複制