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

CAS+Springboot单点登录

作者头像
丁D
发布2022-08-12 15:01:15
1.3K0
发布2022-08-12 15:01:15
举报
文章被收录于专栏:老铁丁D

本文源码demo https://github.com/348786639/cas

什么是单点登录

单点登录就是登录一次处处已登录。 单点登录就是假设我有两个系统 比如 淘宝和天猫,我登录了淘宝,当我访问天猫的时候不用在登录一次。

CAS原理

文字描述 假设我们用3个系统 系统A,系统B,和认证中心 1、访问系统A,第一次访问没登陆,系统A重定向用户认证中心(带上service) 2、用户访问认证中心没有带上TGC(还没登陆),认证中心返回登陆页面 3、用户输入账号密码,进行登陆 4、认证中心进行登陆逻辑校验,成功就向客户端写cookie(TGC),并生成TGT缓存在服务器本地, 用TGT签发ST 5、用户认证中心重定向到第一次访问的带上的service(带上ST) 6、系统A的拦截器收到请求后,拿出ST,向认证中心询问ST是否有效(这个步骤对用户透明,直接使用http访问) 7、认证中心回复有效,并返回用户名字,和一些其他属性 8、系统A收到回复,建立本地Session

当用户第二次访问系统A的时候,由于第一次已经建立了本地session,所以成功登陆

1、当用户访问系统B的时候,没有本地session,系统B将请求重定向到用户认证中心(带上service) 2、这个时候用于我们第一次访问系统A的时候,用户认证中心,已经写下TGC,所以访问的是会带上TGC,认证中心根据TGC找到TGT,说明已经登陆过了 3、认证中心重定向到service地址带上ST 4、系统B收到ST向认证中心询问,ST是否有效,(这个步骤对用户透明,直接使用http访问) 5、认证中心回复有效,并返回用户名字,和一些其他属性 6、系统B收到回复,建立本地Session 参考 https://blog.csdn.net/ban_tang/article/details/80015946 https://www.cnblogs.com/notDog/p/5252973.html

CAS Server搭建

1、从github下载解压(我选择5.2分支) https://github.com/apereo/cas-overlay-template

2、导入idea

目录结构如上图,我们自己建src/java/main、resource目录,并修改project structure

3、修改pom文件 由于我们使用overlay方式进行开发的,war引用的依赖我们在开发中是依赖不到的,所以我们如果使用到Cas的jar还是需要引用的,我们可以将生命周期设置为provided。

由于这里我要查询数据库和集成mybaits,所以也依赖了

