SpringBoot 实现CAS Server统一登录认证

SpringBoot 集成CAS Server

一、CAS Service服务介绍

​ CAS(Central Authentication Service)中心授权服务,是一个开源项目,目的在于为Web应用系统提供一种可靠的单点登录。

​ 在整个认证的流程中的整个流程大概是:首先由CAS Client(我们的客户端应用)发起请求,CAS Client 会重定向到CAS Server进行登录,CAS Server进行账户校验且多个CAS Client 之间可以共享登录的 session ,Server 和 Client 是一对多的关系。基于CAS的SSO访问流程步骤:

  1. 访问服务: CAS Client 客户端发送请求访问应用系统提供的服务资源。
  2. 定向认证: CAS Client 客户端会重定向用户请求到 CAS Server 服务器。
  3. 用户认证: 用户在浏览器端输入用户验证信息,CAS Server服务端完成用户身份认证。
  4. 发放票据: CAS Server服务器会产生一个随机的 Service Ticket 。
  5. 验证票据: CAS Server服务器验证票据 Service Ticket 的合法性,验证通过后,允许客户端访问服务。
  6. 传输用户信息: CAS Server 服务器验证票据通过后,传输用户认证结果信息给客户端。

在这里插入图片描述

​ 从结构上看,CAS 包含两个部分: CAS ServerCAS Client 。 CAS Server 需要独立部署,主要负责对用户的认证工作; CAS Client 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS Server。

​ CAS Client 与受保护的客户端应用部署在一起,以 Filter 方式保护受保护的资源。对于访问受保护资源的每个 Web 请求, CAS Client 会分析该请求的 Http 请求中是否包含 Service Ticket。如果没有,则说明当前用户尚未登录,于是将请求重定向到指定好的 CAS Server 登录地址,并传递 Service (也就是要访问的目的资源地址),以便登录成功过后转回该地址。

​ 在流程图中的第三步输入认证信息,登陆成功后,CAS Server 随机产生一个相当长度、唯一、不可伪造的 Service Ticket,并缓存以待将来验证。之后系统自动重定向到 Service 所在地址,并为客户端浏览器设置一个 Ticket Granted Cookie(TGC),CAS Client 在拿到 Service 和新产生的 Ticket 过后,在第 5,6 步中与 CAS Server 进行身份核实,以确保 Service Ticket 的合法性。

二、CAS Server服务的搭建

2.1 下载cas-overlay-template

​ 这里为大家提供一个 5.1版本的 git地址:<https://github.com/apereo/cas-overlay-template/tree/5.1

5.3版本的git地址

网上都能找到很多下载方式的,或者私信我 我测试使用的是cas-overlay-template-5.3

​ 获取到项目后zip的方式解压出来后的目录如下:

在这里插入图片描述

2.2 使用外部Tomcat部署CAS Server

  • 解压出来文件夹之后,就可以进行打包运行了。在解压的目录下打开命令行 到安装目录下 使用build.cmd run 来进行编译打包。过程可能需要花点时间,

在这里插入图片描述

  • 在打包完成之后就会启动我们的CAS Server,但是服务现在是没有正常启动的,因为Cas server 配置证书路径是基于linux的,而我们是在windows环境下部署,目录结构不一致导致无法找到相应的文件,如果是linux环境的话就可以成功启动了。

在这里插入图片描述

​ 启动失败:

在这里插入图片描述

打包完成之后会在解压的目录下面多了target文件夹:

在这里插入图片描述

  • 我们将Cas.war(或者直接使用Cas文件夹) 复制到本机的 Tomcat 的 webapp目录中,启动Tomcat即可

在这里插入图片描述

启动Tomcat,通过Tomcat日志就可以看见我们的CAS Server是否启动成功了:

在这里插入图片描述

然后我们就能访问部署到本地CAS Server 服务了。通过 127.0.0.1:8080/cas/login 来访问CAS服务,这里的地址需要根据自己的实际情况来定(比如你部署的Tomcat服务默认地址8080是否改变过 等情况):

在这里插入图片描述

至此我们使用外部Tomcat部署CAS Server服务就成功了!

这里可以使用默认的账户密码(账:casuser 密: Mellon)进行登录,

至于后续可能出现的一些疑难杂症(包括静态用户的设置、http的支持等),文章下面部门章节会进行介绍。目前先简单的将CAS Server服务部署上去!

2.3 使用IDEA部署CAS Server

​ 在IDEA中我们直接将项目通过maven工具加载pom文件中的jar包和package命令生成运行包target。然后在项目中建立本地项目的src/main/java 和 src/main/resources目录。最后将target包中的/cas/WEB_INF/classes/services和aplication.properties和log4j2.xml以及/cas/WEB_INF/classes/META-INF复制到resources目录中。

  • 首先通过IDEA将解压后的CAS Server文件打开进行打包

    打开文件后,首相将项目的mave配置好(File/Settings/Build,…/Build Tools/Maven 中配置好自己本地的maven地址),然后加载pom文件中的Jar包。

    maven配置好之后利用maven对项目进行打包

在这里插入图片描述

  • 移动相关配置文件

    项目打包完成之后,在项目中创建Java文件夹和resources文件夹,将上述提到的target文件中的4个文件复制到我们自己创建的resources目录下

    在这里插入图片描述

  • 给CAS Server 项目配置tomcat服务

    首先给CAS Server 添加tamcat服务器

    在这里插入图片描述

​ 配置好Tomcat服务的地址 以及 端口号等基本信息在这里插入图片描述

​ 部署Tomcat服务器

在这里插入图片描述

在这里插入图片描述

选择war exploded模式进行部署我们的Tomcat,选择之后,将下面的 Application context 基路径中的数据改为 / 即可。

  • 为CAS Server 配置JDK

    打开 File/Project Stucture/Project中进行设置

在这里插入图片描述

所有配置都准备完毕之后 我们就可以启动Tomcat了,tomcat启动之后就能够正常访问到我们的CAS Server服务了!

在这里插入图片描述

通过 127.0.0.1:8080/login 即可访问到我们的CAS Service。这里可以使用默认的账户密码(账:casuser 密: Mellon)进行登录。

推出登录的地址: 127.0.0.1:8080/logout

在这里插入图片描述

三、CAS Server的其他配置

3.1 CAS Server 去掉https验证

​ 这里这样设置的目的是为了,后续我们通过项目去请求CAS Server 服务时,能够通过 http 的方式去访问我们的CAS Server服务!

​ 在CAS Server服务 4.2版本时对整体的架构进行了一个优化。

允许Http访问CAS Server 的配置设置:

  • application.properties 文件中的最后一行配置
#忽略https安全协议,使用 HTTP 协议
cas.tgc.secure=false
  • src/main/resources/services中的 HTTPSandIMAPS-10000001.json文件
//原数据
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "^(https|imaps)://.*",
  "name" : "HTTPS and IMAPS",
  "id" : 10000001,
  "description" : "This service definition authorizes all application urls that support HTTPS and IMAPS protocols.",
  "evaluationOrder" : 10000
}

