简单cas实现

???

CAS(Central Authentication Service) 即中央认证服务,也就是所谓的单点登录吧,大企业有多个系统的话,可以通过cas做到在一个系统登录,访问其他系统的时候直接就登录。

下面我们就简单实现以下cas,不用apache的那个。

准备

  1. 修改hosts

    1
    2
    3
    127.0.0.1 mtv
    127.0.0.1 music
    127.0.0.1 sso
  2. 准备两个前端项目
    简单的介绍下,就是打开页面的时候从cookies判断是否存在用户信息,如果不存在就跳转到单点登录的登录界面,如果url携带临时票据(cas返回的),就去cas验证临时票据的有效性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    data: {
    userIsLogin: false,
    userInfo: {},
    },
    created() {
    var me = this;
    // 通过cookie判断用户是否登录
    this.judgeUserLoginStatus();

    // http://mtv:8080/sso-mtv/index.html

    // 判断用户是否登录
    var userIsLogin = this.userIsLogin;
    if (!userIsLogin) {
    // 如果没有登录,判断一下是否存在tmpTicket临时票据
    var tmpTicket = app.getUrlParam("tmpTicket");
    console.log("tmpTicket: " + tmpTicket);
    if (tmpTicket != null && tmpTicket != "" && tmpTicket != undefined) {
    // 如果有tmpTicket临时票据,就携带临时票据发起请求到cas验证获取用户会话
    var serverUrl = app.serverUrl;
    axios.defaults.withCredentials = true;
    axios.post('http://sso:8090/verifyTmpTicket?tmpTicket=' + tmpTicket)
    .then(res => {
    if (res.data.status == 200) {
    var userInfo = res.data.data;
    console.log(res.data.data);
    this.userInfo = userInfo;
    this.userIsLogin = true;
    app.setCookie("user", userInfo);
    window.location.href = "http://mtv:8888/sso-mtv/index.html";
    } else {
    alert(res.data.msg);
    console.log(res.data.msg);
    }
    });
    } else {
    // 如果没有tmpTicket临时票据,说明用户从没登录过,那么就可以跳转至cas做统一登录认证了
    window.location.href = app.SSOServerUrl + "/login?returnUrl=http://mtv:8888/sso-mtv/index.html";
    }

    console.log(app.SSOServerUrl + "/login?returnUrl=" + window.location);
    }
    },
    methods: {
    // 通过cookie判断用户是否登录
    judgeUserLoginStatus() {
    var userCookie = app.getCookie("user");
    if (
    userCookie != null &&
    userCookie != undefined &&
    userCookie != ""
    ) {
    var userInfoStr = decodeURIComponent(userCookie);
    // console.log(userInfo);
    if (
    userInfoStr != null &&
    userInfoStr != undefined &&
    userInfoStr != ""
    ) {
    var userInfo = JSON.parse(userInfoStr);
    // 判断是否是一个对象
    if ( typeof(userInfo) == "object" ) {
    this.userIsLogin = true;
    // console.log(userInfo);
    this.userInfo = userInfo;
    } else {
    this.userIsLogin = false;
    this.userInfo = {};
    }
    }
    } else {
    this.userIsLogin = false;
    this.userInfo = {};
    }
    }
    }
    });

    这个是mtv,的music那个就改成music就好了!

  3. 准备一个springboot工程,依赖redis,thymeleaf