代码语言:javascript
复制
<?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> 
<!-- 使用overlay 方式是在war的基础上进行开发的,,war里面pom问题引用的我们在开发的时候是引用不到的, --> 
<!-- 所以我们如果需要使用cas的jar包,我们还需要在这个文件中进行引用,scope 为provided就行, --> 
<!-- dependencies 这部分是我们自己的依赖,,只是将仓库删除,不然下载不了包,, --> 
<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> 
<!--json服务注册--> 
<dependency> 
<groupId>org.apereo.cas</groupId> 
<artifactId>cas-server-support-json-service-registry</artifactId> 
<version>${cas.version}</version> 
<scope>provided</scope> 
</dependency> 
<dependency> 
<groupId>org.apereo.cas</groupId> 
<artifactId>cas-server-core-authentication</artifactId> 
<version>${cas.version}</version> 
<scope>provided</scope> 
</dependency> 
<dependency> 
<groupId>org.apereo.cas</groupId> 
<artifactId>cas-server-core-authentication-api</artifactId> 
<version>${cas.version}</version> 
<scope>provided</scope> 
</dependency> 
<dependency> 
<groupId>mysql</groupId> 
<artifactId>mysql-connector-java</artifactId> 
<version>5.1.44</version> 
</dependency> 
<dependency> 
<groupId>org.mybatis</groupId> 
<artifactId>mybatis-spring</artifactId> 
<version>2.0.3</version> 
</dependency> 
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> 
<dependency> 
<groupId>org.mybatis</groupId> 
<artifactId>mybatis</artifactId> 
<version>3.4.6</version> 
</dependency> 
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter --> 
<!--引入Spring SPI机制 --> 
<dependency> 
<groupId>org.springframework.boot</groupId> 
<artifactId>spring-boot-starter</artifactId> 
<version>1.5.2.RELEASE</version> 
<exclusions> 
<exclusion> 
<groupId>org.springframework.boot</groupId> 
<artifactId>spring-boot-starter-logging</artifactId> 
</exclusion> 
</exclusions> 
</dependency> 
<dependency> 
<groupId>org.apereo.cas</groupId> 
<artifactId>cas-server-core-configuration</artifactId> 
<version>${cas.version}</version> 
<scope>provided</scope> 
</dependency> 
<dependency> 
<groupId>org.apereo.cas</groupId> 
<artifactId>cas-server-support-rest</artifactId> 
<version>${cas.version}</version> 
<scope>provided</scope> 
</dependency> 
<dependency> 
<groupId>org.apereo.cas</groupId> 
<artifactId>cas-server-core-webflow-api</artifactId> 
<version>${cas.version}</version> 
<scope>provided</scope> 
</dependency> 
<dependency> 
<groupId>org.apereo.cas</groupId> 
<artifactId>cas-server-core-webflow</artifactId> 
<version>${cas.version}</version> 
<scope>provided</scope> 
</dependency> 
<!-- https://mvnrepository.com/artifact/org.apereo.cas/cas-server-support-actions --> 
<dependency> 
<groupId>org.apereo.cas</groupId> 
<artifactId>cas-server-support-actions</artifactId> 
<version>${cas.version}</version> 
<scope>provided</scope> 
</dependency> 
<dependency> 
<groupId>org.projectlombok</groupId> 
<artifactId>lombok</artifactId> 
<version>1.12.6</version> 
<scope>provided</scope> 
</dependency> 
</dependencies> 
<!--下面是复制war原本的 只是删除仓库--> 
<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>${mainClassName}</mainClass> 
<addResources>true</addResources> 
<executable>${isExecutable}</executable> 
<layout>WAR</layout> 
</configuration> 
<executions> 
<execution> 
<goals> 
<goal>repackage</goal> 
</goals> 
</execution> 
</executions> 
</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>${manifestFileToUse}</manifestFile> 
</archive> 
<overlays> 
<overlay> 
<groupId>org.apereo.cas</groupId> 
<artifactId>cas-server-webapp${app.server}</artifactId> 
<!--原有的服务不再初始化进去--> 
<excludes> 
<!-- <exclude>WEB-INF/classes/services/*</exclude>--> 
<!-- <exclude>WEB-INF/classes/application.*</exclude>--> 
<!-- <exclude>WEB-INF/classes/log4j2.*</exclude>--> 
</excludes> 
</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> 
<properties> 
<cas.version>5.2.6</cas.version> 
<springboot.version>1.5.12.RELEASE</springboot.version> 
<!-- app.server could be -jetty, -undertow, -tomcat, or blank if you plan to provide appserver --> 
<app.server>-tomcat</app.server> 
<mainClassName>org.springframework.boot.loader.WarLauncher</mainClassName> 
<isExecutable>false</isExecutable> 
<manifestFileToUse>${project.build.directory}/war/work/org.apereo.cas/cas-server-webapp${app.server}/META-INF/MANIFEST.MF</manifestFileToUse> 
<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> 
<profiles> 
<profile> 
<activation> 
<activeByDefault>true</activeByDefault> 
</activation> 
<id>default</id> 
<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> 
<!-- 
...Additional dependencies may be placed here... 
--> 
</dependencies> 
</profile> 
<profile> 
<activation> 
<activeByDefault>false</activeByDefault> 
</activation> 
<id>exec</id> 
<properties> 
<mainClassName>org.apereo.cas.web.CasWebApplication</mainClassName> 
<isExecutable>true</isExecutable> 
<manifestFileToUse></manifestFileToUse> 
</properties> 
<build> 
<plugins> 
<plugin> 
<groupId>com.soebes.maven.plugins</groupId> 
<artifactId>echo-maven-plugin</artifactId> 
<version>0.3.0</version> 
<executions> 
<execution> 
<phase>prepare-package</phase> 
<goals> 
<goal>echo</goal> 
</goals> 
</execution> 
</executions> 
<configuration> 
<echos> 
<echo>Executable profile to make the generated CAS web application executable.</echo> 
</echos> 
</configuration> 
</plugin> 
</plugins> 
</build> 
</profile> 
<profile> 
<activation> 
<activeByDefault>false</activeByDefault> 
</activation> 
<id>bootiful</id> 
<properties> 
<app.server>-tomcat</app.server> 
<isExecutable>false</isExecutable> 
</properties> 
<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> 
</dependencies> 
</profile> 
<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> 

4、创建登陆处理类 这里我第一次先将账号密码写死,后面使用了jdbc去查询数据库,后面又集成了mybatis,所以这个比较乱,自行修改

代码语言:javascript
复制
package com.ding; 
import org.apereo.cas.authentication.HandlerResult; 
import org.apereo.cas.authentication.PreventedException; 
import org.apereo.cas.authentication.UsernamePasswordCredential; 
import org.apereo.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler; 
import org.apereo.cas.authentication.principal.PrincipalFactory; 
import org.apereo.cas.services.ServicesManager; 
import org.springframework.jdbc.core.JdbcTemplate; 
import org.springframework.jdbc.datasource.DriverManagerDataSource; 
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 
import javax.security.auth.login.FailedLoginException; 
import java.security.GeneralSecurityException; 
import java.util.HashMap; 
import java.util.Map; 
/** 
* 自定义登陆逻辑参考和页面 
* https://blog.csdn.net/u010588262/article/details/80014083 
* https://blog.csdn.net/weixin_37548740/article/details/104053834 
*/ 
public class Login extends AbstractUsernamePasswordAuthenticationHandler { 
private SysUserMapper sysUserMapper; 
public Login(String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order,SysUserMapper sysUserMapper) { 
super(name, servicesManager, principalFactory, order); 
this.sysUserMapper = sysUserMapper; 
} 
@Override 
protected HandlerResult authenticateUsernamePasswordInternal(UsernamePasswordCredential usernamePasswordCredential, String s) throws GeneralSecurityException, PreventedException { 
//使用jdbc试一下 和使用mybatis试试 
DriverManagerDataSource d=new DriverManagerDataSource(); 
d.setDriverClassName("com.mysql.jdbc.Driver"); 
d.setUrl("jdbc:mysql://127.0.0.1:3306/blog"); 
d.setUsername("root"); 
d.setPassword("123456"); 
JdbcTemplate template=new JdbcTemplate(); 
template.setDataSource(d); 
String username=usernamePasswordCredential.getUsername(); 
String pd=usernamePasswordCredential.getPassword(); 
Map<String,Object> user = template.queryForMap("SELECT `password` FROM t_sys_user WHERE user_name = ?", username); 
String pad = sysUserMapper.findUserName(username); 
System.out.printf(pad); 
//查询数据库加密的的密码 
if(username==null || username.equals("admin1111")){ 
throw new FailedLoginException("没有该用户"); 
} 
//返回多属性 
Map<String, Object> map=new HashMap<>(); 
map.put("email", "34865666@qq.com"); 
map.put("phone", "18850588888"); 
if(username.equals("admin") && pd.equals("123456")){ 
return createHandlerResult(usernamePasswordCredential, principalFactory.createPrincipal(username, map), null); 
} 
throw new FailedLoginException("Sorry, login attemp failed."); 
} 
} 