// 需要将  "serviceId" : "^(https|imaps)://.*",
//修该成为:"serviceId" : "^(https|imaps|http)://.*" 即可!
如果你的CAS Server 服务的版本号在4.2 以下的,可以在去查询一下配置方法,这里就不在记录 4.2版本以下的修改方式!

3.2 静态认证用户的添加

​ 静态认证用户是通过 WEb-INF\classes\application.properties 文件中去配置的在这里插入图片描述

​ 在配置文件中,我们的认证用户数量可以添加配置多个,如上图所示,就配置了两个认证用户。

​ 如果你是通过IDEA来实现的CAS Server 服务的部署,那么只需要修改 main/resoources/application.properties文件。

3.3 配置数据库查询认证用户

  • 首先是在maven中导入相关依赖jar
 <dependencies>
        <dependency>
            <groupId>org.apereo.cas</groupId>
            <artifactId>cas-server-support-jdbc</artifactId>
            <version>6.5.0</version>
        </dependency>
        <dependency>
            <groupId>org.apereo.cas</groupId>
            <artifactId>cas-server-support-jdbc-drivers</artifactId>
            <version>6.5.0</version>
        </dependency>
        <!--数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>
    </dependencies>
  • 在通过application.properties文件中添加下面配置
# 注释静态验证的配置
cas.tgc.secure=false
cas.serviceRegistry.initFromJson=true
 
#加密迭代次数
cas.authn.jdbc.encode[0].numberOfIterations=3
#该列名的值可替代上面的值,但对密码加密时必须取该值进行处理
cas.authn.jdbc.encode[0].numberOfIterationsFieldName=
#盐值固定列
cas.authn.jdbc.encode[0].saltFieldName=account
#静态盐值
cas.authn.jdbc.encode[0].staticSalt=.
cas.authn.jdbc.encode[0].sql=SELECT * FROM user WHERE account =?
#对处理盐值后的算法
cas.authn.jdbc.encode[0].algorithmName=MD5
cas.authn.jdbc.encode[0].passwordFieldName=password
cas.authn.jdbc.encode[0].expiredFieldName=expired
cas.authn.jdbc.encode[0].disabledFieldName=disabled
#数据库连接
cas.authn.jdbc.encode[0].url=jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&characterEncoding=UTF-8
#cas.authn.jdbc.encode[0].dialect=org.hibernate.dialect.MySQL5Dialect
cas.authn.jdbc.encode[0].driverClass=com.mysql.jdbc.Driver
cas.authn.jdbc.encode[0].user=root
cas.authn.jdbc.encode[0].password=123456

同时我们需要注释掉 之前配置在application.properties文件中的 静态认证用户,即: cas.auth.accept.users=xxxx数据需要注释掉。

3.4 解决未认证授权的服务

​ 在我们的CAS Client 客户端服务,跳转到CAS Server 进行用户授权登录认证时,我们的CAS Server 服务提示:

“未认证授权的服务
CAS的服务记录是空的,没有定义服务。 希望通过CAS进行认证的应用程序必须在服务记录中明确定义。”

​ 出现这种情况的原因是,我们的CAS Server 服务端中还没有定义对应的服务,也就是我们的应用服务(客户端),需要在CAS Server 服务端进行记录信息,这样才能通过Client客户端跳转到我CAS Server 服务端来进行用户认证。

​ 对应客户端在CAS Server 服务端中是否注册成功,通过我们的CAS Server 服务启动时Tomcat的日志也能看出来。

​ 没有CAS Client 客户端注册的情况下的日志:

2023-11-09 16:37:59,935 INFO [org.apereo.cas.services.AbstractServicesManager] - <Loaded [0] service(s) from [InMemoryServiceRegistry].>
2023-11-09 16:38:59,947 INFO [org.apereo.cas.services.AbstractServicesManager] - <Loaded [0] service(s) from [InMemoryServiceRegistry].>

​ CAS Server中注册了CAS Client客户端时,Tomcat的启动日志:

2023-11-09 16:43:15,594 INFO [org.apereo.cas.ticket.registry.DefaultTicketRegistryCleaner] - <[0] expired tickets removed.>
2023-11-09 16:44:05,568 INFO [org.apereo.cas.services.AbstractServicesManager] - <Loaded [2] service(s) from [InMemoryServiceRegistry].>
2023-11-09 16:45:05,573 INFO [org.apereo.cas.services.AbstractServicesManager] - <Loaded [2] service(s) from [InMemoryServiceRegistry].>

  • 详细的配置过程:

Client Server 的配置位置: 在 HTTPSandIMAPS-10000001.json 文件中配置我们的客户端信息。

{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "^(https|http|imaps)://.*",
  "name" : "HTTPS and IMAPS",
  "id" : 10000001,
  "description" : "This service definition authorizes all application urls that support HTTPS and IMAPS protocols.",
  "evaluationOrder" : 10000
}

配置完json数据后,还需要改动application.properties 文件中的信息,在改配置文件中添加下面两行数据,这样才能使得我们的CAS Server服务端能够读取到配置的客户端信息。

#是否开启json识别功能,默认为false
cas.serviceRegistry.initFromJson=true
#忽略https安全协议,使用 HTTP 协议
cas.tgc.secure=false

上面两个配置完成之后,重启Tomcat服务。

在这里插入图片描述

通过Tomcat日志可以看出,我们的配置已经生效,CAS Server服务端已经读取到配置文件中的客户端。

三、SpringBoot集成cas-client-core实现CAS认证

​ 在部署完CAS Server 认证服务端之后,我们就需要通过CAS Client客户端集成CAS 服务实现集成认证了。在SpringBoot 中 可以通过集成CAS-Client即可对Cas认证进行集成。

集成这部分文章引荐:https://blog.csdn.net/uziuzi669/article/details/119486588

  • 引入POM依赖

这里需要根据自己部署的CAS Server 服务版本选择合适的依赖版本

<!--Cas单点登录认证-->
<dependency>
    <groupId>org.jasig.cas.client</groupId>
    <artifactId>cas-client-core</artifactId>
    <version>3.5.0</version>
</dependency>
  • CAS集成的核心配置类
package com.wxxssf.CasAuthLogin.casConfig;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.Cas20ProxyReceivingTicketValidationFilter;
import org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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 org.springframework.core.Ordered;

/**
 * @Description: cas集成核心配置类
 * @ClassName: CasConfig
 */
@Configuration
@Slf4j
@ConditionalOnProperty(value ="cas.validation-type",havingValue = "cas") //根据应用程序配置文件中的属性值来控制Bean的创建和加载

public class CasConfig {

    /**
    *@Description 需要走cas拦截器的地址
    */
    @Value("${cas.urlPattern:/cas/loginByNameAndCardNo}")
    private String filterUrl;

    /**
     * 默认的cas地址,防止通过 配置信息获取不到,CAS服务端的登录地址,login为固定值
     */
    @Value("${cas.server-url-prefix:https://ciap7.wisedu.com/authserver/login}")
    private String casServerUrl;

    /**
     * 应用校验访问地址(这个地址需要在cas服务端进行配置)
     */
    @Value("${cas.authentication-url:https://ciap7.wisedu.com/authserver}")
    private String authenticationUrl;

