cas登录流程解析及springboot集成cas
作者:mmseoamin日期:2023-12-27

单点登录基本流程分为三种情况

  1. 首次访问应用A,cas未登录:浏览器请求API被cas过滤器(application配置项配置内容)拦截,重定向到cas认证中心登录判断cas是否登录,若cas未登录重定向到cas登录页面。在cas登录页面输入账号密码登录成功后重定向到service参数对应的地址,并通过get的方式挟带了一个ticket,同时在Cookie中设置CASTGC,该Cookie是访问service的cookie,只有访问该地址才会携带这个cookie。向Cookie中添加CASTGC的目的是为了下次访问service_url时,浏览器请求时携带TGC参数,服务器根据该TGC查找对应的TGT,从而判断用户是否登录过了,是否需要展示登录页面。TGT与TGC的关系就像SESSION与Cookie中SESSIONID的关系。后端接收到该ticket会进行验证有效性,校验通过后展示用户相关内容到浏览器上。(首次访问流程结束)
  2. 再次访问应用A,cas已经登录:用户发起请求经过cas-client,也就是过滤器,因为第一次访问成功之后会在session中记录用户信息,因此这里直接就通过了,不用验证了。浏览器返回正常资源。
  3. 正在使用应用A且cas已经登录,想去访问应用B(第一次访问):用户发起访问请求到应用B,由于是第一次访问该应用,会重定向到cas认证中心登录,发现cas已经登录(不会展示cas登录页面),认证中心重定向到service参数对应的地址,并通过get的方式挟带了一个ticket,后端接收到该ticket会进行验证有效性,校验通过后展示用户相关内容到浏览器上。

cas认证流程

cas登录流程解析及springboot集成cas,第1张

  1.  访问服务:由于CAS client和WEB应用部署在一起,当用户访问WEB应用时,CAS client就会处理请求
  2. 定向认证:CAS client客户端校验HTTP请求中是否包含ST和TGT,如果没有则会重定向到CAS server地址进行用户认证
  3. 用户认证:用户通过浏览器填写用户信息,提交给CAS Server认证
  4. 发放票据:CAS Server校验过用户信息后,为CAS client发放ST,并在浏览器cookie中设置TGC,下次访问CAS Server时会根据TGC和TGT验证,判断是否已经登录
  5. 验证票据:CAS Client拿到ST后,再次请求CAS Server验证ST合法性,验证通过后允许客户端访问
  6. 传输用户信息:CAS Server校验过ST后,传输用户信息给CAS client

相关名词解释 

AS

Authentication Service:认证服务,发放TGT

KDC

Key Distribution Center:密钥发放中心

TGS

Ticket-Granting Service:票据授权服务,索取TGT,发放ST

TGC

ticket-granting cookie:授权的票据证明,由CAS Server通过SSL方式发送给终端用户。该值存在Cookie中,根据TGC可以找到TGT。

TGT

Ticket Granting tieckt:俗称大令牌,或者票根,由KDC和AS发放,获取该票据后,可直接申请其他服务票据ST,不需要提供身份认证信息

ST

Service Ticket:服务票据,由KDC的TGS发放,ST是访问server内部的令牌

CAS Client客户端搭建(与项目代码在一起)

1.先在我们的SpringBoot项目的pom.xml文件中,引入对应的依赖,这边我直接贴上我客户端pom.xml里面的代码
  net.unicon.cas 
 cas-client-autoconfig-support 
 2.3.0-GA 
2.然后我们要在这里加上对应配置文件代码,主要就是指明server端的地址以及当前客户端的地址

cas登录流程解析及springboot集成cas,第2张