5、新增登陆配置器

代码语言:javascript
复制
package com.ding; 
import org.apereo.cas.authentication.AuthenticationEventExecutionPlan; 
import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer; 
import org.apereo.cas.authentication.AuthenticationHandler; 
import org.apereo.cas.authentication.principal.DefaultPrincipalFactory; 
import org.apereo.cas.configuration.CasConfigurationProperties; 
import org.apereo.cas.services.ServicesManager; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.beans.factory.annotation.Qualifier; 
import org.springframework.boot.context.properties.EnableConfigurationProperties; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
/** 
* 還需要在下面这个问题配置 不然spring不会管理这个文件 
* O:\cas\cas-overlay-template-5.2\cas-overlay-template-5.2\src\main\resource\META-INF\spring.factories 
*/ 
@Configuration("CustomAuthConfig") 
@EnableConfigurationProperties(CasConfigurationProperties.class) 
public class CustomAuthConfig implements AuthenticationEventExecutionPlanConfigurer { 
@Autowired 
private CasConfigurationProperties casProperties; 
@Autowired 
@Qualifier("servicesManager") 
private ServicesManager servicesManager; 
@Autowired 
private SysUserMapper sysUserMapper; 
@Bean 
public AuthenticationHandler myAuthenticationHandler() { 
final Login handler = new Login(Login.class.getSimpleName(), servicesManager, new DefaultPrincipalFactory(), 10,sysUserMapper); 
return handler; 
} 
@Override 
public void configureAuthenticationExecutionPlan(AuthenticationEventExecutionPlan plan) { 
plan.registerAuthenticationHandler(myAuthenticationHandler()); 
} 
} 

