package com.imooc.controller;
import com.imooc.pojo.Users;
import com.imooc.pojo.vo.UsersVO;
import com.imooc.service.UserService;
import com.imooc.utils.IMOOCJSONResult;
import com.imooc.utils.JsonUtils;
import com.imooc.utils.MD5Utils;
import com.imooc.utils.RedisOperator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
@Controller
public class SSOController {
@Autowired
private UserService userService;
@Autowired
private RedisOperator redisOperator;
public static final String REDIS_USER_TOKEN = "redis_user_token";
public static final String REDIS_USER_TICKET = "redis_user_ticket";
public static final String REDIS_TMP_TICKET = "redis_tmp_ticket";
public static final String COOKIE_USER_TICKET = "cookie_user_ticket";
@GetMapping("/login")
public String login(String returnUrl,
Model model,
HttpServletRequest request,
HttpServletResponse response) {
model.addAttribute("returnUrl", returnUrl);
// 1. 获取userTicket门票,如果cookie中能够获取到,证明用户登录过,此时签发一个一次性的临时票据并且回跳
String userTicket = getCookie(request, COOKIE_USER_TICKET);
boolean isVerified = verifyUserTicket(userTicket);
if (isVerified) {
String tmpTicket = 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门票是否有效
String userId = redisOperator.get(REDIS_USER_TICKET + ":" + userTicket);
if (StringUtils.isBlank(userId)) {
return false;
}
// 2. 验证门票对应的user会话是否存在
String userRedis = redisOperator.get(REDIS_USER_TOKEN + ":" + userId);
if (StringUtils.isBlank(userRedis)) {
return false;
}
return true;
}
/**
* CAS的统一登录接口
* 目的:
* 1. 登录后创建用户的全局会话 -> uniqueToken
* 2. 创建用户全局门票,用以表示在CAS端是否登录 -> userTicket
* 3. 创建用户的临时票据,用于回跳回传 -> tmpTicket
*/
@PostMapping("/doLogin")
public String doLogin(String username,
String password,
String returnUrl,
Model model,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
model.addAttribute("returnUrl", returnUrl);
// 0. 判断用户名和密码必须不为空
if (StringUtils.isBlank(username) ||
StringUtils.isBlank(password)) {
model.addAttribute("errmsg", "用户名或密码不能为空");
return "login";
}
// 1. 实现登录
Users userResult = userService.queryUserForLogin(username,
MD5Utils.getMD5Str(password));
if (userResult == null) {
model.addAttribute("errmsg", "用户名或密码不正确");
return "login";
}
// 2. 实现用户的redis会话
String uniqueToken = UUID.randomUUID().toString().trim();
UsersVO usersVO = new UsersVO();
BeanUtils.copyProperties(userResult, usersVO);
usersVO.setUserUniqueToken(uniqueToken);
redisOperator.set(REDIS_USER_TOKEN + ":" + userResult.getId(),
JsonUtils.objectToJson(usersVO));
// 3. 生成ticket门票,全局门票,代表用户在CAS端登录过
String userTicket = UUID.randomUUID().toString().trim();
// 3.1 用户全局门票需要放入CAS端的cookie中
setCookie(COOKIE_USER_TICKET, userTicket, response);
// 4. userTicket关联用户id,并且放入到redis中,代表这个用户有门票了,可以在各个景区游玩
redisOperator.set(REDIS_USER_TICKET + ":" + userTicket, userResult.getId());
// 5. 生成临时票据,回跳到调用端网站,是由CAS端所签发的一个一次性的临时ticket
String tmpTicket = createTmpTicket();
/**
* userTicket: 用于表示用户在CAS端的一个登录状态:已经登录
* tmpTicket: 用于颁发给用户进行一次性的验证的票据,有时效性
*/
/**
* 举例:
* 我们去动物园玩耍,大门口买了一张统一的门票,这个就是CAS系统的全局门票和用户全局会话。
* 动物园里有一些小的景点,需要凭你的门票去领取一次性的票据,有了这张票据以后就能去一些小的景点游玩了。
* 这样的一个个的小景点其实就是我们这里所对应的一个个的站点。
* 当我们使用完毕这张临时票据以后,就需要销毁。
*/
// return "login";
return "redirect:" + returnUrl + "?tmpTicket=" + tmpTicket;
}
@PostMapping("/verifyTmpTicket")
@ResponseBody
public IMOOCJSONResult verifyTmpTicket(String tmpTicket,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
// 使用一次性临时票据来验证用户是否登录,如果登录过,把用户会话信息返回给站点
// 使用完毕后,需要销毁临时票据
String tmpTicketValue = redisOperator.get(REDIS_TMP_TICKET + ":" + tmpTicket);
if (StringUtils.isBlank(tmpTicketValue)) {
return IMOOCJSONResult.errorUserTicket("用户票据异常");
}
// 0. 如果临时票据OK,则需要销毁,并且拿到CAS端cookie中的全局userTicket,以此再获取用户会话
if (!tmpTicketValue.equals(MD5Utils.getMD5Str(tmpTicket))) {
return IMOOCJSONResult.errorUserTicket("用户票据异常");
} else {
// 销毁临时票据
redisOperator.del(REDIS_TMP_TICKET + ":" + tmpTicket);
}
// 1. 验证并且获取用户的userTicket
String userTicket = getCookie(request, COOKIE_USER_TICKET);
String userId = redisOperator.get(REDIS_USER_TICKET + ":" + userTicket);
if (StringUtils.isBlank(userId)) {
return IMOOCJSONResult.errorUserTicket("用户票据异常");
}
// 2. 验证门票对应的user会话是否存在
String userRedis = redisOperator.get(REDIS_USER_TOKEN + ":" + userId);
if (StringUtils.isBlank(userRedis)) {
return IMOOCJSONResult.errorUserTicket("用户票据异常");
}
// 验证成功,返回OK,携带用户会话
return IMOOCJSONResult.ok(JsonUtils.jsonToPojo(userRedis, UsersVO.class));
}
@PostMapping("/logout")
@ResponseBody
public IMOOCJSONResult logout(String userId,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
// 0. 获取CAS中的用户门票
String userTicket = getCookie(request, COOKIE_USER_TICKET);
// 1. 清除userTicket票据,redis/cookie
deleteCookie(COOKIE_USER_TICKET, response);
redisOperator.del(REDIS_USER_TICKET + ":" + userTicket);
// 2. 清除用户全局会话(分布式会话)
redisOperator.del(REDIS_USER_TOKEN + ":" + userId);
return IMOOCJSONResult.ok();
}
/**
* 创建临时票据
* @return
*/
private String createTmpTicket() {
String tmpTicket = UUID.randomUUID().toString().trim();
try {
redisOperator.set(REDIS_TMP_TICKET + ":" + tmpTicket,
MD5Utils.getMD5Str(tmpTicket), 600);
} catch (Exception e) {
e.printStackTrace();
}
return tmpTicket;
}
private void setCookie(String key,
String val,
HttpServletResponse response) {
Cookie cookie = new Cookie(key, val);
cookie.setDomain("sso.com");
cookie.setPath("/");
response.addCookie(cookie);
}
private void deleteCookie(String key,
HttpServletResponse response) {
Cookie cookie = new Cookie(key, null);
cookie.setDomain("sso.com");
cookie.setPath("/");
cookie.setMaxAge(-1);
response.addCookie(cookie);
}
private String getCookie(HttpServletRequest request, String key) {
Cookie[] cookieList = request.getCookies();
if (cookieList == null || StringUtils.isBlank(key)) {
return null;
}
String cookieValue = null;
for (int i = 0 ; i < cookieList.length; i ++) {
if (cookieList[i].getName().equals(key)) {
cookieValue = cookieList[i].getValue();
break;
}
}
return cookieValue;
}
}
===========前端==============
var index = new Vue({
el: "#mtv",
data: {
userIsLogin: false,
userInfo: {},
},
created() {
var me = this;
// 通过cookie判断用户是否登录
this.judgeUserLoginStatus();
// http://www.mtv.com: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://www.sso.com: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", JSON.stringify(userInfo));
window.location.href = "http://www.mtv.com:8080/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://www.mtv.com:8080/sso-mtv/index.html";
}
console.log(app.SSOServerUrl + "/login?returnUrl=" + window.location);
}
},
methods: {
logout() {
var userId = this.userInfo.id;
axios.defaults.withCredentials = true;
axios.post('http://www.sso.com:8090/logout?userId=' + userId)
.then(res => {
if (res.data.status == 200) {
var userInfo = res.data.data;
console.log(res.data.data);
this.userInfo = {};
this.userIsLogin = false;
app.deleteCookie("user");
alert("退出成功!");
} else {
alert(res.data.msg);
console.log(res.data.msg);
}
});
},
// 通过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 = {};
}
}
}
});
============app.js===============
window.app = {
SSOServerUrl: "http://www.sso.com:8090",
cookieDomain: ".mtv.com",
getCookie: function (cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
// console.log(c)
while (c.charAt(0) == ' ') c = c.substring(1);
if (c.indexOf(name) != -1){
return c.substring(name.length, c.length);
}
}
return "";
},
goErrorPage() {
window.location.href = "http://www.imooc.com/error/noexists";
},
setCookie: function(name, value) {
var Days = 365;
var exp = new Date();
exp.setTime(exp.getTime() + Days*24*60*60*1000);
var cookieContent = name + "="+ encodeURIComponent (value) + ";path=/;";
if (this.cookieDomain != null && this.cookieDomain != undefined && this.cookieDomain != '') {
cookieContent += "domain=" + this.cookieDomain;
}
document.cookie = cookieContent + cookieContent;
// document.cookie = name + "="+ encodeURIComponent (value) + ";path=/;domain=" + cookieDomain;//expires=" + exp.toGMTString();
},
deleteCookie: function(name) {
var cookieContent = name + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
if (this.cookieDomain != null && this.cookieDomain != undefined && this.cookieDomain != '') {
cookieContent += "domain=" + this.cookieDomain;
}
document.cookie = cookieContent;
},
getUrlParam(paramName) {
var reg = new RegExp("(^|&)" + paramName + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象
var r = window.location.search.substr(1).match(reg); //匹配目标参数
if (r != null) return decodeURI(r[2]); return null; //返回参数值
},
/**
* 构建购物车商品对象
*/
ShopcartItem: function(itemId, itemImgUrl, itemName, specId, specName, buyCounts, priceDiscount, priceNormal) {
this.itemId = itemId;
this.itemImgUrl = itemImgUrl;
this.itemName = itemName;
this.specId = specId;
this.specName = specName;
this.buyCounts = buyCounts;
this.priceDiscount = priceDiscount;
this.priceNormal = priceNormal;
},
addItemToShopcart(pendingItem) {
// 判断有没有购物车,如果没有购物车,则new 一个购物车list
// 如果有购物车,则直接把shopcartItem丢进去
var foodieShopcartCookie = this.getCookie("shopcart");
var foodieShopcart = [];
if (foodieShopcartCookie != null && foodieShopcartCookie != "" && foodieShopcartCookie != undefined) {
var foodieShopcartStr = decodeURIComponent(foodieShopcartCookie);
foodieShopcart = JSON.parse(foodieShopcartStr);
var isHavingItem = false;
// 如果添加的商品已经存在与购物车中,则购物车中已经存在的商品数量累加新增的
for(var i = 0 ; i < foodieShopcart.length ; i ++) {
var tmpItem = foodieShopcart[i];
var specId = tmpItem.specId;
if (specId == pendingItem.specId) {
isHavingItem = true;
var newCounts = tmpItem.buyCounts + pendingItem.buyCounts;
tmpItem.buyCounts = newCounts;
// 删除主图在数组中的位置
foodieShopcart.splice(i, 1, tmpItem);
}
}
if (!isHavingItem) {
foodieShopcart.push(pendingItem);
}
} else {
foodieShopcart.push(pendingItem);
}
this.setCookie("shopcart", JSON.stringify(foodieShopcart));
},
/**
* 获得购物车中的数量
*/
getShopcartItemCounts() {
// 判断有没有购物车,如果没有购物车,则new 一个购物车list
// 如果有购物车,则直接把shopcartItem丢进去
var foodieShopcartCookie = this.getCookie("shopcart");
var foodieShopcart = [];
if (foodieShopcartCookie != null && foodieShopcartCookie != "" && foodieShopcartCookie != undefined) {
var foodieShopcartStr = decodeURIComponent(foodieShopcartCookie);
foodieShopcart = JSON.parse(foodieShopcartStr);
}
return foodieShopcart.length;
},
/**
* 获得购物车列表
*/
getShopcartList() {
// 判断有没有购物车,如果没有购物车,则new 一个购物车list
// 如果有购物车,则直接把shopcartItem丢进去
var foodieShopcartCookie = this.getCookie("shopcart");
var foodieShopcart = [];
if (foodieShopcartCookie != null && foodieShopcartCookie != "" && foodieShopcartCookie != undefined) {
var foodieShopcartStr = decodeURIComponent(foodieShopcartCookie);
foodieShopcart = JSON.parse(foodieShopcartStr);
}
return foodieShopcart;
},
checkMobile(mobile) {
var myreg = /^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1}))+\d{8})$/;
if (!myreg.test(mobile)) {
return false;
}
return true;
},
checkEmail(email) {
var myreg = /^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/;
if (!myreg.test(email)) {
return false;
}
return true;
},
}