Spring Boot CAS 集成

                   最近项目中需要单点登录到其他系统,单点登录的服务校验用的是CAS,所以在网上查找了很多资料,最后结合前辈们的精华,终于搞定了,但是为了不让大家过多的去寻找资料汇总,我这边写一篇从安装到集成的,希望大家看到这篇文章就可以对CAS有个大体上了解,写的不好请大家不要喷我谢谢。

先看一下具体操纵流程(我这边使用的是REST方式进行对接CAS的)因为我的项目是老项目,已经有登陆了所以就不用跳转到CAS的登陆页面了。

Spring Boot CAS 集成_第1张图片

对接CAS的流程就是上图的如果看不懂图片流程,也没关系,具体操作起来慢慢的一步一步的操作也能明白。

第一步:安装CAS服务器

下载服务器文件

https://github.com/apereo/cas-overlay-template

我这里下载5.2版本的

Spring Boot CAS 集成_第2张图片

下载ZIP包

Spring Boot CAS 集成_第3张图片

解压开就是下图这样了

Spring Boot CAS 集成_第4张图片

是不是一脸懵,我刚下下载下来看了一下结构也是一脸懵,是maven项目,但是 没有src目录,这个项目就是么有代码目录,我们只能通过配置文件进行修改。

我们刚刚下载下来的这个CAS 服务本身是不支持REST的,我们需要添加RETS的配置,让CAS支持REST。对了还有数据库连接支持也一起写上。

我们打开pom.xml

添加如果支持


    
        mysql
        mysql-connector-java
        5.1.21
    
    
        org.apereo.cas
        cas-server-support-jdbc-drivers
        ${cas.version}
    
    
    
        org.apereo.cas
        cas-server-support-jdbc
        ${cas.version}
    
    
        org.apereo.cas
        cas-server-support-rest
        ${cas.version}
    
    
        org.apereo.cas
        cas-server-support-rest-tokens
        ${cas.version}
    

添加完成后就可以打包了。

liunx 执行

./build.sh package

windows 执行

build.cmd package

直到出现构建成功即可如图(构建过程可能出现多次错误,重试就可以了)

 

如果不想自己构建,我这边为大家构建好了,直接拿去用就可以地址如下

https://download.csdn.net/download/cdszdd8/11945308

打好的war包放到容器里让容器为他自己解压,解压后目录如图

Spring Boot CAS 集成_第5张图片

我们需要修改的就是下图这个application.properties(这里的目录是容器里解压后的项目目录啊,别找错了呢)

Spring Boot CAS 集成_第6张图片

打开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

我们找到下图的文件(这里的目录是容器解压后的目录啊,别找错了啊)

Spring Boot CAS 集成_第7张图片

打开后添加一个http如图就可以了。

Spring Boot CAS 集成_第8张图片

启动容器。出现如下图片说明启动成功了。

Spring Boot CAS 集成_第9张图片

访问一下

Spring Boot CAS 集成_第10张图片

到此服务端安装成功界面上的错误,是因为我们没有配置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 params =new ArrayList();
        params.add(new BasicNameValuePair("username",username));
        params.add(new BasicNameValuePair("password",password));
        Map header=new HashMap();
        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 params =new ArrayList();
        params.add(new BasicNameValuePair("service",casClientProperties.getLogOutUrl()+"/"+moduleName));
        Map header=new HashMap();
        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 params =new HashMap<>();
        params.put("ticket",serviceTicket);
        params.put("service",casClientProperties.getLogOutUrl()+"/"+moduleName);
        Map header=new HashMap();
        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("");
        if (begin < 0)
            return BaseDto.error("获取用户登录失败!");
        int end = res.indexOf("");
        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 来校验就可以获取到用了。

好吧,就写到这里吧,如果有疑问就评论里给我留言吧,我尽量给大家解释,谢谢观看。

 

 

 

 

你可能感兴趣的:(CAS)