还需要在src\main\resource\META-INF\spring.factories进行配置 这个文件我们可以去overlay复制过来修改

代码语言:javascript
复制
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 
com.ding.CustomAuthConfig,\ 
com.ding.MyBatisDataSourceConfig 

6、resource建立services(注册客户端)

去将overlay的\services\HTTPSandIMAPS-10000001.json复制过来进行修改 这里由于我还没有配置https,先使用http所以serviceId配置http 我们可以通过这个进行自定义登陆页面 “theme”: “blog”,会找 resource\templates\blog\casLoginView.html

代码语言:javascript
复制
{ 
"@class" : "org.apereo.cas.services.RegexRegisteredService", 
"serviceId" : "^(https||http|imaps)://.*", 
"name" : "HTTPS and IMAPS", 
"id" : 10000001, 
"description" : "自定义登陆页面 theme 获取templates/blog/casLoginView.html", 
"evaluationOrder" : 10000, 
"theme": "blog", 
"attributeReleasePolicy": { 
"@class": "org.apereo.cas.services.ReturnAllAttributeReleasePolicy" 
} 
} 

7、配置application.properties配置文件 这个文件我们可以去overlay复制过来修改

代码语言:javascript
复制
## 
# CAS Server Context Configuration 
# 
server.context-path=/cas 
server.port=8080 
#####start#########签发证书,如果是用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=ALWAYS 
server.compression.enabled=true 
server.compression.mime-types=application/javascript,application/json,application/xml,text/html,text/xml,text/plain 
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 
#####end#########签发证书,如果是用spring boot之类嵌入式的容器,则需要改这里的配置,如果是直接部在tomcat中,则需要把tomcat改成https的################### 
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=true 
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 
# 
#cas.authn.accept.users=casuser::Mellon 
#开启识别json文件,默认false 
#自动扫描服务配置,默认开启 
#cas.serviceRegistry.watcherEnabled=true 
#120秒扫描一遍 
#cas.serviceRegistry.repeatInterval=120000 
#延迟15秒开启 
#cas.serviceRegistry.startDelay=15000 
#资源加载路径 
cas.serviceRegistry.config.location=classpath:/services 
cas.tgc.secure=false 
cas.serviceRegistry.initFromJson=true 
# 默认主题 
#cas.theme.defaultThemeName=blog 

8、自定义登陆页面 创建templates\blog目录及casLoginView.html文件 src\main\resource\templates\blog\casLoginView.html