#CAS服务器的URL前缀。指定CAS服务器的地址和端口号。
cas.server-url-prefix=http://192.168.3.96:8199/cas
#CAS服务器的登录页面URL。指定CAS登录页面的完整地址。
cas.server-login-url=http://192.168.3.96:8199/cas/login
# CAS客户端的主机URL。指定CAS客户端的地址和端口号。
cas.client-host-url=http://localhost:9003
#:CAS验证URL的模式。指定需要进行CAS认证的URL模式,多个模式之间使用逗号分隔。
cas.authentication-url-patterns=/,/index,/base/login,/index/
#CAS客户端的本地URL。在CAS登录成功后,会跳转回CAS客户端的这个地址。service参数用来指定登录成功后要返回的URL。
local.url=http://192.168.3.96:8199/cas/login?service=http://localhost:9003/cas/index
#应用程序的HTTP端口号。指定应用程序运行的HTTP端口。
server.httpPort= 9003
 3.然后在我们的启动类上要加上这个@EnableCasClient注解

cas登录流程解析及springboot集成cas,第3张

4.接下来就是对CAS过滤器进行配置(该部分使用公司已有的cas过滤器比较合适,下面给出的代码仅为参考提供思路,具体以实际业务需求为准)
package com.xk.common.web;
import com.xk.common.core.user.dao.CapUserRepository;
import com.xk.common.core.user.model.dto.CapUserDto;
import com.xk.common.core.user.model.entity.CapUserEntity;
import com.xk.framework.common.Constants;
import net.unicon.cas.client.configuration.CasClientConfigurationProperties;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
@Controller("indexController")
public class IndexController {
    protected Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private CapUserRepository capUserRepository;
    @RequestMapping("/")
    public String toIndexDefault(HttpServletRequest request) {
        CapUserDto capUserDto = (CapUserDto) request.getSession().getAttribute(Constants.XK_SESSION_USER);
        // 如果当前没有登录,session中没有会话,代表非法访问
        if (capUserDto == null) {
            logger.info(">>>>>>>>>>>>>>当前没有登录,session中没有会话,代表非法访问<<<<<<<<<<<<<<<");
            return "error/404";
        }
        CapUserEntity capUser=capUserRepository.xkFindById(capUserDto.getId());
        String oldPwd=null;
        BCryptPasswordEncoder bc = new BCryptPasswordEncoder(4);
        if(null!=capUser && StringUtils.isNotEmpty(capUser.getId())){
            oldPwd=capUser.getPassword();
            capUser.setPassword(bc.encode("wgs1234"));
        }else{
            logger.info(">>>>>>>>>>>>>>当前没有登录,session中没有会话,代表非法访问<<<<<<<<<<<<<<<");
            return "error/404";
        }
        UsernamePasswordAuthenticationToken token=new UsernamePasswordAuthenticationToken(capUserDto.getUserId(),"wgs1234");
        Authentication authentication=authenticationManager.authenticate(token);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        request.getSession().setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
        // 从session中获取用户信息,然后根据返回的usertype,跳转到不同的逻辑页面
        //CapUserDto wgsUserInfoDto = capUserService.getUserById(capUserDto.getUserId());
        //  如果当前登录,但是没有找到匹配的记录
        /*if (wgsUserInfoDto == null) {
            logger.info(">>>>>>>>>>>>>>当前登录存在session会话,但是没有找到匹配的记录,userid:{}<<<<<<<<<<<<<<<", capUserDto.getUserId());
            return "error/404";
        }*/
        //userService.loadUserByUsername(capUserDto.getUserName());
        request.getSession().setAttribute("XK_USER", capUserDto);
        capUser.setPassword(oldPwd);
        return "admin/home";
    }
    @GetMapping("/home")
    public String toIndex(HttpServletRequest request) {
        return toIndexDefault(request);
    }
    @Autowired
    private CasClientConfigurationProperties casClientConfigurationProperties;
    /**
     * 本地应用(CASClient)退出登录
     * @param request
     * @return
     */
    @GetMapping("/portal/logout")
    public String toLogout(HttpServletRequest request) {
        //销毁本地应用的Session
        request.getSession().invalidate();
        //直接跳转至CASServer的注销地址,并带上本地应用的主页地址,便于再次登录后返回至该应用
        return "redirect:" + casClientConfigurationProperties.getServerUrlPrefix() + "/logout?service=" + casClientConfigurationProperties.getClientHostUrl() ;
    }
}
package com.xk.wgs.cas;
import com.google.common.collect.ImmutableMap;
import com.xk.common.core.organize.model.dto.EmployeeDto;
import com.xk.common.core.user.model.dto.CapRoleDto;
import com.xk.common.core.user.model.dto.CapUserDto;
import com.xk.framework.common.Constants;
import com.xk.platform.core.organize.service.IEmployeeQueryService;
import com.xk.platform.security.user.service.CustomUserDetailsService;
import com.xk.platform.security.user.service.ICapRoleService;
import com.xk.platform.security.user.service.ICapUserService;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.util.AssertionHolder;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
public class CasUserReceivingTicketValidationFilter extends Cas20ProxyReceivingTicketValidationFilter {
    public CasUserReceivingTicketValidationFilter() {
        super();
    }
    private ICapUserService capUserService;
    private ICapRoleService capRoleQueryService;
    private IEmployeeQueryService employeeQueryService;
    @Resource
    private CustomUserDetailsService userDetailsService;
    public CasUserReceivingTicketValidationFilter(ICapUserService capUserService, ICapRoleService capRoleQueryService,IEmployeeQueryService employeeQueryService) {
        super();
        this.capUserService = capUserService;
        this.capRoleQueryService = capRoleQueryService;
        this.employeeQueryService = employeeQueryService;
    }
    @Override
    public void onSuccessfulValidation(HttpServletRequest request,
                                       HttpServletResponse response, Assertion assertion) {
        AttributePrincipal principal = assertion.getPrincipal();
        logger.info(principal.getName() + "--------------------------------");
        AssertionHolder.setAssertion(assertion);
        //获取用户信息
        CapUserDto capUserDto = capUserService.getUserByUserid(principal.getName());
        capUserDto.setPassword("");
        String roleId = "";
        // 获取用户登录信息
        // 获取当前用户授予的角色
        if (capUserDto == null) {
            logger.error(">>>>>>>>>>>>>>>>>>>单点登录成功后的门户无此人信息,登录账号:{}<<<<<<<<<<<<<<", principal.getName());
        } else {
            // 根据学号/工号,查询所属的员工,再查员工授予的角色
            EmployeeDto empByUserId = employeeQueryService.getEmpByUserId(capUserDto.getId());
            // 查询员工授予的角色
            List capRoleDtos = capRoleQueryService.findRolesByPartyIdAndPartyType(empByUserId.getId(), "EMP");
            if (capRoleDtos != null && capRoleDtos.size() > 0) {
                roleId = capRoleDtos.get(0).getId();
                logger.info(">>>>>>>查询到角色id:{}",roleId);
            }
        }
        // 这里一般只有一个角色,确定;
        // 生成令牌
        Map map = ImmutableMap.of("userid", principal.getName(), "roleId", roleId);
        String jwt = RTokenAuthenticationService.genAuthentication(response,
                principal.getName(),
                map);
        //userDetailsService.loadUserByUsername(capUserDto.getUserId());
        request.getSession().setAttribute(Constants.XK_SESSION_USER, capUserDto);
        request.getSession().setAttribute("XK_TOKEN", jwt);
        request.getSession().setAttribute("XK_USERID", principal.getName());
    }
    @Override
    public void onFailedValidation(HttpServletRequest request, HttpServletResponse response) {
        logger.info("Failed to validate cas ticket");
    }
}

该部分代码因涉及到token的生成及其他业务流程,不适用于其他项目,仅为提供参考思路。 

退出登录

退出登录时通过a标签跳转/portal/logout,重定向到cas销毁地址,实现退出登录。

cas集成完毕,下面是测试阶段内容。

访问应用,跳转到cas登陆页面,输入账号密码后登陆成功。

cas登录流程解析及springboot集成cas,第4张

cas登录流程解析及springboot集成cas,第5张

 点击退出登录

cas登录流程解析及springboot集成cas,第6张