    /**
     * 应用访问地址(这个地址需要在cas服务端进行配置)
     */
    @Value("${cas.client-host-url:http://localhost:8090}")
    private String appServerUrl;


    @Bean
    public ServletListenerRegistrationBean servletListenerRegistrationBean() {
        log.info(" servletListenerRegistrationBean  \n cas 单点登录配置 \n appServerUrl = " + appServerUrl + "\n casServerUrl = " + casServerUrl);
        ServletListenerRegistrationBean listenerRegistrationBean = new ServletListenerRegistrationBean();
        listenerRegistrationBean.setListener(new SingleSignOutHttpSessionListener());
        listenerRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return listenerRegistrationBean;
    }

    /**
     * 单点登录退出
     */
    @Bean
    public FilterRegistrationBean singleSignOutFilter() {
        log.info(" servletListenerRegistrationBean ");
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new SingleSignOutFilter());
        registrationBean.addUrlPatterns(filterUrl);
        registrationBean.addInitParameter("casServerUrlPrefix", casServerUrl);
        registrationBean.setName("CAS Single Sign Out Filter");
        registrationBean.setOrder(1);
        return registrationBean;
    }

    /**
     * 单点登录认证
     */
    @Bean
    public FilterRegistrationBean AuthenticationFilter() {
        log.info(" AuthenticationFilter ");
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new AuthenticationFilter());
        registrationBean.addUrlPatterns(filterUrl);
        registrationBean.setName("CAS Filter");
        registrationBean.addInitParameter("casServerLoginUrl", casServerUrl);
        registrationBean.addInitParameter("serverName", appServerUrl);
        registrationBean.setOrder(1);
        return registrationBean;
    }

    /**
     * 决定票据验证过滤器的版本,默认30,old是20版
     */
    @Value("${cas.filterVersion:new}")
    private String filterVersion;


    /**
     * 单点登录校验
     */
    @Bean
    public FilterRegistrationBean Cas30ProxyReceivingTicketValidationFilter() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        if (StringUtils.isNotBlank(filterVersion) && filterVersion.equals("old")){
            log.info(" Cas20ProxyReceivingTicketValidationFilter ");
            registrationBean.setFilter(new Cas20ProxyReceivingTicketValidationFilter());
        }else {
            log.info(" Cas30ProxyReceivingTicketValidationFilter ");
            registrationBean.setFilter(new Cas30ProxyReceivingTicketValidationFilter());
        }
        registrationBean.addUrlPatterns(filterUrl);
        registrationBean.setName("CAS Validation Filter");
        registrationBean.addInitParameter("casServerUrlPrefix", authenticationUrl);
        registrationBean.addInitParameter("serverName", appServerUrl);
        registrationBean.setOrder(1);
        return registrationBean;
    }

    /**
     * 单点登录请求包装
     */
    @Bean
    public FilterRegistrationBean httpServletRequestWrapperFilter() {
        log.info(" httpServletRequestWrapperFilter ");
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        //registrationBean.setFilter(new HttpServletRequestWrapperFilter());
        registrationBean.setFilter(new HttpServletRequestWrapperFilter());
        registrationBean.addUrlPatterns(filterUrl);
        registrationBean.setName("CAS HttpServletRequest Wrapper Filter");
        registrationBean.setOrder(1);
        return registrationBean;
    }
}

​ 上面的单点登录校验器,需要我们创建票据验证 TicketValidationFiter ,但是需要注意的是票据验证过滤器有两种类型分别是:Cas30ProxyReceivingTicketValidationFilter 和 Cas20ProxyReceivingTicketValidationFilter。

​ 在认证票据是会出现报错的情况,这时候就需要考虑这个票据验证器TicketValidationFiter的版本问题,CAS Server 的版本是否兼容Filter,从而引起冲突问题,具体使用哪一种票据验证器,需要根据实际情况去调整,这个票据验证器的选择也是通过application.properties配置文件中去实现的动态选择。

  • 上面的各个连接地址是通过application.properties文件进行动态配置读取的,示例如下:
#===Cas集成认证===
#需要走拦截器的地址 /api/loginByNameAndCardNo :验票拦截路径
cas.urlPattern = /cas/loginByNameAndCardNo
# 客户端如果要登录,会跳转到CAS服务端的登录地址(认证地址) : 认证中心登录页面地址   
# http://192.168.0.145:8080/cas/login
cas.server-url-prefix = http://192.168.0.145:8080/cas/login
# CAS 服务端地址(认证平台地址):认证中心地址  
# http://192.168.0.145:8080/cas
cas.authentication-url = http://192.168.0.145:8080/cas
# 客户端在CAS服务端登录成功后,自动从CAS服务端跳转回客户端的地址 :应用地址,也就是自己的系统地址。 https://cwfw.mtxy.edu.cn
cas.client-host-url = http://192.168.0.145:8998
# Ticket校验器使用 Cas30ProxyReceivingTicketValidationFilter :动态开启 cas 单点登录
cas.validation-type = cas
# 验票器版本
cas.filterVersion = new
  • CAS认证用户信息Vo类
package com.wxxssf.CasAuthLogin.Vo;
import lombok.Setter;
import java.util.Map;

/**
 * @Description: Cas认证用户信息
 */
@Getter
@Setter
public class CasUserInfo {
    /** 用户名 */
    private String userName;
    /** 用户 */
    private String userAccount;
    /** 用户信息 */
    private Map<String, Object> attributes;
}
  • 认证通过后返回数据,获取用户信息的工具类封装
package com.wxxssf.CasAuthLogin.casUtils;

import com.wxxssf.CasAuthLogin.Vo.CasUserInfo;
import lombok.extern.slf4j.Slf4j;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;

import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
import java.util.Map;

/**
 * @Description: Cas认证工具类
 */

@Slf4j
public class CasUtil {

    /**
     * cas client 默认的session key  _const_cas_assertion_
     */
    public final static String CAS = "_const_cas_assertion_";

    /**
     * 封装CasUserInfo
     */
    public static CasUserInfo getCasUserInfoFromCas(HttpServletRequest request) {
        System.out.println("request.toString() = " + request.toString());
        Object object = request.getSession().getAttribute(CAS);
        if (null == object) {
            return null;
        }
        Assertion assertion = (Assertion) object;
        return buildCasUserInfoByCas(assertion);
    }

    /**
     * 构建CasUserInfo
     */
    private static CasUserInfo buildCasUserInfoByCas(Assertion assertion) {
        if (null == assertion) {
            log.error(" Cas没有获取到用户 ");
            return null;
        }
        CasUserInfo casUserInfo = new CasUserInfo();
        String userName = assertion.getPrincipal().getName();
        log.info(" cas对接登录用户= " + userName);
        log.info("用户消息:"+assertion.getPrincipal().toString());
        casUserInfo.setUserAccount(userName);
        //获取属性值
        Map<String, Object> attributes = assertion.getPrincipal().getAttributes();
        Object name = attributes.get("cn");
        casUserInfo.setUserName(name == null ? userName : name.toString());
        casUserInfo.setAttributes(attributes);
        return casUserInfo;
    }

