单点登录
单点登录又称之为 Single Sign On
, 简称SSO
,实现基于用户会话的共享。
- 提供用户效率
- 提高开发人员的效率
- 简化管理
单点登录的实现方式
- 单机部署:web单系统应用
- 分布式集群部署
基于Cookie的单点登录
底层原理是同源策略。比如说现在有个域名为 www.lgq.com
, 是教育类网站, 但是如果有其他的产品线, 可以通过构建子域名提供服务给用户访问, 比如: music.lgq.com
, shop.lgq.com
, blog.lgq.com
等等, 分别为音乐, 电商以及博客等, 用户只需要在其中一个站点登录, 那么其他站点也会随之而登录。
也就是说, 用户自始至终只在某一个网站下登录后, 那么他所产生的会话, 就共享给了其他的网站, 实现了单点网站登录后, 同时间接登录了其他的网站, 这个其实就是单点登录, 他们的会话是共享的, 都是同一个用户会话。
- 原理主要也是 cookie 和 网站 的依赖关系, 域名
www.lgq.com
和*.lgq.com
的cookie值是可以共享的, 可以被携带至后端的, 比如设置为.lgq.com
, .t.lgq.com
, 这样是OK的. - 二级域名的独立 cookie 是不能共享的, 不能被其他二级域名获取, 比如:
music.lgq.com
的 cookie 是不能被mtv.lgq.com
共享, 两者互不影响, 要共享必须设置为*.lgq.com
.
使用cookie作为媒介,存放用户凭证。用户登录父应用后,应用返回一个机密的cookie,当用户访问子应用的时候,携带这个cookie,授权应用解密cookie进行校验,校验则登录当前用户。
缺陷:
- cookie不安全
- 不能跨域实现免登录
CAS实现跨域单点登录
多个系统的登录,使用独立的登录系统去验证,相当于一个中介公司,整合了所有人,实现等一登录,而这个称之为CAS系统,全称 Central Authentication Service,即中央认证服务,是一个单点登录的解决方案,可用于不同域名之间实现单点登录。
以下是CAS单点登录的时序图(官方图):
三个重要概念
- Ticket Granting ticket(TGT):CAS server根据用户名和密码生成的票据,存在server端。
- Ticket Granting cookie(TGC):一个cookie,存放用户身份信息,由server发送给client。
- Service Ticket(ST):由TGT生成的一次性票据,用户验证,只能使用一次。
基于CAS原理实现SSO
用户登录两个域名不同的系统,一个音乐系统,一个博客系统,实现跨域单点登录的CAS时序图如下:
音乐系统和博客系统前端主要逻辑如下:
let me = this;
// 通过cookie判断用户是否登录
this.judgeUserLoginStatus();
// http://www.music.com:8080/sso-music/index.html
// 判断用户是否登录
var userIsLogin = this.userIsLogin;
if (!userIsLogin) {
// 如果没有登录,判断一下是否存在tmpTicket临时票据
let tmpTicket = app.getUrlParam("tmpTicket");
console.log("tmpTicket: " + tmpTicket);
if (tmpTicket != null && tmpTicket != "" && tmpTicket != undefined) {
// 如果有tmpTicket临时票据,就携带临时票据发起请求到cas验证获取用户会话
axios.defaults.withCredentials = true;
axios.post('http://www.sso.com:8090/verifyTmpTicket?tmpTicket=' + tmpTicket)
.then(res => {
if (res.data.status == 200) {
var userInfo = res.data.data;
console.log(res.data.data);
// debugger;
this.userInfo = userInfo;
this.userIsLogin = true;
app.setCookie("user", JSON.stringify(userInfo));
// debugger;
window.location.href = "http://www.music.com:92/index.html";
} else {
alert(res.data.msg);
console.log(res.data.msg);
}
});
} else {
// 如果没有tmpTicket临时票据,说明用户从没登录过,那么就可以跳转至cas做统一登录认证了
window.location.href = app.SSOServerUrl + "/login?returnUrl=http://www.music.com:92/index.html";
}
console.log(app.SSOServerUrl + "/login?returnUrl=" + window.location);
}
CAS 主要逻辑:
@Controller
public class SSOController {
private static final String TICKET_ERROR_TIP = "用户票据异常!";
@Resource
private UserService userService;
@Resource
private RedisOperator redisOperator;
/**
* 用户会话key, 值是 用户VO
*/
protected static final String REDIS_USER_TOKEN = "redis_user_token";
/**
* 全局门票key前缀, 值是 用户ID
*/
protected static final String REDIS_USER_TICKET = "redis_user_ticket";
/**
* 临时门票key前缀, 值是 临时票据的md5加密. 用于回跳调用端网站.
*/
protected static final String REDIS_TMP_TICKET = "redis_tmp_ticket";
/**
* 用户cookie key, 值是 随机生成的全局票据
*/
protected static final String COOKIE_USER_TICKET = "cookie_user_ticket";
private static final Logger log = LoggerFactory.getLogger(SSOController.class);
/**
* 没有登录就跳转到CAS登录界面
*
* @param returnUrl
* @param model
* @param request
* @param response
* @return
*/
@GetMapping("/login")
public String goToLogin(String returnUrl, Model model,
HttpServletRequest request, HttpServletResponse response) {
model.addAttribute("returnUrl", returnUrl);
// 后续完善校验是否登录
// 1. 获取userTicket门票,如果获取到,说明用户登录过,此时颁发一次性的临时门票
String userTicket = this.getCookie(request, COOKIE_USER_TICKET);
if (this.verifyUserTicket(userTicket)) {
// 发放临时票据
String tmpTicket = this.createTmpTicket();
return "redirect:" + returnUrl + "?tmpTicket=" + tmpTicket;
}
// 2. 用户从未登录过,第一次进入则跳转到cas登录界面
return "login";
}
/**
* 校验CAS全局用户门票
*
* @param userTicket
* @return
*/
private boolean verifyUserTicket(String userTicket) {
// 0. 验证CAS门票不能为空
if (StringUtils.isBlank(userTicket)) {
return false;
}
// 1. 验证CAS门票是否有效, 其实就是验证是否在redis中
String userId = this.redisOperator.get(REDIS_USER_TICKET + ":" + userTicket);
if (StringUtils.isBlank(userId)) {
return false;
}
// 2. 根据用户ID验证门票的user会话是否存在
String userToken = this.redisOperator.get(REDIS_USER_TOKEN + ":" + userId);
return !StringUtils.isBlank(userToken);
}
/**
* CAS统一登录逻辑.
* <ul>
* <li>登录过后创建用户的全局会话 uniqueToken</li>
* <li>创建用户全局门票,用以表示在CAS端是否登录 userTicket</li>
* <li>创建用户的临时票据,用于回跳回传 tmpTicket</li>
* </ul>
*
* @param returnUrl
* @param username
* @param password
* @param model
* @param request
* @param response
* @return
*/
@PostMapping("/doLogin")
public String doLogin(String returnUrl,
String username, String password, Model model,
HttpServletRequest request, HttpServletResponse response) {
model.addAttribute("returnUrl", returnUrl);
// 1. 实现登录
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
model.addAttribute("errmsg", "用户名或密码错误!");
return "login";
}
Users user = this.userService.queryUserForLogin(username, password);
if (user == null) {
model.addAttribute("errmsg", "用户名或密码错误!");
return "login";
}
// 2. 登录成功之后,创建用户会话(使用redis)
String uniqueToken = UUID.randomUUID().toString().trim();
UsersVO usersVO = new UsersVO();
BeanUtils.copyProperties(user, usersVO);
usersVO.setUserUniqueToken(uniqueToken);
this.redisOperator.set(REDIS_USER_TOKEN + ":" + user.getId(), JsonUtils.objectToJson(usersVO));
// 3. 生成ticket门票,全局门票,代表用户在CAS端登录过
String userTicket = UUID.randomUUID().toString().trim();
// 3.1 用户全局门票需要放入CAS端的cookie中
this.setCookie(COOKIE_USER_TICKET, userTicket, response);
// 4. userTicket 关联 用户id,并且放入redis中,代表用户有门票了,可以到各个站点访问了。这个key其实就是
this.redisOperator.set(REDIS_USER_TICKET + ":" + userTicket, user.getId());
// 5. 生成临时票据,回跳到调用端网站,是由CAS端所签发的一个一次性的临时ticket
String tmpTicket = this.createTmpTicket();
// userTicket: 用于表示用户在CAS端的一个登录状态:已经登录
// tmpTicket: 是一个临时票据,用于颁发给用户进行一次性验证的票据,由时效性
// 举例:去公园玩,大门口买了一张统一门票,这个就是CAS系统和用户全局会话,动物园的一些小景点,需要凭门票去领取一次性的票据,
// 有了这张票据之后,就能去小景点游玩了,这样的一个个小景点其实就是我们这里所对应的一个个站点。
// 当我们使用完毕这张临时票据之后,就要销毁。
// return "login";
// 重定向
return "redirect:" + returnUrl + "?tmpTicket=" + tmpTicket;
}
/**
* 验证临时票据
*
* @param tmpTicket
* @param request
* @param response
* @return
*/
@PostMapping("/verifyTmpTicket")
@ResponseBody
public FoodieJsonResult verifyTmpTicket(String tmpTicket,
HttpServletRequest request, HttpServletResponse response) {
// 使用一次性临时票据验证用户是否登录过,如果登录过,把用户会话信息返回给站点
// 使用完毕后,需要销毁临时票据
String tmpTicketVal = this.redisOperator.get(REDIS_TMP_TICKET + ":" + tmpTicket);
if (StringUtils.isBlank(tmpTicketVal)) {
return FoodieJsonResult.errorUserTicket(TICKET_ERROR_TIP);
}
// 判断临时票据是否正确,如果没问题则需要销毁,并且拿到CAS端cookie中的全局userTicket,以此再获取用户的cookie
if (!StringUtils.equals(tmpTicketVal, CodecUtils.md5Hex(tmpTicket, ""))) {
return FoodieJsonResult.errorUserTicket(TICKET_ERROR_TIP);
}
// 销毁临时票据
this.redisOperator.del(REDIS_TMP_TICKET + ":" + tmpTicket);
// 1. 验证并获取用户的userTicket
String userTicket = this.getCookie(request, COOKIE_USER_TICKET);
String userId = this.redisOperator.get(REDIS_USER_TICKET + ":" + userTicket);
// 判断用户id
if (StringUtils.isBlank(userId)) {
return FoodieJsonResult.errorUserTicket(TICKET_ERROR_TIP);
}
// 2. 验证门票对于的user会话是否存在
String userRedisVO = this.redisOperator.get(REDIS_USER_TOKEN + ":" + userId);
if (StringUtils.isBlank(userRedisVO)) {
return FoodieJsonResult.errorUserTicket(TICKET_ERROR_TIP);
}
// 验证成功,返回ok,携带用户会话
return FoodieJsonResult.ok(JsonUtils.jsonToPojo(userRedisVO, UsersVO.class));
}
/**
* 注销
*
* @param userId
* @param request
* @param response
* @return
*/
@PostMapping("/logout")
@ResponseBody
public FoodieJsonResult logout(String userId,
HttpServletRequest request, HttpServletResponse response) {
// 0. 获取CAS中的用户门票
String userTicket = this.getCookie(request, COOKIE_USER_TICKET);
// 1. 清除UserTicket票据,redis/cookie
this.deleteCookie(response, COOKIE_USER_TICKET);
this.redisOperator.del(REDIS_USER_TICKET + ":" + userTicket);
// 2. 清除用户全局会话(分布式会话)
this.redisOperator.del(REDIS_USER_TOKEN + ":" + userId);
return FoodieJsonResult.ok();
}
/**
* 创建临时票据. 用于一次性验证的票据, 有效时间10分钟.
*
* @return 临时票据
*/
private String createTmpTicket() {
String tmpTicket = UUID.randomUUID().toString().trim();
this.redisOperator.set(REDIS_TMP_TICKET + ":" + tmpTicket, CodecUtils.md5Hex(tmpTicket, ""), 600);
return tmpTicket;
}
/**
* 设置进cookie域中
*
* @param key 键
* @param value 值
* @param response HttpServletResponse
*/
private void setCookie(String key, String value, HttpServletResponse response) {
Cookie cookie = new Cookie(key, value);
cookie.setDomain("sso.com");
// 所有都可以
cookie.setPath("/");
response.addCookie(cookie);
}
/**
* 获取cookie值
*
* @param request HttpServletRequest
* @param key 键
* @return cookie value
*/
private String getCookie(HttpServletRequest request, String key) {
Cookie[] cookies = request.getCookies();
if (cookies == null || StringUtils.isBlank(key)) {
return null;
}
Optional<String> cookieValue = Arrays.stream(cookies)
.filter(cookie -> StringUtils.equals(cookie.getName(), key))
.map(Cookie::getValue)
.findAny();
return cookieValue.orElse(null);
}
/**
* 删除cookie
*
* @param response
* @param key
*/
private void deleteCookie(HttpServletResponse response, String key) {
Cookie cookie = new Cookie(key, null);
cookie.setDomain("sso.com");
cookie.setPath("/");
cookie.setMaxAge(-1);
response.addCookie(cookie);
}
}
Apereo的CAS使用
拉取CAS服务模板。5.3版本之前是基于maven的,之后是基于gradle的。
# 拉取最新版
git clone https://github.com/apereo/cas-overlay-template.git
# 拉取5.3版本
git clone -b 5.3 https://github.com/apereo/cas-overlay-template.git
使用5.3(maven)版本
使用IDE打开项目,待项目拉取完依赖后,会在根目录生成一个overlays
的目录。在根目录创建src/main/resources
目录,把overlays/org.apereo.cas.cas-server-webapp-tomcat-5.3.16/WEB-INF/classes/application.properties
复制到此目录下面。
配置相关
创建keystore
# 生成密匙
keytool -genkey -alias castest -keyalg RSA -keystore ./castest.keystore
注意,名字和姓氏那里填的是CAS服务器的域名(test.cas.com)。
把证书导入JDK,注意这个JDK是运行client时的JDK,否则在CAS client接收临时票据时会报javax.net.ssl.SSLHandshakeException
错。
# 生成证书
keytool -export -alias castest -storepass 123456 -file ./castest.cer -keystore ./castest.keystore
# 信任证书,导入JDK,注意输入密匙库口令是 changeit ,不是密匙的口令
keytool -import -alias castest -keystore D:/SDK/JDK/jdk8u302-b08/jre/lib/security/cacerts -file ./castest.cer -trustcacerts
把keystore
拷贝到src/main/resources
,在application.properties
配置CAS服务器的keystore
。(keystore的位置可以任意,注意位置不要配置错)
server.ssl.key-store=classpath:castest.keystore
server.ssl.key-store-password=123456
server.ssl.key-password=123456
# 用户名和密码
cas.authn.accept.users=lgq::51233
创建services
文件夹,注册服务文件放在这里,然后在application.properties
中配置位置。
cas.serviceRegistry.json.location=classpath:/services
注册文件格式如下:
{
"@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
}
编译运行
命令进入根目录,执行以下指令
./build.cmd run
会发现不少报错,原因是jdk版本的问题,本机系统使用JDK17
,而项目需要JDK8
。
解决方案:可以直接修改系统环境变量的JAVA_HOME
或者修改build.cmd
脚本,注意,还要修改运行时使用的java版本。
@set JAVA_HOME=D:\SDK\JDK\jdk8u302-b08
:debug
call:package %1 %2 %3 & %JAVA_HOME%/bin/java.exe %JAVA_ARGS% -Xdebug -Xrunjdwp:transport=dt_socket,address=5000,server=y,suspend=n -jar target/cas.war
@goto:eof
:run
call:package %1 %2 %3 & %JAVA_HOME%/bin/java.exe %JAVA_ARGS% -jar target/cas.war
@goto:eof
修改后再运行一次。
报错Invalid keystore format
。
Caused by: java.io.IOException: Invalid keystore format
at sun.security.provider.JavaKeyStore.engineLoad(JavaKeyStore.java:666) ~[?:1.8.0_302]
at sun.security.provider.JavaKeyStore$JKS.engineLoad(JavaKeyStore.java:57) ~[?:1.8.0_302]
at sun.security.provider.KeyStoreDelegator.engineLoad(KeyStoreDelegator.java:224) ~[?:1.8.0_302]
at sun.security.provider.JavaKeyStore$DualFormatJKS.engineLoad(JavaKeyStore.java:71) ~[?:1.8.0_302]
at java.security.KeyStore.load(KeyStore.java:1445) ~[?:1.8.0_302]
at org.apache.tomcat.util.security.KeyStoreUtil.load(KeyStoreUtil.java:69) ~[tomcat-embed-core-8.5.47.jar!/:8.5.47]
at org.apache.tomcat.util.net.SSLUtilBase.getStore(SSLUtilBase.java:216) ~[tomcat-coyote-8.5.47.jar!/:8.5.47]
at org.apache.tomcat.util.net.SSLHostConfigCertificate.getCertificateKeystore(SSLHostConfigCertificate.java:206) ~[tomcat-coyote-8.5.47.jar!/:8.5.47]
at org.apache.tomcat.util.net.SSLUtilBase.getKeyManagers(SSLUtilBase.java:282) ~[tomcat-coyote-8.5.47.jar!/:8.5.47]
at org.apache.tomcat.util.net.SSLUtilBase.createSSLContext(SSLUtilBase.java:246) ~[tomcat-coyote-8.5.47.jar!/:8.5.47]
at org.apache.tomcat.util.net.AbstractJsseEndpoint.createSSLContext(AbstractJsseEndpoint.java:98) ~[tomcat-coyote-8.5.47.jar!/:8.5.47]
at org.apache.tomcat.util.net.AbstractJsseEndpoint.initialiseSsl(AbstractJsseEndpoint.java:72) ~[tomcat-coyote-8.5.47.jar!/:8.5.47]
at org.apache.tomcat.util.net.NioEndpoint.bind(NioEndpoint.java:244) ~[tomcat-coyote-8.5.47.jar!/:8.5.47]
at org.apache.tomcat.util.net.AbstractEndpoint.start(AbstractEndpoint.java:1191) ~[tomcat-coyote-8.5.47.jar!/:8.5.47]
at org.apache.coyote.AbstractProtocol.start(AbstractProtocol.java:591) ~[tomcat-coyote-8.5.47.jar!/:8.5.47]
at org.apache.catalina.connector.Connector.startInternal(Connector.java:1018) ~[tomcat-catalina-8.5.47.jar!/:8.5.47]
... 21 more
keystore
的格式不符合tomcat
,查看keystore
的类型。
# 查看keystore信息
keytool -list -v -keystore ./castest.keystore -storepass 123456
密钥库类型: PKCS12
密钥库提供方: SUN
您的密钥库包含 1 个条目
别名: castest
创建日期: 2021年10月23日
条目类型: PrivateKeyEntry
证书链长度: 1
证书[1]:
所有者: CN=test.cas.com, OU=lgq, O=lgq, L=gd, ST=gz, C=CN
发布者: CN=test.cas.com, OU=lgq, O=lgq, L=gd, ST=gz, C=CN
序列号: cec5368feddea099
生效时间: Sat Oct 23 16:31:45 CST 2021, 失效时间: Fri Jan 21 16:31:45 CST 2022
证书指纹:
SHA1: 57:29:54:3D:BC:87:71:CC:7B:B1:C2:31:CA:F3:3B:4A:06:1A:D8:E2
SHA256: A8:C7:8E:77:D2:F9:A6:02:5E:6F:4D:44:B7:43:A6:26:BA:B9:7B:E0:B7:06:67:AA:E2:C1:07:B5:28:9D:F7:9B
签名算法名称: SHA256withRSA
主体公共密钥算法: 2048 位 RSA 密钥
版本: 3
可以看到默认创建的key类型是PKCS12
,应该换成JKS
。
# 重新创建
keytool -genkey -alias castest -keyalg RSA -storetype JKS -keystore ./castest.keystore
替换keystore之后,再启动,成功。
修改hosts
,配置127.0.0.1 test.cas.com
,然后访问https://test.cas.com:8443/cas/login
,访问成功如下:
CAS Client
引入依赖
<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-support-springboot</artifactId>
<version>3.6.2</version>
</dependency>
配置application.yml
cas:
server-url-prefix: https://test.cas.com:8443/cas
server-login-url: https://test.cas.com:8443/cas/login
client-host-url: http://www.castest.com:8090
运行之后,访问http://www.castest.com:8090/login
,报未认证授权的服务错。
原因是默认的服务不支持http
的。修改服务注册信息,在src/main/resources/services
下的json文件,添加对http
的支持。
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^(https|http)://.*",
"name" : "HTTP",
"id" : 10000001,
"description" : "This service definition authorizes all application urls that support HTTPS and HTTP protocols.",
"evaluationOrder" : 10000
}
然后还要在CAS server的application.properties
中添加以下内容:
cas.tgc.secure=false
cas.serviceRegistry.initFromJson=true
修改后重新编译运行,访问http://www.castest.com:8090/login
。
可以看到地址栏有个JSESSIONID
。一般情况下是在cookie中的,服务器可以从cookie中获取session的id,但是如果客户端禁用cookie的话,就要通过url来找到这个session的id了,可以通过以下方式去掉:
方式一,通过配置文件。
server:
servlet:
session:
tracking-modes: cookie
cookie:
http-only: true
方式二,继承SpringBootServletInitializer
后在onStarup
中修改配置。
@SpringBootApplication
@EnableCasClient
public class DemoApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
// 只使用cookie
servletContext.setSessionTrackingModes(Collections.singleton(SessionTrackingMode.COOKIE));
// 阻止js去获取cookie信息
SessionCookieConfig sessionCookieConfig = servletContext.getSessionCookieConfig();
sessionCookieConfig.setHttpOnly(true);
}
}
使用JDBC认证登录
默认是在application.properties
文件中配置用户信息的,现在改为MySQL
。在pom.xml
添加依赖。
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apereo.cas/cas-server-support-jdbc -->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-jdbc</artifactId>
<version>5.3.16</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apereo.cas/cas-server-support-jdbc-drivers -->
<!-- 如果不想写下面的mysql驱动,可以写下面的这个,包括多种数据库的连接驱动,mysql,oracle,sql server -->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-jdbc-drivers</artifactId>
<version>5.3.16</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>mysql</groupId>-->
<!-- <artifactId>mysql-connector-java</artifactId>-->
<!-- <version>8.0.18</version>-->
<!-- </dependency>-->
</dependencies>
在application.properties
中添加相关配置。
# 数据库校验用户名 必须包含密码字段
cas.authn.jdbc.query[0].sql=select * from sys_user where username=?
# 上面sql查询的字段名
cas.authn.jdbc.query[0].fieldPassword=password
# 账号是否过期字段,1为过期,过期不可用
cas.authn.jdbc.query[0].fieldExpired=expired
# 账号是否可用,1为不可用
cas.authn.jdbc.query[0].fieldDisabled=disabled
# 数据库驱动
cas.authn.jdbc.query[0].driverClass=com.mysql.cj.jdbc.Driver
# 连接url
cas.authn.jdbc.query[0].url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
# 用户名
cas.authn.jdbc.query[0].user=root
# 密码
cas.authn.jdbc.query[0].password=xxxx
# 默认加密策略,默认NONE是不加密,下面配置使用md5
cas.authn.jdbc.query[0].passwordEncoder.type=DEFAULT
cas.authn.jdbc.query[0].passwordEncoder.characterEncoding=UTF-8
cas.authn.jdbc.query[0].passwordEncoder.encodingAlgorithm=MD5
创建相关表
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`expired` int(11) DEFAULT NULL,
`disabled` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 4
DEFAULT CHARSET = utf8mb4;
-- 插入模拟数据
INSERT INTO `sys_user`
VALUES ('1', 'lgq', 'e10adc3949ba59abbe56e057f20f883e', '0', '1');
INSERT INTO `sys_user`
VALUES ('2', 'admin', 'e10adc3949ba59abbe56e057f20f883e', '1', '0');
INSERT INTO `sys_user`
VALUES ('3', 'manager', 'e10adc3949ba59abbe56e057f20f883e', '0', '0');
配置完后,编译启动,访问https://test.cas.com:8443/cas/login
。
使用lgq登录,因为disabled
为1,所以会提示被禁用了。
使用admin登录,expired
字段值为1,提示过期。
使用manager登录成功。
自定义返回用户信息
TODO
使用新版本(gradle)
TODO