代码语言:javascript
复制
<!DOCTYPE html> 
<html> 
<head> 
<meta charset="UTF-8"/> 
<meta name="viewport" content="width=device-width, initial-scale=1"/> 
<meta http-equiv="X-UA-Compatible" content="IE=edge"/> 
<link rel="stylesheet" th:href="@{${#themes.code('blog.css.file')}}"/> 
<script th:src="@{${#themes.code('blog.js.file')}}"></script> 
<title th:text="${#themes.code('demo.pageTitle')}"></title> 
</head> 
<!--里面以${#themes.code('blog.js.file')}形式获取的参数是从主题同名文件blog.properties中获取的:properties中获取的--> 
<body> 
<h1 th:text="${#themes.code('demo.pageTitle')}"></h1> 
<div> 
<form method="POST" th:object="${credential}"> 
<div th:if="${#fields.hasErrors('*')}"> 
<span th:each="err : ${#fields.errors('*')}" th:utext="${err}"/> 
</div> 
<h2 th:utext="#{screen.welcome.instructions}"></h2> 
<section class="row"> 
<label for="username" th:utext="#{screen.welcome.label.netid}"/> 
<div th:unless="${openIdLocalId}"> 
<input class="required" 
id="username" 
size="25" 
tabindex="1" 
type="text" 
th:disabled="${guaEnabled}" 
th:field="*{username}" 
th:accesskey="#{screen.welcome.label.netid.accesskey}" 
autocomplete="off"/> 
</div> 
</section> 
<section class="row"> 
<label for="password" th:utext="#{screen.welcome.label.password}"/> 
<div> 
<input class="required" 
type="password" 
id="password" 
size="25" 
tabindex="2" 
th:accesskey="#{screen.welcome.label.password.accesskey}" 
th:field="*{password}" 
autocomplete="off"/> 
</div> 
</section> 
<section> 
<input type="hidden" name="execution" th:value="${flowExecutionKey}"/> 
<input type="hidden" name="_eventId" value="submit"/> 
<input type="hidden" name="geolocation"/> 
<input class="btn btn-submit btn-block" 
name="submit" 
accesskey="l" 
th:value="#{screen.welcome.button.login}" 
tabindex="6" 
type="submit"/> 
<a th:href="@{/reg}">点我注册</a> 
</section> 
</form> 
</div> 
</body> 
</html> 

CAS Client搭建

1、新建一个spring boot项目 2、引入CAS客户端的依赖

代码语言:javascript
复制
<dependencies> 
<dependency> 
<groupId>org.springframework.boot</groupId> 
<artifactId>spring-boot-starter-web</artifactId> 
</dependency> 
<dependency> 
<groupId>org.mybatis.spring.boot</groupId> 
<artifactId>mybatis-spring-boot-starter</artifactId> 
<version>2.1.2</version> 
</dependency> 
<dependency> 
<groupId>org.springframework.boot</groupId> 
<artifactId>spring-boot-starter-test</artifactId> 
<scope>test</scope> 
</dependency> 
<dependency> 
<groupId>mysql</groupId> 
<artifactId>mysql-connector-java</artifactId> 
<scope>runtime</scope> 
</dependency> 
<!-- 要有一个验证登陆成功跳转页面,使用使用thymeleaf--> 
<dependency> 
<groupId>org.springframework.boot</groupId> 
<artifactId>spring-boot-starter-thymeleaf</artifactId> 
</dependency> 
<!--CAS客户端依赖--> 
<dependency> 
<groupId>org.jasig.cas.client</groupId> 
<artifactId>cas-client-core</artifactId> 
<version>3.5.0</version> 
</dependency> 
</dependencies> 

3、新增CAS客户端拦截器bean的配置

代码语言:javascript
复制
package com.ding.cas; 
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.AssertionThreadLocalFilter; 
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.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.ArrayList; 
import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 
/** 
* 查看下面这个链接和开放平台 
* https://www.cnblogs.com/whm-blog/p/11248304.html 
*/ 
@Configuration 
public class CasConfigure { 
@Autowired 
private CasClientProperties casClientProperties; 
/** 
* 用于实现单点登出功能 
*/ 
@Bean 
public ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> singleSignOutHttpSessionListener() { 
ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> listener = new ServletListenerRegistrationBean<>(); 
listener.setEnabled(true); 
listener.setListener(new SingleSignOutHttpSessionListener()); 
listener.setOrder(1); 
return listener; 
} 
/** 
* 该过滤器用于实现单点登出功能,单点退出配置,一定要放在其他filter之前 
*/ 
@Bean 
public FilterRegistrationBean singleSignOutFilter() { 
FilterRegistrationBean filterRegistration = new FilterRegistrationBean(); 
filterRegistration.setFilter(new SingleSignOutFilter()); 
filterRegistration.setEnabled(true); 
filterRegistration.addUrlPatterns(casClientProperties.getFilterUrl ()); 
filterRegistration.addInitParameter("casServerUrlPrefix", casClientProperties.getCasServerLoginUrl()); 
filterRegistration.addInitParameter("serverName", casClientProperties.getServerName()); 
filterRegistration.setOrder(3); 
return filterRegistration; 
} 
/** 
* 该过滤器负责用户的认证工作 
* @return 
*/ 
@Bean 
public FilterRegistrationBean authenticationFilterRegistrationBean() { 
FilterRegistrationBean authenticationFilter = new FilterRegistrationBean(); 
authenticationFilter.setFilter(new AuthenticationFilter()); 
Map<String, String> initParameters = new HashMap<>(); 
initParameters.put("casServerLoginUrl", casClientProperties.getCasServerLoginUrl()); 
initParameters.put("ignorePattern", "/openApi/|/recall/|/test/|/health|/open/|/error*|/webjars/|/swagger*|/Mei*|/assets*|/v2/api*"); 
initParameters.put("serverName", casClientProperties.getServerName()); 
authenticationFilter.setInitParameters(initParameters); 
authenticationFilter.setOrder(4); 
List<String> urlPatterns = new ArrayList<>(); 
urlPatterns.add(casClientProperties.getFilterUrl()); 
authenticationFilter.setUrlPatterns(urlPatterns); 
return authenticationFilter; 
} 
/** 
* 该过滤器负责对Ticket的校验工作 
* @return 
*/ 
@Bean 
public FilterRegistrationBean cas20ProxyReceivingTicketValidationFilter() { 
FilterRegistrationBean registrationBean = new FilterRegistrationBean(); 
registrationBean.setFilter(new Cas30ProxyReceivingTicketValidationFilter()); 
registrationBean.addUrlPatterns("/*"); 
registrationBean.addInitParameter("casServerUrlPrefix", casClientProperties.getCasServerUrlPrefix()); 
registrationBean.addInitParameter("serverName", casClientProperties.getServerName()); 
registrationBean.addInitParameter("useSession", String.valueOf(true)); 
registrationBean.addInitParameter("exceptionOnValidationFailure", String.valueOf(false)); 
registrationBean.addInitParameter("redirectAfterValidation", String.valueOf(true)); 
registrationBean.setEnabled(casClientProperties.isEnable()); 
registrationBean.setOrder(4); 
return registrationBean; 
} 
/** 
* 该过滤器对HttpServletRequest请求包装, 可通过HttpServletRequest的getRemoteUser()方法获得登录用户的登录名 
* @return 
*/ 
@Bean 
public FilterRegistrationBean casHttpServletRequestWrapperFilter(){ 
FilterRegistrationBean authenticationFilter = new FilterRegistrationBean(); 
authenticationFilter.setFilter(new HttpServletRequestWrapperFilter()); 
authenticationFilter.setOrder(6); 
List<String> urlPatterns = new ArrayList<String>(); 
urlPatterns.add(casClientProperties.getFilterUrl()); 
authenticationFilter.setUrlPatterns(urlPatterns); 
return authenticationFilter; 
} 
/** 
* 该过滤器使得可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。 
* 比如AssertionHolder.getAssertion().getPrincipal().getName()。 
* 这个类把Assertion信息放在ThreadLocal变量中,这样应用程序不在web层也能够获取到当前登录信息 
* @return 
*/ 
@Bean 
public FilterRegistrationBean casAssertionThreadLocalFilter(){ 
FilterRegistrationBean authenticationFilter = new FilterRegistrationBean(); 
authenticationFilter.setFilter(new AssertionThreadLocalFilter()); 
authenticationFilter.setOrder(7); 
List<String> urlPatterns = new ArrayList<String>(); 
urlPatterns.add(casClientProperties.getFilterUrl()); 
authenticationFilter.setUrlPatterns(urlPatterns); 
return authenticationFilter; 
} 
} 
@Configuration 
public class CasClientProperties { 
/** 
* 是否开启单点登录 
*/ 
private boolean enable = true; 
/** 
* 单点登录需要访问的CAS SERVER URL入口 
*/ 
private String casServerLoginUrl; 
/** 
* 托管此应用的服务器名称,例如本机:http://localhost:8080 
*/ 
private String serverName; 
/** 
* cas服务器的开头 例如 http://localhost:8443/cas 
*/ 
private String casServerUrlPrefix; 
/** 
* 验证白名单,当请求路径匹配此表达式时,自动通过验证 
*/ 
private String ignorePattern; 
/** 
* 白名单表达式的类型 
* REGEX 正则表达式 默认的 
* CONTAINS 包含匹配 
* EXACT 精确匹配 
*/ 
private String ignoreUrlPatternType; 
private String filterUrl; 
setget方法 
} 

4、新增登陆成功页面

代码语言:javascript
复制
@Controller 
public class IndexController { 
@RequestMapping("/index") 
public String sayHello(){ 
//方案一:获取其他属性和名字 
Map<String,Object> map = AssertionHolder.getAssertion().getPrincipal().getAttributes(); 
AssertionHolder.getAssertion().getPrincipal().getName(); 
//方案二:获取其他属性和名字 
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); 
Principal principal = request.getUserPrincipal(); 
Map<String, Object> attributes = ((AttributePrincipal) principal).getAttributes(); 
return "index"; 
} 
/** 
* 验证会不会被拦截 
* @return 
*/ 
@RequestMapping("/openApi/openApi") 
public String openApi(){ 
return "openApi"; 
} 
} 