    /**
     * @Description new:获取用户信息全部数据展示示例:
     *
     * userInfo = {
     * "userAccount":"20220037",
     * "attributes":{"isFromNewLogin":"false",
     *      "authenticationDate":"2023-07-27T09:15:46.799+08:00[GMT+08:00]",
     *      "loginType":"1",
     *      "successfulAuthenticationHandlers":"com.wisedu.minos.config.login.RememberMeUsernamePasswordHandler",
     *      "cn":"xxx",
     *      "userName":"xxx",
     *      "samlAuthenticationStatementAuthMethod":"urn:oasis:names:tc:SAML:1.0:am:unspecified",
     *      "credentialType":"MyRememberMeCaptchaCredential",
     *      "uid":"20220037",
     *      "authenticationMethod":"com.wisedu.minos.config.login.RememberMeUsernamePasswordHandler",
     *      "longTermAuthenticationRequestTokenUsed":"false",
     *      "containerId":"ou=1000001,ou=People",
     *      "cllt":"userNameLogin",
     *      "dllt":"generalLogin"},
     * "userName":"xxx"}
    */
    public static CasUserInfo getUserInfo(HttpServletRequest request){
        Principal userPrincipal = request.getUserPrincipal();
        CasUserInfo casUserInfo = new CasUserInfo();
        if (userPrincipal != null && userPrincipal instanceof AttributePrincipal){
            AttributePrincipal attributePrincipal = (AttributePrincipal) userPrincipal;
            //获取用户信息中公开的Attributes部分
            Map<String, Object> map = attributePrincipal.getAttributes();
            String cn = (String)map.get("cn");
            String user_name = (String)map.get("userName");
            casUserInfo.setUserAccount(userPrincipal.getName());
            casUserInfo.setAttributes(map);
            casUserInfo.setUserName(cn == null ? userPrincipal.getName() : cn );
        }
        return casUserInfo;
    }
    // TODO: 2023-07-24   AttributePrincipal类和Assertion  区别~~!!
}
  • 单点登录接口
/**
   * cas 单点登录
   *
   * @param request 请求头(姓名+身份证号)
   * @param ticket cas 票据
   * @return
   */
  @GetMapping(value = "/api/loginByNameAndCardNo")
  @ApiOperation("cas单点登录")
  public String loginByNameAndCardNo(HttpServletRequest request) {
    CasUserInfo userInfo = CasUtil.getCasUserInfoFromCas(request);
    log.info("userInfo = " + JSONObject.toJSON(userInfo));
    String url = "main";
    MadStudent student = new MadStudent();
    student.setName(userInfo.getAttributes().get("Name").toString());
    student.setCardNo(userInfo.getAttributes().get("IdCard").toString());
  	// 登录用户校验 
  	// xxxxx
  	// 用户数据为 true
  	// 跳转页面
    return "url";
    } else {
      return "redirect:" + casUrl;
    }
  }

四、其他方式实现的认证案例记录(略):

:该部分内容仅仅为自己记录使用,可跳过!


4.1 基于深信服IDTrust 实现Cas认证

  • 方式一:基于JDK的java.net包中已经提供了访问Http协议的 HttpURLConnection 类实现
<%@ page import="java.net.URL" %>
<%@ page import="java.net.URLConnection" %>
<%@ page import="javax.net.ssl.HttpsURLConnection" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.net.HttpURLConnection" %>
<%@ page import="javax.net.ssl.SSLSession" %>
<%@ page import="javax.net.ssl.HostnameVerifier" %>
<%@ page import="java.net.URLEncoder" %><%--
  Created by IntelliJ IDEA.
  User: AD
  To change this template use File | Settings | File Templates.
--%>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!
    private static void trustAllHttpsCertificates() throws Exception {
        javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];
        javax.net.ssl.TrustManager tm = new miTM();
        trustAllCerts[0] = tm;
        javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext
                .getInstance("SSL");
        sc.init(null, trustAllCerts, null);
        javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc
                .getSocketFactory());
    }

    static class miTM implements javax.net.ssl.TrustManager,
            javax.net.ssl.X509TrustManager {
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return null;
        }
        public boolean isServerTrusted(
                java.security.cert.X509Certificate[] certs) {
            return true;
        }
        public boolean isClientTrusted(
                java.security.cert.X509Certificate[] certs) {
            return true;
        }
        public void checkServerTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
                throws java.security.cert.CertificateException {
            return;
        }
        public void checkClientTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
                throws java.security.cert.CertificateException {
            return;
        }
    }
%>
<%

    String infoMessage =null;
    HostnameVerifier hv = new HostnameVerifier() {
        public boolean verify(String urlHostName, SSLSession session) {
            String infoMessage ="Warning: URL Host: " + urlHostName + " vs. " + session.getPeerHost();
            System.out.println("info = " + infoMessage);
            return true;
        }
    };
    trustAllHttpsCertificates();
    HttpsURLConnection.setDefaultHostnameVerifier(hv);
	String service = "http://xxxxx:xx/casLoginTicket.jsp"; //回调地址
    String encode = URLEncoder.encode(service); 
    URL url = new URL("https://xxxx/cas/login?service="+encode);  //认证地址
    HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
    httpsURLConnection.setDoInput(true);
    httpsURLConnection.setRequestMethod("GET");
    httpsURLConnection.setRequestProperty("Content-Type","application/json;charset=utf-8");
    System.out.println("准备执行李连接!!");
    httpsURLConnection.connect();
    InputStream inputStream = httpsURLConnection.getInputStream();
    byte[] buff = new byte[1024];
    int len = -1;
    StringBuffer stringBuffer = new StringBuffer();
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    while((len = inputStream.read(buff)) != -1){
        stringBuffer.append(new String(buff,0,len,"utf-8"));
        byteArrayOutputStream.write(buff,0,len);
    }
    System.out.println("stringBuffer = " + stringBuffer);
    System.out.println("byteArrayOutputStream.toString() = " + byteArrayOutputStream.toString());
    //关闭资源
    byteArrayOutputStream.close();
    inputStream.close();
    httpsURLConnection.disconnect();
    //response.getWriter().write( stringBuffer.toString());
%>
<html>
<head>
    <title>加载中请稍等....</title>
</head>
<script>
    console.log("加载中....")
</script>
<body>
<h3><%=encode%></h3>
<h3><%=stringBuffer%></h3>
</body>
<script>
</script>
</html>
  • 方式二:基于cn.hutool 的http 包中已经提供了访问Http协议的 Hutool-http 类实现

<%@ page import="cn.hutool.http.HttpRequest" %>
<%@ page import="cn.hutool.http.HttpResponse" %>
<%@ page import="java.net.URLEncoder" %>
<%--
  Created by IntelliJ IDEA.
  User: AD
  To change this template use File | Settings | File Templates.
