登录
原创

cas源码实现

发布于 2021-04-08 阅读 26
  • Java
原创

本文实现为学习B站艾编程架构师项目实战教学:阿里淘宝天猫单点登录项目实战教学的笔记,如有侵权,请联系我删除

完整代码链接

https://github.com/micro-cloud-fly/cas-sys

cas单点登录的源码实现

本文所示内容,使用springboot简化开发,没有使用cas单点登录server端以及client端的第三方包,旨在通过代码梳理cas单点登录的原理

hosts配置

127.0.0.1       www.sso.com  # 统一认证中心
127.0.0.1       www.tb.com   # 模拟淘宝客户端
127.0.0.1       www.tm.com   # 模拟天猫客户端

项目架构说明

  • cas-sys是整个项目的父工程
  • cas-server继承于cas-sys,是cas单点登录的统一认证中心
  • taobao继承于cas-sys,是cas单点登录的一个客户端
  • tmall继承于cas-sys,是cas单点登录的一个客户端

如何运行

  1. 分别启动三个子工程
  2. 访问淘宝
  3. 访问天猫
  4. 天猫或者淘宝任意一个登录
  5. 再次访问没有登录的那一个客户端

项目设计解析

当访问淘宝的 http://www.tb.com:8082/taobao 地址时,由于淘宝设置了全局的拦截器,此时会走到
interceptor包内的LoginInterceptor的preHandler方法,此时会首先检查session中是否存在isLogin
标识,如果存在了,证明已经登录过了,那么拦截器返回true,放行这个请求。但是第一次请求的时候,session
中是肯定不存在登录信息的。在这种情况下,就要去重定向到统一认证中心这个项目,请求的地址是checkLogin
在服务端的checkLogin这个方法中,会判断session里面有没有token信息,如果没有,就会重定向到服务端的
登录页面,这里面的关键点是,淘宝请求到checkLogin的时候,会携带一个参数,告诉服务端我是从哪里来的,
checkLogin这个方法如果检测到没有token信息进行重定向到登录页面的时候,也会携带这个参数redirectUrl,
在登录页面,进行登录的时候时候,在login方法中,也会携带这个参数。

接下来是login方法,在这个方法中,首先会校验用户名和密码,如果验证通过了,则会发放一个token给淘宝这个客户端,
然后重定向到淘宝,淘宝这个客户端到拦截器又拦截了这个请求,此时还是没有登录信息,那么会去校验这个请求里面
是否有token这个参数,此时确实是存在了token,那么他会带着token再去cas统一认证中心去验证,这个token是否
正确,假如正确的话,那么就说明验证通过了,此时会在淘宝客户端记录一个session证明登录成功了,然后放行重定向
的请求。

此时登录天猫的时候,流程同淘宝,但是当天猫的客户端重定向到统一认证中心的时候,会发现这个浏览器已经存在了
一个登录的session,在session中记录了token,于是天猫客户端也拿着token去统一认证中心去校验,此时流程
就和上面介绍的淘宝一样了,因此此实现了免登录即可访问。

原理图解析

在这里插入图片描述

代码部分

目录结构

cas-sys
├── cas-server
│   ├── HELP.md
│   ├── cas-server.iml
│   ├── mvnw
│   ├── mvnw.cmd
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── cn
│       │   │       └── juhe
│       │   │           ├── CasServerApplication.java
│       │   │           ├── controller
│       │   │           │   └── ServerController.java
│       │   │           └── db
│       │   │               └── MockDB.java
│       │   └── resources
│       │       ├── application.properties
│       │       └── templates
│       │           ├── index.html
│       │           └── login.html
│       └── test
│           └── java
│               └── cn
│                   └── juhe
│                       └── CasServerApplicationTests.java
├── cas-sys.iml
├── cas.png
├── pom.xml
├── readme.md
├── taobao
│   ├── HELP.md
│   ├── mvnw
│   ├── mvnw.cmd
│   ├── pom.xml
│   ├── src
│   │   ├── main
│   │   │   ├── java
│   │   │   │   └── cn
│   │   │   │       └── juhe
│   │   │   │           ├── TaobaoApplication.java
│   │   │   │           ├── config
│   │   │   │           │   └── LoginConfig.java
│   │   │   │           ├── controller
│   │   │   │           │   └── IndexController.java
│   │   │   │           └── interceptor
│   │   │   │               └── LoginInterceptor.java
│   │   │   └── resources
│   │   │       ├── application.properties
│   │   │       ├── static
│   │   │       └── templates
│   │   │           ├── index.html
│   │   │           └── taobao.html
│   │   └── test
│   │       └── java
│   │           └── cn
│   │               └── juhe
│   │                   └── TaobaoApplicationTests.java
│   └── taobao.iml
└── tmall
    ├── HELP.md
    ├── mvnw
    ├── mvnw.cmd
    ├── pom.xml
    ├── src
    │   ├── main
    │   │   ├── java
    │   │   │   └── cn
    │   │   │       └── juhe
    │   │   │           ├── TmallApplication.java
    │   │   │           ├── config
    │   │   │           │   └── LoginConfig.java
    │   │   │           ├── controller
    │   │   │           │   └── IndexController.java
    │   │   │           └── interceptor
    │   │   │               └── LoginInterceptor.java
    │   │   └── resources
    │   │       ├── application.properties
    │   │       └── templates
    │   │           ├── index.html
    │   │           └── tmall.html
    │   └── test
    │       └── java
    │           └── cn
    │               └── juhe
    │                   └── TmallApplicationTests.java
    └── tmall.iml

