在学习一个开源项目是时集成了cas,但文档过于简单,研究了两天这次记录做个补充
1.下载cas项目
GitHub - apereo/cas-overlay-template at 5.3
2.编译
解压zip,命令行进去,执行mvn clean package
结束之后会出现 target 文件夹,里面有一个cas.war包,这个cas.war包就是我们要运行的程序。
环境介绍
节点 服务名
http://localhost:3100/ 某J前端
http://localhost:9999/sys/cas/client/validateLogin 某J后端二次验证用户并赋予token登信息
http://cas.test.com:8888/cas CAS服务端(域名自己配置host文件)
http://localhost:9999/sys/casLogin rest-client(认证服务)
cas怎么调用cas认证服务的 cas.authn.rest.uri=http://localhost:9999/sys/casLogin
当用户点击登录后,cas会发送post请求到http://rest.cas.com:8083/login并且把用户信息以"用户名:密码"进行Base64编码放在authorization请求头中。
若输入用户名密码为:admin/123456;那么请求头包括:
authorization=Basic Base64(admin+MD5(123))
那么发送后客户端必须响应一下数据,cas明确规定如下:
● cas 服务端会通过post请求,并且把用户信息以”用户名:密码”进行Base64编码放在authorization请求头中
● 200状态码:并且格式为
{“@class”:”org.apereo.cas.authentication.principal.SimplePrincipal”,”id”:”casuser”,”attributes”:{}}是成功的
● 403状态码:用户不可用
● 404状态码:账号不存在
● 423状态码:账户被锁定
● 428状态码:过期
● 其他登录失败
某j整体思路
3.配置
修改cas.war中的cas\WEB-INF\classes配置文件
cas服务端配置
pom.xml添加以下rest依赖
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-rest-authentication</artifactId>
<version>${cas.version}</version>
</dependency>
application.properties添加如下配置
#是否开启json识别功能,默认为false
cas.serviceRegistry.initFromJson=true
#忽略https安全协议,使用 HTTP 协议
cas.tgc.secure=false
cas.authn.rest.uri=http://localhost:9999/sys/casLogin
兼容http请求:
修改cas\WEB-INF\classes\services\HTTPSandIMAPS-10000001.json 加入http
{
"@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
}
#如果密码有加密,打开下面配置,我的是明文
#cas.authn.rest.passwordEncoder.type=DEFAULT
#cas.authn.rest.passwordEncoder.characterEncoding=UTF-8
#cas.authn.rest.passwordEncoder.encodingAlgorithm=MD5
4.启动cas服务
把cas.war放到tomcat里启动就行
5.rest-client接口代码
实体CasUser
package org.jeecg.modules.system.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.Map;
/**
* @author: zfq
* @date: 2024
* @description: cas-rest返回cas服务端信息
*/
public class CasUser {
@JsonProperty("id")
@NotNull
private String username;
/**
* 需要返回实现org.apereo.cas.authentication.principal.Principal的类名接口
*/
@JsonProperty("@class")
private String clazz = "org.apereo.cas.authentication.principal.SimplePrincipal";
@JsonProperty("attributes")
private Map<String, Object> attributes = new HashMap<>();
@JsonIgnore
@NotNull
private String password;
/**
* 用户状态,根据状态判断是否可用
*/
@JsonIgnore
private String state;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getClazz() {
return clazz;
}
public void setClazz(String clazz) {
this.clazz = clazz;
}
public Map<String, Object> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, Object> attributes) {
this.attributes = attributes;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
@JsonIgnore
public CasUser addAttribute(String key, Object val) {
getAttributes().put(key, val);
return this;
}
}
接口casLogin
/**
* 1. cas 服务端会通过post请求,并且把用户信息以"用户名:密码"进行Base64编码放在authorization请求头中
* 2. 返回200状态码并且格式为{"@class":"org.apereo.cas.authentication.principal.SimplePrincipal","id":"casuser","attributes":{}} 是成功的
* 2. 返回状态码403用户不可用;404账号不存在;423账户被锁定;428过期;其他登录失败
* @param httpHeaders
* @return
*/
@RequestMapping("/casLogin")
public Object login(@RequestHeader HttpHeaders httpHeaders){
CasUser casUser=new CasUser();
try {
UserTemp userTemp = obtainUserFormHeader(httpHeaders);
//尝试查找用户库是否存在
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getUsername,userTemp.username);
SysUser user = sysUserService.getOne(queryWrapper);
String userpassword = PasswordUtil.encrypt(user.getUsername(), userTemp.password, user.getSalt());
//{“@class”:”org.apereo.cas.authentication.principal.SimplePrincipal”,”id”:”casuser”,”attributes”:{}}是成功的
casUser.setUsername(user.getUsername());
if (user != null) {
if (!user.getPassword().equals(userpassword)) {
//密码不匹配
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
if (!"1".equals(user.getStatus())) {// 状态(1:正常 2:冻结 )
//用户已锁定
return new ResponseEntity(HttpStatus.LOCKED);
}
} else {
//不存在 404
return new ResponseEntity(HttpStatus.NOT_FOUND);
}
} catch (UnsupportedEncodingException e) {
new ResponseEntity(HttpStatus.BAD_REQUEST);
}
//成功返回json
return casUser;
}
/**
* This allows the CAS server to reach to a remote REST endpoint via a POST for verification of credentials.
* Credentials are passed via an Authorization header whose value is Basic XYZ where XYZ is a Base64 encoded version of the credentials.
* @param httpHeaders
* @return
* @throws UnsupportedEncodingException
*/
private UserTemp obtainUserFormHeader(HttpHeaders httpHeaders) throws UnsupportedEncodingException {
//cas服务端会通过把用户信息放在请求头authorization中,并且通过Basic认证方式加密
String authorization = httpHeaders.getFirst("authorization");
if(StringUtils.isEmpty(authorization)){
return null;
}
String baseCredentials = authorization.split(" ")[1];
//用户名:密码
String usernamePassword = new String(Base64Utils.decodeFromString(baseCredentials), "UTF-8");
String[] credentials = usernamePassword.split(":");
return new UserTemp(credentials[0], credentials[1]);
}
/**
* 从请求头中获取用户名和密码
*/
private class UserTemp {
private String username;
private String password;
public UserTemp(String username, String password) {
this.username = username;
this.password = password;
}
}
某J validateLogin二次验证用户并赋予token(不是该框架的可以忽略,是的话官方有代码)
package org.jeecg.modules.cas.controller;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.modules.cas.util.CasServiceUtil;
import org.jeecg.modules.cas.util.XmlUtils;
import org.jeecg.modules.system.entity.SysDepart;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.service.ISysDepartService;
import org.jeecg.modules.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
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 com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
/**
* <p>
* CAS单点登录客户端登录认证
* </p>
*
* @Author zfq
* @since 2023-12-20
*/
@Slf4j
@RestController
@RequestMapping("/sys/cas/client")
public class CasClientController {
@Autowired
private ISysUserService sysUserService;
@Autowired
private ISysDepartService sysDepartService;
@Autowired
private RedisUtil redisUtil;
@Value("${cas.prefixUrl}")
private String prefixUrl;
@GetMapping("/validateLogin")
public Object validateLogin(@RequestParam(name="ticket") String ticket,
@RequestParam(name="service") String service,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
Result<JSONObject> result = new Result<JSONObject>();
log.info("Rest api login.");
try {
String validateUrl = prefixUrl+"/p3/serviceValidate";
String res = CasServiceUtil.getStValidate(validateUrl, ticket, service);
log.info("res."+res);
final String error = XmlUtils.getTextForElement(res, "authenticationFailure");
if(StringUtils.isNotEmpty(error)) {
throw new Exception(error);
}
final String principal = XmlUtils.getTextForElement(res, "user");
if (StringUtils.isEmpty(principal)) {
throw new Exception("No principal was found in the response from the CAS server.");
}
log.info("-------token----username---"+principal);
//1. 校验用户是否有效
SysUser sysUser = sysUserService.getUserByName(principal);
result = sysUserService.checkUserIsEffective(sysUser);
if(!result.isSuccess()) {
return result;
}
String token = JwtUtil.sign(sysUser.getUsername(), sysUser.getPassword());
// 设置超时时间
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME*2 / 1000);
//获取用户部门信息
JSONObject obj = new JSONObject();
List<SysDepart> departs = sysDepartService.queryUserDeparts(sysUser.getId());
obj.put("departs", departs);
if (departs == null || departs.size() == 0) {
obj.put("multi_depart", 0);
} else if (departs.size() == 1) {
sysUserService.updateUserDepart(principal, departs.get(0).getOrgCode(),null);
obj.put("multi_depart", 1);
} else {
obj.put("multi_depart", 2);
}
obj.put("token", token);
obj.put("userInfo", sysUser);
result.setResult(obj);
result.success("登录成功");
} catch (Exception e) {
//e.printStackTrace();
result.error500(e.getMessage());
}
return new HttpEntity<>(result);
}
}
6.某J前后端配置
- 前端vue项目对接CAS
某J 前端3.0.0以后只默认集成SSO所有要的代码,只需需修改环境配置文件即可开启SSO
#单点服务端地址
VITE_GLOBE_APP_CAS_BASE_URL=http://cas.test.com:8443/cas
# 是否开启单点登录
VITE_GLOB_APP_OPEN_SSO = false
复制
VITE_GLOB_APP_OPEN_SSO =true 即代表开启SSO登录
=============================
- 后端boot修改yml配置
cas:
# 配置CAS服务地址,cas为工程目录,部署到ROOT目录下http://cas.test.com:8443即可
prefixUrl: http://cas.test.com:8443/cas
6.最后启动各个服务访问前端:
一会跳到cas界面
输入完账号密码跳到首页但没内容,某j的bug感觉是
刷新完正常
参考博客:
cas5.3.2单点登录-rest认证(十二)_cas 调用其它接口认证-CSDN博客 这家写的cas真不错,十几篇成体系,比某些收费的都要好
https://www.cnblogs.com/youqc/p/14861455.html 这家的主要是入门免证书,htpp配置等