--%>
<%
    System.out.println("进入集成跳转页面!!");
    String service = "http://xxxx:8888/casLoginTicket.jsp";  //回调地址
    String encode = URLEncoder.encode(service);

    HttpResponse result = HttpRequest
            .get("https://xxxx/cas/login?service="+encode) //CAS认证地址
            .header("Content-Type", "application/json;charset=UTF-8")  
            .timeout(60 * 1000)
            .execute();
    int status = result.getStatus();
    System.out.println("status = " + status);
    String body = result.body();
    System.out.println("body = " + body);
    response.getWriter().write(body);
%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>加载中请稍等....</title>
</head>
<script>
    console.log("加载中....")
</script>
<body>
<h3><%=status%></h3>
<h3><%=body%></h3>
</body>
<script>
    console.log("请求响应数据展示:<%=status%>")
</script>
</html>



4.2 基于职教云平台的统一认证案例

  • 统一认证访问主路径 jsp文件 (该平台的认证是需要提前申请入驻,然后绑定对应回调地址和认证地址之后才能进行认证)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%--<%@ page import="org.apache.commons.lang.StringUtils"%>--%>
<%--<%@ page import="com.ibatis.sqlmap.client.SqlMapClient,com.ufgov.midas.yy.util.*,java.util.*"%>--%>
<%--<%@ page import="com.ufgov.midas.qx.util.*,com.ufgov.midas.qx.sqlmap.*,java.sql.*,com.ufgov.midas.pt.common.DAOFactory" %>--%>
<%--<%@ page import="org.ly.uap.client.authentication.AttributePrincipal"%>--%>
<%@ page import="sun.misc.*"%>
<%--应用访问主路径jsp--%>
<%
	//认证地址:
    String url ="https://xxxxxxx/provider/oauth2/authorize?" +
            "response_type=code" +
            "&client_id=XXXX"+
            "&redirect_uri=http://XXXXX:8888/ASXYSSOLoginSuccessNEW.jsp"+
            "&scope=openid";
    //response.sendRedirect(url);
%>
<head>
    <meta charset="utf-8">
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="expires" content="0">
    <link href="css/style.css" rel="stylesheet" type="text/css">
    <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
    <title>SSO_RZ_Welcome!!!</title>
    <style>
        .button {
           ......
        }
        .button2:hover {
            box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19);
        }
        div{
          ....
        }
    </style>
    <script LANGUAGE="JavaScript">
        var url ="<%=url%>"
        // var urlQd = "https://idaastest.gzzjzhy.com/provider/oauth2/authorize?" +
        //     "response_type=code" +
        //     "&client_id=1688831259003981824"+
        //     "&redirect_uri=http://111.85.31.2:8888/ASXYSSOLoginSuccess.jsp"+
        //     "&scope=????";
        function getAuthCode(){
            // alert("跳转统一认证URL="+url)
            window.location.href=url;
        }
    </script>
</head>
<body style="background: #fff;">
<div>
</div>
</body>
</html>
<script language="javascript">getAuthCode();</script>



4.3 基于钉钉的集成认证登录

  • 首先是认证访问页面代码
<%@ page contentType="text/html; charset=GBK" language="java" import="java.sql.*" errorPage="" %>

<%@ page import="com.alibaba.fastjson.JSONObject"%> 
<%@ page import="com.dingtalk.api.DefaultDingTalkClient"%> 
<%@ page import="com.dingtalk.api.DingTalkClient"%> 
<%@ page import="com.dingtalk.api.request.OapiGettokenRequest"%> 
<%@ page import="com.dingtalk.api.request.OapiUserGetuserinfoRequest"%> 
<%@ page import="com.dingtalk.api.response.OapiGettokenResponse"%> 
<%@ page import="com.dingtalk.api.request.OapiV2UserGetRequest"%> 
<%@ page import="com.dingtalk.api.response.OapiUserGetuserinfoResponse"%> 
<%@ page import="com.taobao.api.ApiException"%> 
<%@ page import="com.dingtalk.api.response.OapiV2UserGetResponse"%> 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html> 
<head>
<%@page import="java.util.Date"%>
<%@page import="java.text.SimpleDateFormat"%>
	<%@ page import="java.util.List" %>
	<%@ page import="java.util.Map" %>
	<%@ page import="javax.lang.model.element.NestingKind" %>
	<script language="javascript" src="./des.js"></script>