cas实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
@Controller
public class CasLoginController {

@Autowired
StringRedisTemplate redisTemplate;
private final String REDIS_TICKET = "redis_ticket";
private final String REDIS_USER = "redis_user";
private final String REDIS_TEMP_TICKET = "redis_temp_ticket";
private final String COOKIE_TICKET = "cookie_ticket";


@GetMapping("login")
public String login(String returnUrl, HttpServletRequest request, HttpServletResponse response, Model model){
model.addAttribute("returnUrl",returnUrl);
// 获取全局ticket
String globalTicket = getCookie(request, COOKIE_TICKET).getValue();
if(globalTicket!=null && !globalTicket.equals("")){
String userId = redisTemplate.opsForValue().get(REDIS_TICKET + ":" + globalTicket);
// 4.判断用户是否存在
if(userId!=null && !userId.equals("")){
String userInfo = redisTemplate.opsForValue().get(REDIS_USER + ":" + userId);
//判断用户会话是否存在
if(userInfo!=null && !userInfo.equals("")){
return "redirect:"+returnUrl+"?tmpTicket="+createTicket();
}
}
}
return "login";
}

@PostMapping("doLogin")
public String doLogin(String username,String password,String returnUrl,HttpServletRequest request, HttpServletResponse response, Model model) {
// 1.模拟登陆
if(username.equals("yuuki") && password.equals("123456")) {
// 1.1. 创建用户会话
redisTemplate.opsForValue().set(REDIS_USER+":"+"1","{\"username\":\"yuuki\"}");
// 2. 生成全局ticket 并放入cookie
String globalTicket = UUID.randomUUID().toString();
setCookie(COOKIE_TICKET,globalTicket,response);
// 3. 把用户放到redis
redisTemplate.opsForValue().set(REDIS_TICKET+":"+globalTicket,"1");
// 4. 创建临时ticket
String ticket = createTicket();
return "redirect:"+returnUrl+"?tmpTicket="+ticket;
}
model.addAttribute("returnUrl",returnUrl);
model.addAttribute("errMsg","用户名或密码错误");
return "login";
}

@PostMapping("verifyTmpTicket")
@ResponseBody
public Object verifyTmpTicket(String tmpTicket,HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> result = new HashMap<>();
// 1. 获取临时ticket
String tempTicket = redisTemplate.opsForValue().get(REDIS_TEMP_TICKET + ":" + tmpTicket);
// 2. 判断是否存在临时ticket
if(tempTicket!=null && !tempTicket.equals("")) {
// 3. 移除临时ticket
redisTemplate.delete(REDIS_TEMP_TICKET + ":" + tmpTicket);
String globalTicket = getCookie(request, COOKIE_TICKET).getValue();
String userId = redisTemplate.opsForValue().get(REDIS_TICKET + ":" + globalTicket);
// 4.判断用户是否存在
if(userId!=null && !userId.equals("")){
String userInfo = redisTemplate.opsForValue().get(REDIS_USER + ":" + userId);
//判断用户会话是否存在
if(userInfo!=null && !userInfo.equals("")){
result.put("status",200);
result.put("msg","success");
result.put("data",userInfo);
return result;
}
}
}
result.put("status",500);
result.put("msg","票据错误");
return result;
}


private String createTicket(){
String tempTicket = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(REDIS_TEMP_TICKET+":"+tempTicket,tempTicket,600, TimeUnit.SECONDS);
return tempTicket;
}

private void setCookie(String key,String value,HttpServletResponse response) {
Cookie cookie = new Cookie(key, value);
cookie.setDomain("sso");
cookie.setPath("/");
response.addCookie(cookie);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>cas 登录</title>
</head>
<body>
<h2>cas 登录</h2>
<form th:action="@{doLogin}" method="post" >
<label>用户名</label>
<input name="username"><br>
<label>密码</label>
<input type="password" name="password">
<div style="color:red;" th:text="${errMsg}"></div><br>
<input type="hidden" name="returnUrl" th:value="${returnUrl}">

<input type="submit" value="登录"><br>
</form>
</body>
</html>

直接贴代码自己看了- -

简单介绍下就是:

  1. 当前端cookies没有用户信息时就会重定向到cas的login接口
  2. login接口里会先去sso的cookies查找是否存在全局票据,如果存在接着校验全局票据有效性,并且返回一个临时票据给前端。否则跳转到cas的登录页
  3. 用户在cas登录后,校验完成后会生成全局票据存放在sso的cookies里面,并且将用户信息保存到redis,然后生成临时票据返回给前端。
  4. 前端接收到临时票据后,会将临时票据传递给cas校验,校验成功后会将用户信息返回,前端将用户信息保存到cookies中。

这就是简单的cas实现了,下次试试Apache的。

end