cas-server 核心代码

package cn.juhe.controller;

import cn.juhe.db.MockDB;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpSession;
import java.util.UUID;

@Controller
@Slf4j
public class ServerController {

    @RequestMapping("/index")
    public String index(String redirectUrl, Model model) {
        return "login";
    }

    @RequestMapping("/login")
    public String login(String username, String password, String redirectUrl, HttpSession session, Model model) {
        //校验用户名和密码是否合法,如果合法,则发送一个令牌给这个用户,这个用户收到令牌之后,再过来请求看看这个令牌是否正确
        if ("admin".equals(username) && "admin".equals(password)) {
            //发放一个ticket
            log.info("用户名密码验证成功。。。");
            String token = UUID.randomUUID().toString();
            session.setAttribute("token", token);
            MockDB.T_TOKEN.add(token);//把这个令牌存到数据库当中
            //保存这个用户的session
            //如果登录成功了,则需要重定向到来时候到路,并且携带上这个生成到ticket,
            model.addAttribute("token", token);
            log.info("ticket:{}", token);
            System.out.println("redirectUrl:" + redirectUrl);
            return "redirect:" + redirectUrl + "?token=" + token;
        }
        model.addAttribute("redirectUrl", redirectUrl);
        return "login";

    }

    @RequestMapping("checkLogin")
    public String checkLogin(String redirectUrl, HttpSession session, Model model) {
        String token = (String) session.getAttribute("token");

        log.info("checkLogin.token:{}", token);
        if (StringUtils.isEmpty(token)) {
            //如果token是空,那么这个请求肯定是没有登录成功过的,则引导其去登录页面
            model.addAttribute("redirectUrl", redirectUrl);
            return "login";
        }
        model.addAttribute("token", token);
        //如果不为空,则这个请求从哪里来,就让他回到哪里去
        return "redirect:" + redirectUrl + "?token=" + token;
    }

    @RequestMapping("/verify")
    @ResponseBody
    public String verifyToken(String token) {
        if (MockDB.T_TOKEN.contains(token)) {
            return "true";
        }
        return "false";
    }
}

客户端核心代码

package cn.juhe.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class IndexController {

    @RequestMapping("/taobao")
    public String index() {
        System.out.println("taobao");
        return "taobao";
    }
}
package cn.juhe.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    @Value("${cas.server.url.prefix}")
    private String casServerUrl;


    @Value("${cas.client.url}")
    private String casClientUrl;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("casServerUrl:{}", casServerUrl);
        log.info("casClientUrl:{}", casClientUrl);
        //首先检查,有没有登录过,从session中获取
        HttpSession session = request.getSession();
        Boolean isLogin = (Boolean) session.getAttribute("loginFlag");
        if (isLogin != null && isLogin) {
            //如果有session存在,证明已经登录成功了,则可以访问首页
            return true;
        } else {
            //如果没有登录过,但是请求携带了token,那么就判定为是用户认证中心发过来的
            String token = request.getParameter("token");
            log.info("token:{}", token);
            if (!StringUtils.isEmpty(token)) {
                //如果用户认证中心传过来了token,那么此时需要再去用户认证中心去验证这个token是否是正确的
                RestTemplate restTemplate = new RestTemplate();
                String forObject = restTemplate.getForObject(casServerUrl + "verify?token=" + token, String.class);
                //如果用户认证中心说这个token是正确的,那么就确定登录成功了,此时设置 session
                log.info("forObject:{}", forObject);
                if ("true".equals(forObject)) {
                    session.setAttribute("loginFlag", true);
                    return true;
                }
            }
            //如果session不存在,则让其跳转到用户认证中心,并且告诉用户认证中心,跳转回来到地址是我淘宝
            response.sendRedirect(casServerUrl + "checkLogin?redirectUrl=" + casClientUrl + "/taobao");
            return false;
        }

    }
}

评论区

眉上的汗水,眉下的泪水,你总要选择一样

0

0

0