新增index.html页面和openApi.html

代码语言:javascript
复制
<!DOCTYPE html> 
<html lang="en" xmlns="http://www.thymeleaf.org"> 
<head> 
<meta charset="UTF-8"> 
<title>Title</title> 
</head> 
<body> 
index 
</body> 
</html> 

CAS 安全性

TGC的安全性

对于一个CAS用户来说,最重要是要保护它的 TGC ,如果 TGC 不慎被 CAS Server 以外的实体获得, Hacker 能够找到该 TGC ,然后冒充 CAS 用户访问所有授权资源。

从基础模式可以看出, TGC 是 CAS Server 通过 SSL 方式发送给终端用户,因此,要截取 TGC 难度非常大,从而确保 CAS 的安全性。所以CAS的安全性是依赖于SSL的。

TGC 面临的风险主要并非传输窃取。比如你登陆了之后,没有 Logout ,离开了电脑,别人就可以打开你的浏览器,直接访问你授权访问的应用 ,设置一个 TGC 的有效期,可以减少被别人盗用

Service Ticket安全性

首要明白, Service Ticket 是通过 Http 传送的,所有网络中的其他人可以 Sniffer 到其他人的 Ticket 。

CAS 协议从几个方面让 Service Ticket 变得更加安全。

1、Service Ticket 只能使用一次。 CAS 协议规定,无论 Service Ticket 验证是否成功, CAS Server 都会将服务端的缓存中清除该 Ticket ,从而可以确保一个 Service Ticket 不能被使用两次。

