最近项目中需要单点登录到其他系统,单点登录的服务校验用的是CAS,所以在网上查找了很多资料,最后结合前辈们的精华,终于搞定了,但是为了不让大家过多的去寻找资料汇总,我这边写一篇从安装到集成的,希望大家看到这篇文章就可以对CAS有个大体上了解,写的不好请大家不要喷我谢谢。
先看一下具体操纵流程(我这边使用的是REST方式进行对接CAS的)因为我的项目是老项目,已经有登陆了所以就不用跳转到CAS的登陆页面了。
对接CAS的流程就是上图的如果看不懂图片流程,也没关系,具体操作起来慢慢的一步一步的操作也能明白。
第一步:安装CAS服务器
下载服务器文件
https://github.com/apereo/cas-overlay-template
我这里下载5.2版本的
下载ZIP包
解压开就是下图这样了
是不是一脸懵,我刚下下载下来看了一下结构也是一脸懵,是maven项目,但是 没有src目录,这个项目就是么有代码目录,我们只能通过配置文件进行修改。
我们刚刚下载下来的这个CAS 服务本身是不支持REST的,我们需要添加RETS的配置,让CAS支持REST。对了还有数据库连接支持也一起写上。
我们打开pom.xml
添加如果支持
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.21</version>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-jdbc-drivers</artifactId>
<version>${cas.version}</version>
</dependency>
<!--jdbc认证需要添加的,这个是cas的依赖包-->
<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-rest</artifactId>
<version>${cas.version}</version>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-rest-tokens</artifactId>
<version>${cas.version}</version>
</dependency>
</dependencies>
添加完成后就可以打包了。
liunx 执行
./build.sh package
windows 执行
build.cmd package
直到出现构建成功即可如图(构建过程可能出现多次错误,重试就可以了)
如果不想自己构建,我这边为大家构建好了,直接拿去用就可以地址如下
打好的war包放到容器里让容器为他自己解压,解压后目录如图
我们需要修改的就是下图这个application.properties(这里的目录是容器里解压后的项目目录啊,别找错了呢)
打开application.properties 替换如下代码
##
# CAS Server Context Configuration
#
server.context-path=/cas
server.port=8443
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
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
cas.serviceRegistry.initFromJson=true
cas.authn.jdbc.query[0].sql=SELECT * FROM access_user WHERE name = ?
cas.authn.jdbc.query[0].healthQuery=
cas.authn.jdbc.query[0].isolateInternalQueries=false
cas.authn.jdbc.query[0].url=jdbc:mysql://xx.xx.xx.x:3306/xxxx?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=xxxx
cas.authn.jdbc.query[0].password=xxx
cas.authn.jdbc.query[0].ddlAuto=create-drop
cas.authn.jdbc.query[0].maxAgeDays=180
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.ticket.st.numberOfUses=50
cas.ticket.st.timeToKillInSeconds=120
cas.logout.followServiceRedirects=true
#加密策略 默认NONE未加密 可支持MD5 、 SHA
#cas.authn.jdbc.query[0].passwordEncoder.type=MD5
在这里我们还需要修改一个文件,就是cas默认只支持https我们这边修改他让他可以支持http
我们找到下图的文件(这里的目录是容器解压后的目录啊,别找错了啊)
打开后添加一个http如图就可以了。
启动容器。出现如下图片说明启动成功了。
访问一下
到此服务端安装成功界面上的错误,是因为我们没有配置https这里配置https 我就不配置了,如果需要用的话,去下载一个免费的证书配置到容器上就可以了。不要为了这个去捣鼓你的jdk,我这边按网上的弄结果我太菜了,把jdk搞坏了,所以这个东西不好弄成功,所以还是不要弄了。
spring boot 集成CAS
我们在application-xxx.xml 中添加配置
#http://xxxxxx/cas 是cas的容器
#https://xxxxx.com 这个是我们服务1的地址
cas:
casServerLoginUrl: http://xxxxxx/cas/login
serverName: https://xxxxx.com
casServerUrlPrefix: http://xxxx/cas
useSession: true
redirectAfterValidation: true
exceptionOnValidationFailure: true
建一个配置文件工具类
@ConfigurationProperties(prefix = "cas")
@Configuration
public class CasClientProperties {
/**
* 单点登录需要访问的CAS SERVER URL入口
*/
private String casServerLoginUrl;
/**
* 托管此应用的服务器名称
*/
private String serverName;
/**
* 指定是否应将renew = true发送到CAS服务器
*/
private boolean renew = false;
/**
* 指定是否应将gateway = true发送到CAS服务器
*/
private boolean gateway = false;
/**
* cas服务器的开头 例如 http://localhost:8443/cas
*/
private String casServerUrlPrefix;
/**
* 是否将Assertion 存入到session中
* 如果不使用session(会话),tickets(票据)将每次请求时都需要tickets
*/
private boolean useSession = true;
/**
* 是否在票证验证后重定向到相同的URL,但在参数中没有票证
*/
private boolean redirectAfterValidation = true;
/**
* 是否在tickets验证失败时抛出异常
*/
private boolean exceptionOnValidationFailure = false;
private String logOutUrl;
private String restUrl;
public boolean isEnable() {
return enable;
}
public void setEnable(boolean enable) {
this.enable = enable;
}
public String getCasServerLoginUrl() {
return casServerLoginUrl;
}
public void setCasServerLoginUrl(String casServerLoginUrl) {
this.casServerLoginUrl = casServerLoginUrl;
}
public String getServerName() {
return serverName;
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
public boolean isRenew() {
return renew;
}
public void setRenew(boolean renew) {
this.renew = renew;
}
public boolean isGateway() {
return gateway;
}
public void setGateway(boolean gateway) {
this.gateway = gateway;
}
public String getCasServerUrlPrefix() {
return casServerUrlPrefix;
}
public void setCasServerUrlPrefix(String casServerUrlPrefix) {
this.casServerUrlPrefix = casServerUrlPrefix;
}
public boolean isUseSession() {
return useSession;
}
public void setUseSession(boolean useSession) {
this.useSession = useSession;
}
public boolean isRedirectAfterValidation() {
return redirectAfterValidation;
}
public void setRedirectAfterValidation(boolean redirectAfterValidation) {
this.redirectAfterValidation = redirectAfterValidation;
}
public boolean isExceptionOnValidationFailure() {
return exceptionOnValidationFailure;
}
public void setExceptionOnValidationFailure(boolean exceptionOnValidationFailure) {
this.exceptionOnValidationFailure = exceptionOnValidationFailure;
}
public String getLogOutUrl() {
return logOutUrl;
}
public void setLogOutUrl(String logOutUrl) {
this.logOutUrl = logOutUrl;
}
public String getRestUrl() {
return restUrl;
}
public void setRestUrl(String restUrl) {
this.restUrl = restUrl;
}
}
再写个cas 操作工具类
/**
* Stone.Cai
* 2019年10月18日15:56:03
* 添加
* sso登录登出服务
*/
@Service
public class SsoService {
@Autowired
private AccessUserRepository accessUserRepository;
@Autowired
private CasClientProperties casClientProperties;
/**
* Stone.Cai
* 2019年10月28日16:36:21
* 添加
* 获取TGT
* @param username
* @param password
* @return
*/
private BaseDto findTgT(String username, String password){
if(StringUtils.isBlank(username)||StringUtils.isBlank(password)){
return BaseDto.error("数据传输错误!");
}
List<NameValuePair> params =new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("username",username));
params.add(new BasicNameValuePair("password",password));
Map<String,String> header=new HashMap<String,String>();
header.put("Content-Type","application/x-www-form-urlencoded");
String res= HttpClientUtil.doPost(casClientProperties.getCasServerUrlPrefix()+"/v1/tickets",params,header);
if(StringUtils.isBlank(res)){
return BaseDto.error("用户信息错误!");
}
Matcher matcher = Pattern.compile(".*action=\".*/(.*?)\".*").matcher(res);
if (matcher.matches()) {
return BaseDto.success("操作成功!",matcher.group(1));
}
return BaseDto.error("用户信息错误!");
}
/**
* Stone.Cai
* 2019年10月28日16:44:23
* 添加
* 根据用户信息获取ST
* @return
*/
private BaseDto findST(String ticketGrantingTicket,String moduleName){
if(StringUtils.isBlank(ticketGrantingTicket)){
return BaseDto.error("数据传输错误!");
}
List<NameValuePair> params =new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("service",casClientProperties.getLogOutUrl()+"/"+moduleName));
Map<String,String> header=new HashMap<String,String>();
header.put("Content-Type","application/x-www-form-urlencoded");
String res= HttpClientUtil.doPost(casClientProperties.getCasServerUrlPrefix()+"/v1/tickets/"+ticketGrantingTicket,params,header);
if(StringUtils.isBlank(res)){
return BaseDto.error("获取用户登录失败!");
}
return BaseDto.success("操作成功",res);
}
/**
* Stone.Cai
* 2019年10月28日16:55:20
* 添加
* 获取ST是否成功
* @param serviceTicket
* @param moduleName
* @return
*/
public BaseDto verifyServiceTicket(String serviceTicket, String moduleName){
Map<String, String> params =new HashMap<>();
params.put("ticket",serviceTicket);
params.put("service",casClientProperties.getLogOutUrl()+"/"+moduleName);
Map<String,String> header=new HashMap<String,String>();
header.put("Content-Type","application/x-www-form-urlencoded");
String res= HttpClientUtil.getRequest(casClientProperties.getCasServerUrlPrefix()+"/p3/serviceValidate",params,"","UTF-8");
if(StringUtils.isBlank(res)){
return BaseDto.error("获取用户登录失败!");
}
int begin = res.indexOf("<cas:user>");
if (begin < 0)
return BaseDto.error("获取用户登录失败!");
int end = res.indexOf("</cas:user>");
String user= res.substring(begin + 10, end);
AccessUser myuser= accessUserRepository.isNameRepeat(user);
if(myuser==null){
return BaseDto.error("获取用户登录失败!");
}
JSONObject obj=new JSONObject();
obj.put("userName",myuser.getName());
obj.put("pwd",myuser.getPassword());
String code="";
try {
code=AesUtil.aesPKCS7PaddingEncrypt(obj.toJSONString());
} catch (Exception e) {
e.printStackTrace();
}
return BaseDto.success("操作成功", URLEncoder.encode(code));
}
/**
* Stone.Cai
* 2019年10月28日17:13:30
* 添加
* login
* @param userName
* @param password
* @return
*/
public BaseDto login(String userName,String password){
//获取tgt
BaseDto dto= findTgT(userName,password);
// if(dto.getCode()!=200){
// return dto;
// }
//获取st
//dto= findST(dto.getDataList().toString(),"");
return dto;
}
/**
* Stone.Cai
* 2019年10月30日13:56:59
* 添加
* 获取ST进行单点登录
* @param tgt
* @return
*/
public BaseDto loginST(String tgt){
return findST(tgt,"");
}
/**
* Stone.Cai
* 2019年10月31日09:42:41
* 添加
* 删除TGT 退出
* @param tgt
* @return
*/
public BaseDto deleteTicketGrantingTicket(String tgt){
if (StringUtils.isBlank(tgt))
return BaseDto.error("用户信息错误!");
HttpClientUtil.deleteRequest(casClientProperties.getCasServerUrlPrefix()+"/v1/tickets/"+tgt);
return BaseDto.success("操作成功!");
}
这样就后太就可以进行cas 校验了
服务器1 用户在登陆成功后接着调用 findTgT 来获取TGT 并保存起来 ,如果要跳转到服务器2 需要用TGT 获取 findST 能拿到ST后就可以跳转到 http://server2?ticket=ST 这样 到服务器2后如果检测到ticket有值就使用 verifyServiceTicket 来校验就可以获取到用了。
好吧,就写到这里吧,如果有疑问就评论里给我留言吧,我尽量给大家解释,谢谢观看。