Bootstrap

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>

;