2、Service Ticket 在一段时间内失效。 假设用户拿到 Service Ticket 之后,他请求 helloservice 的过程又被中断了, Service Ticket 就被空置了,事实上,此时, Service Ticket 仍然有效。 CAS 规定 Service Ticket 只能存活一定的时间,然后 CAS Server 会让它失效。

url带jsessionid处理

如下面链接,当我们从cas登录成功后跳回页面,url后面带上jsessionid,这样看起来很别扭 http://127.0.0.1:10086/index;jsessionid=5D9A11B35C145518155D141B771368F0

去掉jsessionid方法 我们只需要在cas登录页面form表单提交的时候设置method=”POST”,post要大写,小写不行

代码语言:javascript
复制
<form method="POST" th:object="${credential}"></form> 

CAS返回多属性

默认情况下CAS只会返回username给客户端,但是在实际情况下,一个username是不能满足我们的要求的,我们可能需要邮件,电话号码,权限等数据,所以我们需要对CAS进行改造 1、修改Cas server的services文件

代码语言:javascript
复制
修改services可以设置不同的客户端返回不同的属性 
使用ReturnAllAttributeReleasePolicy表示返回所有的属性,当然也可以限制返回部分属性,限制哪些属性不能返回。 
{ 
"@class" : "org.apereo.cas.services.RegexRegisteredService", 
"serviceId" : "^(https||http|imaps)://.*", 
"name" : "HTTPS and IMAPS", 
"id" : 10000001, 
"description" : "自定义登陆页面 theme 获取templates/blog/casLoginView.html", 
"evaluationOrder" : 10000, 
"theme": "blog", 
"attributeReleasePolicy": { 
"@class": "org.apereo.cas.services.ReturnAllAttributeReleasePolicy" 
} 
} 