<meta http-equiv="Content-Type" content="text/html; charset=GBK" />
<title>xxxx</title>
<style>
html,body {。。。。。}
#info{}
#down-info{。。。。。}
</style>
<SCRIPT LANGUAGE=javascript>
{
	<%
	if(ckey!=""){
	  //com.ufgov.midas.pt.service.changeLogin cl = new com.ufgov.midas.pt.service.changeLogin();
	  //String skey = cl.MD5(yonghu + sj + ip);
	  String skey = (yonghu + sj + ip);
	  Date now=new Date();
	  SimpleDateFormat f=new SimpleDateFormat("yyyyMMdd");
	  String DateStr = f.format(now);
	  skey = skey.toLowerCase();
	  ckey = ckey.toLowerCase();
	  if("".equals(sj)){
		  sj = DateStr;
	  }

	  String cood = request.getParameter("msg");
	  System.out.println("获取到的cood值为=" + cood);
//与钉钉进行交互
	  if(yonghu == "" || yonghu == null){
	   // 获取access_token,注意正式代码要有异常流处理
       DingTalkClient client = new DefaultDingTalkClient("https://xxxxx/gettoken");
       OapiGettokenRequest request1 = new OapiGettokenRequest();
       request1.setAppkey("dingcz1ruaglmqxxxx");
       request1.setAppsecret("nVgvFHLiYiHxxxGmcK-Widi_xp29vIxxxXu71mCkOtxxxxs");
       request1.setHttpMethod("GET");
       OapiGettokenResponse response1 = null;
       try {
           response1 = client.execute(request1);
       } catch (ApiException e) {
           throw new RuntimeException(e);
       }
	   System.out.println("获取用户token:response1.getBody() = " + response1.getBody());
       JSONObject jsonObject =JSONObject.parseObject(response1.getBody());
       String access_token = (String) jsonObject.get("access_token");

//获取用户id
       DingTalkClient client2 = new DefaultDingTalkClient("https://xxxxx/user/getuserinfo");
       OapiUserGetuserinfoRequest request2 = new OapiUserGetuserinfoRequest();
       //todo--SSO单点登录授权码
       String requestAuthCode = cood;
       request2.setCode(requestAuthCode);
       request2.setHttpMethod("GET");
       OapiUserGetuserinfoResponse response2= null;
       try {
           response2 = client2.execute(request2, access_token);
           System.out.println("获取用户id信息info:response2.getBody() = " + response2.getBody());
       } catch (ApiException e) {
        // TODO Auto-generated catch block
            e.printStackTrace();
       }
       // 查询得到当前用户的userId
       // 获得到userId之后应用应该处理应用自身的登录会话管理(session),避免后续的业务交互(前端到应用服务端)每次都要重新获取用户身份,提升用户体验
       String userId = response2.getUserid();
	   System.out.println("userId = " + userId);
//获取用户信息
       String userInfo = null;
       try {
           DingTalkClient client3 = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/get");
           OapiV2UserGetRequest req = new OapiV2UserGetRequest();
//           req.setUserid("001");
           req.setUserid(userId);
           req.setLanguage("zh_CN");
           OapiV2UserGetResponse rsp = client3.execute(req, access_token);
           System.out.println("获取用户资料info:rsp.getBody() = " + rsp.getBody());
           userInfo = rsp.getBody();
       } catch (ApiException e) {
           e.printStackTrace();
       //获取userid
		   System.out.println("JSONObject.parseObject(userInfo) = " + JSONObject.parseObject(userInfo));
		   JSONObject result  = (JSONObject) JSONObject.parseObject(userInfo).get("result");
		   System.out.println(result.get("userid"));
		   //yonghu = (String) result.get("userid");


	   // 获取job_number
	   	JSONObject result2  = (JSONObject) JSONObject.parseObject(userInfo).get("result");
		  if (result2 == null){
			  System.out.println("不存在该用户");
		  }else {
			   //yonghu = (String) result2.get("job_number");
			   //工号
			   String number =(String) result2.get("job_number");
			   System.out.println("result2.get(\"job_number\") = " +number );
			   yonghu = number;
		  }
		  //role_list:id
		  JSONObject result3 = (JSONObject) JSONObject.parseObject(userInfo).get("result");
		  List roleList = (List) result3.get("role_list");
		  if (roleList.size()>0){
			  Map map =(Map) roleList.get(0);
			  System.out.println("map="+map);
			  //yonghu = map.get("id").toString();
		  }
	  }

	  %>

	  // if (code==""||code==null){
	  //   alert("免登授权码获取失败!");//免登授权码获取失败
 	 //    closeWin();
 	 //    return false;
	  // } else
	  if("<%=yonghu%>"=="" || "<%=yonghu%>"==null){
		alert("用户信息错误,请重新登录!");//可能是key值不正确
		alert("yonghu="+"<%=yonghu%>");
		closeWin();
		return false;
	 }else{
		alert("yonghu="+"<%=yonghu%>")
		alert("验证成功自动登录!!")
		alert("用户登录名正确!")
	 }
	  <%
	}
	%>
	var uid="<%=yonghu%>";
  	doLogin(productName,uid);
}
</SCRIPT> 
</head>
<body>
	<table border=1 width=100% height=100%><tr><td align="center" valign="middle">
		<div id="u8check">
			<div id="info">	客户端程序, 请稍候...</div>
		</div>
	</td></tr></table>
</body>
</html><script language="javascript">doCallU8();</script>



4.4 基于Cas服务的集成示例

<%@ page language="java" pageEncoding="UTF-8"%>
<%@page import="api.ResponseWithHttpClient"%>
<%@page import="java.util.regex.*"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>cas</title>
</head>

<body>
	<%
		//根据现场的实际认证地址修改(一般只修改域名cas.leaf.com就行)
		String casHost = "https://认证域名/lyuapServer/";
		String tkt = request.getParameter("ticket");
		
		//当前的访问路径的协议及地址和端口号[== 重要部分代码 ==]
		String service = request.getScheme() + "://" + request.getServerName();
		int port = request.getServerPort();
		if (port!=80 && port!=443) {//默认端口,不加在跳转路径下
			service += ":" + request.getServerPort();
		}
		
		service += request.getRequestURI();
		//判断票据是否为空,为空则未经过认证,需重新定向到认证,否者则认证成功
		if (tkt == null || tkt.length() == 0) {
			String redir = casHost + "login?service=" + service;
			response.sendRedirect(redir);
			return;
		} else {

			//校验ticket,获取用户信息
			String validateurl = casHost + "serviceValidate?" + "ticket=" + tkt + "&" + "service=" + service;
			System.out.println("-------url1-----" + validateurl);

			//发送请求[这里引用了 api jar包,这部分引用需要自己调整封装请求、发送请求]
			String data = ResponseWithHttpClient.getResponseWithHttpClient(validateurl, "UTF-8");
			
			if (data != null) {
				String username = null;
				Pattern pt = Pattern.compile("<cas:user>(.*)</cas:user>");
				Matcher match = pt.matcher(data);
				while (match.find()) {
					username = match.group(1);
				}
				if (username == null) {
					out.println("账号不对");
					return;
				}
				out.println("您好!" + username);
			}
		}
	%>
</body>
</html>



4.5 CAS认证中心集成校园某系统(yycw)示例

*仅用与自己备份记录,回顾笔记!!

  • 通过 Springboot 构架集成项目war 包:
  1. 当前的访问路径的协议及地址和端口号
  2. 通过请求头判断是 app 还是 web
  3. 封装请求,向CAS 服务端获取用户信息
  4. 通过 Pattern 和 Matcher 解析 CAS 服务中心响应的用户信息,然后封装重定向首页地址

*仅用与自己备份记录,回顾笔记!!
PS:示例代码中的 加密工具类略(密)! 以及 JSP文件版本略(密)!

package com.example.gsxy_sso_u8cloud.controller;

import com.example.gsxy_sso_u8cloud.util.Base64Utils;
import com.example.gsxy_sso_u8cloud.util.MD5Utils;
import com.example.gsxy_sso_u8cloud.util.RSAUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.lang.System.out;

/**
 * @ClassName : SSOController
 * @Description : 单点集成Controller层
 * @Author : AD
 */
@RestController
@RequestMapping()
public class SSOController {
    private static final Logger logger = LoggerFactory.getLogger(SSOController.class);

 /**
     * Description: CAS 认证Demo 入口地址
     *
     * @param request
     * @param response
     * @return java.lang.String
    */
    // http://172.16.104.158:9815/gsxy/ssoLogin?wbdm=200100
    @GetMapping("/ssoLogin")
    public String  clientLoginInfo(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // response.setContentType("text/html;charset=UTF-8");

        /* 认证中心地址 */
        String casHost = "https://xx.xx.edu.cn/maxkey";
        /* 产品id */
        String Client_id = "xxx门户中注册产品后的唯一标识xxx";
        /* 登录地址 */
        String loginUrl = casHost +"/authz/cas/"+Client_id;
        /* 退出地址 */
        String logoutUrl = casHost +"/logout?reLoginUrl=login";
        /* 校验地址 */
        String loginValidateUrl = casHost +"/authz/cas/serviceValidate";

        /** 当前的访问路径的协议及地址和端口号 */
        String service = request.getScheme() + "://" + request.getServerName();
        int port = request.getServerPort();
        if (port!=80 && port!=443) {
            //默认端口,不加在跳转路径下
            service += ":" + request.getServerPort();
        }
        // service += request.getRequestURI();

        /** 获取请求数据 */
        Map<String, String[]> parameterMap = request.getParameterMap();
        String paramStr = parameterMap.toString();

        /** 获取请求头中数据 */
        StringBuilder heardStr = new StringBuilder();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()){
            String headerName = headerNames.nextElement();
            String header = request.getHeader(headerName);
            heardStr.append(headerName+"="+header+"  ;");
        }
        String heardStr1 = heardStr.toString();

        /** 判断是同浏览器登录还是其它方式(app/H5/web) */
        String userAgent = request.getHeader("User-Agent");
        //这个请求头指示来自哪个页面,H5页面通常会包含具体的网页地址,而APP可能不会有或是特定格式的链接;
        String referer = request.getHeader("Referer");
        String loginType = "";
        if (userAgent != null) {
            if (userAgent.contains("Chrome") || userAgent.contains("Firefox") || userAgent.contains("Safari")) {
                loginType= "web";
            } else if (userAgent.contains("Mobile")) {
                loginType= "H5";
            } else if (userAgent.contains("YourAppName")) { // 替换为你的App名称
                loginType= "app";
            }
        }

        //判断票据是否为空,为空则未经过认证,需重新定向到认证,否者则认证成功
        String tkt = request.getParameter("ticket");
        if (tkt == null || tkt.length() == 0) {
            response.sendRedirect(loginUrl);
            return "传入票据为null,重新登录!";
        } else {
            //检验 ticket,获取用户信息
            String validateUrl = loginValidateUrl + "?ticket=" + tkt + "&" + "service=" + service;
            out.println("-------url1-----" + validateUrl);

            //发送请求
            BufferedReader br = null;
            String resultData = "";
            try{
                //创建URL
                URL realUrl = new URL(validateUrl);
                //打开连接
                HttpURLConnection connection =(HttpURLConnection)realUrl.openConnection();
                connection.setRequestMethod("GET");
                connection.setRequestProperty("Accept", "*/*");
                // connection.setRequestProperty("Accept", "application/xml");
                // connection.setRequestProperty("connection", "Keep-Alive");
                // connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
                br = new BufferedReader(new InputStreamReader(
                        connection.getInputStream()));
                String line;
                while ((line = br.readLine()) != null) {
                    resultData += line;
                }
            }catch(Exception e){
                return e.getMessage();
            }   finally {
                try {
                    if (br != null) {
                        br.close();
                    }
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
            String message = "跳转失败!";
            String userid = null;
            String pcUtl = null;
            String appUrl = null;
            if (resultData != null && resultData.length() > 0) {
                Pattern pt = Pattern.compile("<cas:user>(.*)</cas:user>");
                Matcher match = pt.matcher(resultData);
                while (match.find()) {
                    /**
                     * 踩坑bug:正则表达式从标签中获取数据
                     * group(0) 将返回整个匹配的内容,例如:"<cas:user>200161</cas:user>"。
                     * group(1) 将返回被 <cas:user> 标签包围的内容,即实际的用户 ID,例如 "200161"。
                     * */
                    // userid = match.group(0).trim();
                    userid = match.group(1).trim();
                }
                if (userid == null) {
                    out.println("获取用户账户失败!");
                    message   = "从CAS认证中心获取用户信息(<cas:user>)为空!";
                }else {
                    pcUtl = createSSOUrl(userid, service, "SYS");
                    appUrl = createSSOUrl(userid,service,"U8APP");
                    if ("web".equalsIgnoreCase(loginType)){
                        response.sendRedirect(pcUtl);
                    }else {
                        response.sendRedirect(appUrl);
                    }
                }
            }
            return "message="+message+"  \t \n" +
                    " validateUrl="+validateUrl+" \t \n" +
                    "pcUtl==:"+pcUtl  + " \t \n "+
                    "appUrl==:"+appUrl + " \t \n "+
                    "loginType = "+loginType +" \t \n"+
                    "userid===" + userid;
        }
    }
    
    

    /**
     * Description: xxxxxxyycw集成方案
     *
     * @param wbdm
     * @param service
     * @param type SYS / U8APP
     * @return java.lang.String
     *  * 1.格式: http://127.0.0.1:8188/singleLogin?yonghuKey=XX&produceName=SYS&route=taskcenter&fullscreen=0
     *  * 2.参数说明:
     *  * yonghukey: 用户信息
     *  * porduceName: SYS(代表 PC 端)、U8APP(代表 U8 产品中心的 APP 产品)
     *  * route: 不传默认是主界面,taskcenter 为任务中心,其他需联系 U8 产品中心做专项开发
     *  * fullscreen: 是否全屏 0-是,1-否
    */
    public String createSSOUrl(String wbdm, String service,String type){

        String casPublicKey="xxx公钥手动打码xxx!!!";
        String casMd5Key="xxx密钥手动打码xxx!!!";

        String url = "";
        try {
            String md5 = MD5Utils.MD5Encoder(casMd5Key + wbdm, "UTF-8");
            byte[] data = (wbdm + md5).getBytes();
            byte[] encodedData = RSAUtils.encryptByPublicKey(data, casPublicKey);
            String yonghuKey = URLEncoder.encode(Base64Utils.encode(encodedData), "UTF-8");
            url = service+"/singleLogin?yonghuKey="+yonghuKey+"&produceName="+type+"&fullscreen=0";
            // response.sendRedirect(pcUtl);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return  url;
    }


    /**
     * Description: 略! CAS认证Demo(重定向地址封装) 测试接口! 
     *
     * http://127.0.0.1:8080/casLogin?wbdm=20140010
     * http://127.0.0.1:8188/gsxy/casLogin?wbdm=20140010 
     * http://172.16.104.158:9815/gsxy/casLogin?wbdm=200100
     *
     * @param request
     * @param response
     * @return java.lang.String
    */
    //@GetMapping("/casLogin")
    public String ssoLogin(@RequestParam("wbdm") String wbdm, HttpServletRequest request, HttpServletResponse response){
        String service = request.getScheme() + "://"+ request.getServerName();
        int port = request.getServerPort();
        if (port != 80 && port != 443){
            service += ":" + request.getServerPort();
            // service += ":8188";
        }
        // service += request.getRequestURI();

        out.println(" 传递过来获取到的参数 wbdm="+wbdm);
        out.println(" 请求地址信息 service="+service);
        logger.info("传递过来获取到的参数 wbdm={} service={}", wbdm, service);
        String pcUtl = createSSOUrl(wbdm, service, "SYS");
        return "redirect:"+pcUtl;
    }
}

*仅用与自己备份记录,回顾笔记!!

PS:示例代码中的 加密工具类略(密)! 以及 JSP文件版本略(密)!

4.5 基于Cas服务集成(zyzy)示例

采用了 HttpURLConnection 实现集成跳转,其中包含了,忽略主机名验证,和 禁用SSL校验 的示例代码备份!

  1. 主机名校验(Hostname Verification)
  • 定义:主机名校验是用来确保所连接的服务器的证书中包含的主机名与实际请求的主机名匹配。这是确保连接安全性的一种手段,防止中间人攻击(Man-in-the-Middle Attack)。

  • 影响:当你使用 HostnameVerifier 并直接返回 true 时,实际上是在告诉 Java 忽略对服务器证书中主机名的检查。这意味着,无论证书中的主机名是什么,你都将接受连接,这可能使得你连接到一个伪造服务器而不自知。

  1. SSL 校验(SSL Certification Verification)
  • 定义:SSL 校验是检查服务器提供的 SSL 证书是否有效,包括证书是否由受信任的 CA 签发、证书是否过期、证书是否与请求的主机名匹配等。
  • 影响:如果禁用 SSL 校验(通过不信任所有证书),任何服务器的证书都将被接受,包括自签名证书或伪造证书。这可能导致数据传输的安全性降低,因为连接在加密基础上建立,但无法验证其身份。

相同的异常:在实践中,如果 SSL 验证失败,可能会抛出类似的异常(如 SSLHandshakeException)。这可能让人感觉这两个机制产生的异常没有显著区别,但实际上,错误的来源不同。 忽略主机名校验更专注于请求的目标主机与证书中的主机名匹配,而禁用 SSL 校验则是完全放弃对 SSL 证书的验证。

这两种做法都带来了潜在的风险,建议在开发过程中谨慎使用它们,并在生产或实时环境中确保进行适当的安全检查。

  • 代码示例内容:
<%@ page language="java" pageEncoding="UTF-8"%>
<%@ page import="java.net.URL" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.util.Date" %>
<%@ page import="java.text.SimpleDateFormat" %>
<%@ page import="javax.net.ssl.*" %>
<%@ page import="java.security.cert.X509Certificate" %>


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
  <title>cas</title>
</head>

<body>
<%
  //根据现场的实际认证地址修改(一般只修改域名cas.leaf.com就行)
  String casHost = "https://xx.xx.edu.cn:0000/xxxx/";
  String tkt = request.getParameter("ticket");
  //当前的访问路径的协议及地址和端口号
  String service = request.getScheme() + "://" + request.getServerName();
  int port = request.getServerPort();
  if (port!=80 && port!=443) {//默认端口,不加在跳转路径下
    service += ":" + request.getServerPort();
  }
  service += request.getRequestURI();
  //判断票据是否为空,为空则未经过认证,需重新定向到认证,否者则认证成功
  if (tkt == null || tkt.length() == 0) {
    String redir = casHost + "login?service=" + service;
    //response.getWriter().printf("redir="+redir);
    response.sendRedirect(redir);
    return;
  } else {
    /** 解决:TLS 版本不匹配问题 */
    //System.setProperty("https.protocols", "TLSv1.2");

    String validateurl = casHost + "serviceValidate?" + "service=" + service  + "&" + "ticket=" + tkt;
    //String validateurlNew = URLEncoder.encode(validateurl, "UTF-8");
    //response.getWriter().write("validateurl="+validateurl);

    String data = null;
    String data2 = null;
    try{

      /**
       *  =============================================
       *  HttpURLConnection 中 禁用SSL校验(仅用于开发测试)
       *    如果你尝试通过 HttpsURLConnection 访问一个 HTTPS 地址而没有导入相应的 SSL/TLS 证书,通常会导致连接失败
       * */
      TrustManager[] trustAllCerts = new TrustManager[]{
              new X509TrustManager() {
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                  return null;
                }
                public void checkClientTrusted(X509Certificate[] certs, String authType) {}
                public void checkServerTrusted(X509Certificate[] certs, String authType) {}
              }
      };
      SSLContext sc = SSLContext.getInstance("SSL");
      sc.init(null, trustAllCerts, new java.security.SecureRandom());
      HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
      /** ============================================= */

      //1.创建URL对象
      URL realUrl = new URL(validateurl);
      //2.通过URL对象获取能够访问Http协议的类HttpURLConnection
      HttpsURLConnection httpsURLConnection = (HttpsURLConnection) realUrl.openConnection();
      //3.封装请求相关参数
      //请求方式
      httpsURLConnection.setRequestMethod("GET");
      // 自动跟随重定向
      httpsURLConnection.setInstanceFollowRedirects(true);

      httpsURLConnection.setRequestProperty("User-Agent", "Mozilla/5.0");
      httpsURLConnection.setRequestProperty("Accept", "application/xml"); // 或其他相应格式
      httpsURLConnection.setRequestProperty("Cookie", request.getSession().getId());
      // httpsURLConnection.setRequestProperty("service",service);
      // httpsURLConnection.setRequestProperty("ticket",tkt);
      // httpsURLConnection.setRequestProperty("Cookie",request.getSession().getId());
      //httpsURLConnection.setRequestProperty("Accept", "application/xml");
      //httpsURLConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
      //设置请求头
      //httpsURLConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0..6261.95 Safari/537.36");
      //httpsURLConnection.setRequestProperty("User-Agent", "Mozilla/5.0");
      // 手动设置 Host 请求头(通常不需要,系统会自动设置)
      //httpsURLConnection.setRequestProperty("Host", "example.com");
      //httpsURLConnection.setRequestProperty("version", "1.0");
      //httpsURLConnection.setConnectTimeout(6*1000); //设置超时时间
      //httpsURLConnection.setReadTimeout(6*1000);

      /**
       * =============================================
       * 忽略主机名校验:自定义 HostnameVerifier
       */
      HostnameVerifier allHostsValid = new HostnameVerifier() {
          public boolean verify(String hostname, SSLSession session) {
              return true; // 不进行主机名验证
          }
      };
      httpsURLConnection.setHostnameVerifier(allHostsValid);
      /** ============================================= */

      //4.建立连接
      httpsURLConnection.connect();

      //5.得到响应数据(流对象)
      InputStream inputStream = httpsURLConnection.getInputStream();
      byte[]  buff = new byte[1024]; //创建缓存大小
      int len = -1; //每次读取到的内容长度
      /*获取io流中的数据方式1*/
      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
      /*获取io流中的数据方式2*/
      StringBuffer result = new StringBuffer();
      while ((len = inputStream.read(buff)) != -1){  //当没有内容可读取后为-1则推出
        result.append(new String(buff,0,len));
        byteArrayOutputStream.write(buff,0,len);
      }
      System.out.println("result = " + result);
      data = byteArrayOutputStream.toString();
      //关闭流资源
      byteArrayOutputStream.close();
      inputStream.close();
      httpsURLConnection.disconnect();
      //response.getWriter().printf("11111输出数据 result ="+result);
      data2 = result.toString();
    }catch(Exception e){
      e.printStackTrace();
      response.getWriter().write(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "HttpURLConnection  发送请求异常:"+e.getMessage() +"  \t\n 请求地址:"+validateurl);
    }
    response.getWriter().write(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "响应请求  222 data="+data+"   333 result="+data2+"   url="+validateurl+"  sessionId="+request.getSession().getId());

//        if (data != null &&  data.length() > 0){
//            return;
//        }else {
//            response.getWriter().printf("响应请求 失败地址  url="+validateurl);
//            return;
//        }

			/*
			if (data != null &&  data.length() > 0) {
				Pattern pt = Pattern.compile("<cas:user>(.*)</cas:user>");
				Matcher match = pt.matcher(data);
				while (match.find()) {
					username = match.group(1);
				}
				if (username == null) {
					//out.println("账号不存在!");
					response.getWriter().printf("账号不存在!");
					return;
				}
				response.getWriter().printf("您好!" + username);
				//request.getSession().setAttribute("UID",username);
				//response.sendRedirect("http://11.11.11:1111/clientapp/index_jm.jsp");
			}
			*/
  }
%>
</body>
</html>

[完结!]

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值