抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

单点登录

单点登录又称之为 Single Sign On, 简称SSO,实现基于用户会话的共享

  1. 提供用户效率
  2. 提高开发人员的效率
  3. 简化管理

单点登录的实现方式

  1. 单机部署:web单系统应用
  2. 分布式集群部署

基于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进行校验,校验则登录当前用户。

缺陷:

  1. cookie不安全
  2. 不能跨域实现免登录

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

评论