2、在CAS server修改登陆处理逻辑

代码语言:javascript
复制
。。。。 
。。。。 
//返回多属性 
Map<String, Object> map=new HashMap<>(); 
map.put("email", "34865666@qq.com"); 
map.put("phone", "18850588888"); 
if(username.equals("admin") && pd.equals("123456")){ 
return createHandlerResult(usernamePasswordCredential, principalFactory.createPrincipal(username, map), null); 
} 
throw new FailedLoginException("Sorry, login attemp failed."); 

3、修改客户端 原本我以为只要执行上面2个步骤就行了。但是实际上,我在客户端还是获取不到。 这里我的CAS是5.2.26版本,原本是使用CAS2.0协议的,需要改成CAS3.0协议,

我们需要在检查Ticket的时候使用Cas30ProxyReceivingTicketValidationFilter,原本是使用Cas20ProxyReceivingTicketValidationFilter不行

代码语言:javascript
复制
/** 
* 该过滤器负责对Ticket的校验工作 
* @return 
*/ 
@Bean 
public FilterRegistrationBean cas20ProxyReceivingTicketValidationFilter() { 
FilterRegistrationBean registrationBean = new FilterRegistrationBean(); 
registrationBean.setFilter(new Cas30ProxyReceivingTicketValidationFilter()); 
registrationBean.addUrlPatterns("/*"); 
registrationBean.addInitParameter("casServerUrlPrefix", casClientProperties.getCasServerUrlPrefix()); 
registrationBean.addInitParameter("serverName", casClientProperties.getServerName()); 
registrationBean.addInitParameter("useSession", String.valueOf(true)); 
registrationBean.addInitParameter("exceptionOnValidationFailure", String.valueOf(false)); 
registrationBean.addInitParameter("redirectAfterValidation", String.valueOf(true)); 
registrationBean.setEnabled(casClientProperties.isEnable()); 
registrationBean.setOrder(4); 
return registrationBean; 

4.客戶端获取数据

代码语言:javascript
复制
@RequestMapping("/index") 
public String sayHello(){ 
//方案一:获取其他属性和名字 
Map<String,Object> map = AssertionHolder.getAssertion().getPrincipal().getAttributes(); 
AssertionHolder.getAssertion().getPrincipal().getName(); 
//方案二:获取其他属性和名字 
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); 
Principal principal = request.getUserPrincipal(); 
Map<String, Object> attributes = ((AttributePrincipal) principal).getAttributes(); 
return "index"; 
} 

参考

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-05-19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是单点登录
  • CAS原理
  • CAS Server搭建
  • CAS Client搭建
  • CAS 安全性
    • TGC的安全性
      • Service Ticket安全性
      • url带jsessionid处理
      • CAS返回多属性
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档