задний план
Одиночный вход также известен как единый знак, называемый SSO, единственным входом можно основываться на общем пользовательском сеансе, он был ничего двум, первым первым взглядом, это его принцип - это достичь распределенного сеанса.
Например, теперь есть доменное имя первого уровня.www.imooc.com, является образовательным веб-сайтом, но у MOOC есть и другие линейки продуктов, которые могут предоставлять услуги пользователям путем создания доменных имен второго уровня, таких как: music.imooc.com , blog.imooc.com и т. д. соответственно для музыки MOOC и Для МООК-блогов и т. д. пользователям необходимо авторизоваться только на одном из сайтов, после чего авторизуются и другие сайты.
То есть после того, как пользователь авторизуется только под определенным веб-сайтом от начала до конца, сгенерированная им сессия используется совместно с другими веб-сайтами.После реализации одноточечного входа на веб-сайт он также косвенно входит на другие веб-сайты в то же самое время. время, то на самом деле это веб-сайт с одной точкой. Нажмите, чтобы войти, их сеансы являются общими, и все они являются одним и тем же сеансом пользователя.
Cookie + Redis реализуют SSO
Затем реализованный нами ранее сервер распределенного сеанса основан на Redis, поэтому сеанс может проходить к любой системе в серверной части, и можно получить информацию о пользовательских данных в кеше.Таким образом, информация о идентификаторе пользователя и токене в файле cookie может быть перенесены при отправке запроса, чтобы его можно было получить после запроса задней части с передней части Таким образом, фактически пользователь находится в определенном После входа в систему и регистрации на одном конце, по сути, оба файла cookie и Redis будет содержать информацию о пользователе.Пока пользователь не выходит из системы, вход может быть достигнут на любом сайте.
Затем этот принцип в основном также зависит от файлов cookie и веб-сайтов, доменных имен верхнего уровня.www.imooc.comЗначение файла cookie с *.imooc.com может быть общим и может быть перенесено на серверную часть, например, для него установлено значение .imooc.com, .t.mukewang.com, так что все в порядке.
Независимый файл cookie доменного имени второго уровня не может использоваться совместно и не может быть получен другими доменными именами второго уровня. влияют друг на друга. Чтобы поделиться, он должен быть установлен на .imooc.com.
Что делать, если доменное имя верхнего уровня отличается?
Единый вход в предыдущем разделе основан на том же имени домена верхнего уровня, что, если имя домена верхнего уровня другое? Напримерwww.imooc.comЧтобы поделиться с сессией www.mukewang.com, что мне делать в это время? ! Как показано на рисунке ниже, в настоящее время из-за разных доменных имен верхнего уровня файл cookie не может быть междоменным. Каждый сайт запрашивает сервер, и файл cookie не может быть синхронизирован. Например, после того, как пользователь под www.imooc.com инициирует запрос, будет файл cookie, но он снова зайдет на www.abc.com.Поскольку файл cookie не может быть перенесен, вам будет предложено снова войти в систему.
Итак, как реализовать единый вход при столкновении с разными доменными именами верхнего уровня? Обратимся к следующей картинке:
Как показано на рисунке выше, вход между несколькими системами будет проверяться через независимую систему входа, которая эквивалентна посреднической компании, объединяющей всех. Затем это называется системой CAS.Полное название CAS – Центральная служба аутентификации, которая является центральной службой аутентификации.Это решение для единого входа, которое можно использовать для единого входа между разными доменными именами верхнего уровня. .
Анализ процесса
Когда пользователь входит в систему в первый раз, процесс выглядит следующим образом:
1) Браузер пользователя должен войти в ограниченные ресурсы для доступа к системе A. В это время выполняется проверка входа, и обнаруживается, что вход не выполнен. Затем выполняется операция по получению билета. выполнено, и обнаруживается, что билета нет.
2) Система А обнаруживает, что запрос должен быть авторизован, перенаправляет запрос в центр аутентификации и получает глобальную операцию билета, если нет, авторизуется.
3) Центр аутентификации представляет страницу входа, и пользователь входит в систему. После успешного входа центр аутентификации перенаправляет запрос в систему А и прикрепляет токен аутентификации. В это время центр аутентификации генерирует глобальный билет по адресу в то же время.
4) В это время снова выполняется проверка входа в систему, и обнаруживается, что пользователь не вошел в систему, а затем снова выполняется операция билета.В это время билет (токен) может быть получен.Система А связывается с центром аутентификации, чтобы убедиться, что токен действителен, что доказывает, что пользователь вошел в систему.
5) Система А возвращает ограниченные ресурсы пользователю
Когда вошедший в систему пользователь получает доступ к системе B в группе приложений в первый раз:
1) Браузер должен авторизоваться на ресурсе с ограниченным доступом для доступа к другому приложению B. В это время выполняется проверка логина, и обнаруживается, что он не авторизован, а затем выполняется операция получения билета, и оказалось, что билета нет.
2), система B обнаруживает, что запрос требует входа в систему, перенаправляет запрос в центр аутентификации, получает операцию глобального билета и получает глобальный билет, который можно получить, и центр аутентификации обнаруживает, что он вошел в систему. .
3) Центр аутентификации выдает временный билет (токен) и перенаправляет в систему B с токеном.
4) В это время снова выполняется проверка входа в систему, и обнаруживается, что пользователь не вошел в систему, а затем снова выполняется операция билета.В это время билет (токен) может быть получен.Система B связывается с центром аутентификации, чтобы убедиться, что токен действителен, что доказывает, что пользователь вошел в систему.
5) Система B возвращает ограниченный ресурс клиенту.
Глобальное значение законопроекта - определить, приземлился ли пользователь в Центре сертификации.
Смысл временного билета состоит в том, чтобы выдать пользователю аутентификацию при входе в систему.
код
@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;
}
}
Ссылка на ссылку:
blog.CSDN.net/no_game_no_…
у-у-у. Краткое описание.com/afraid/75EDC C05 ах…