1 Обзор
Полное название SSO на английском языке — Single Sign On. SSO заключается в том, что в системах с несколькими приложениями пользователям необходимо войти в систему только один раз, чтобы получить доступ ко всем взаимно доверенным системам приложений. Например, Tmall и Taobao заходят на страницу входа, и оба требуют, чтобы вы вошли в систему. Теперь, после входа в систему на Taobao, вы можете обновить данные непосредственно на Tmall, и вы обнаружите, что уже вошли в систему.
2. Принципиальная схема реализации sso
1. Принципиальная схема этой демонстрации
2. Схема 2
3. Возникшие проблемы
1. Та же политика происхождения
Политика того же происхождения — хорошо известная политика безопасности, предложенная Netscape. Все браузеры, поддерживающие JavaScript, теперь используют эту стратегию. Так называемый гомологичный означает, что доменное имя, протокол и порт совпадают. Когда две вкладки браузера открыты соответственно страницы Baidu и Google Когда вкладка браузера Baidu выполняет скрипт, она проверяет, к какой странице он принадлежит. То есть для проверки того же происхождения, будут выполняться только скрипты с тем же происхождением, что и Baidu. Если он не из того же источника, при запросе данных браузер сообщит об исключении в консоли, указывающем, что доступ запрещен. Политика того же происхождения — это поведение браузера для защиты локальных данных от загрязнения данными, полученными кодом JavaScript, поэтому он перехватывает данные, полученные из запроса, отправленного клиентом, то есть запрос отправляется и сервер отвечает, но не может быть Браузер получает.
2. домен cookie
Домен файла cookie (обычно соответствующий доменному имени веб-сайта). Когда браузер отправляет HTTP-запрос, он автоматически передает файл cookie, соответствующий домену, а не все файлы cookie.
решение
-
1. Используйте обратный прокси-сервер nginx для подключения всех сервисов к одному источнику.
-
2. Вход в систему для аутентификации успешно создает сеансы для всех служб (пустая трата ресурсов).
-
3. Междоменное перенаправление файлов cookie с синхронизацией параметров
Файл cookie сначала помещается в домен, и запрос, который должен войти в систему, обращается к этому домену, чтобы получить параметры в этом домене.Когда параметры будут получены, перенаправление будет ссылаться на исходный системный домен.
[Схема этой демонстрации]
3. Междоменный запрос
Доступ к ресурсам, не относящимся к тому же домену, будет запрещен из-за ограничений безопасности браузера.
springboot разрешает междоменные запросы
4. После того, как redisTemplate запишет значение redis для установки времени истечения срока действия, полученные данные получат управляющие символы и не могут быть преобразованы в объекты Bean.
решение
заменить управляющие символы
replaceAll("[\\x00-\\x09\\x11\\x12\\x14-\\x1F\\x7F]","");
5. RedisTemplate сталкивается с проблемой невозможности сериализации с использованием сериализатора jdkSerializeable по умолчанию.
решение
Настройте redisTemplate для использования сериализатора StringRedisSerializer.
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public RedisTemplate<String,String> redisTemplate(){
RedisTemplate<String,String> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}
6. Компонент не может быть введен с помощью @Autowired в перехватчике spring.
решение
Когда перехватчик инициализирован, перехватчик сначала управляется Spring, и bean-компонент будет внедрен в перехватчик.
/**
* 拦截器初始化配置
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
/**
* 解决拦截器不能注入bean 问题
* @return
*/
@Bean
WebInterceptor WebInterceptor(){
return new WebInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(WebInterceptor()).addPathPatterns("/**");
}
}
4. Процесс реализации
1. Создайте интегрированный redisTemplate с весенней загрузкой для предоставления службы redis.
1. Измените pom, чтобы добавить зависимости
pom в основном увеличивает зависимости
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Единая зависимость облачного сервиса
<!-- Spring Cloud eureka Begin -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- zipkin begin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<!-- Spring Cloud eureka End -->
<!-- config begin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- admin begin-->
<dependency>
<groupId>org.jolokia</groupId>
<artifactId>jolokia-core</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>${spring-cloud-admin.version}</version>
</dependency>
<!--feign Begin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--feign End-->
<!-- config获取不到配置时自动重试 begin-->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- config获取不到配置时自动重试 end-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
2. application.yml настраивает параметры Redis
#redis配置
spring:
redis:
host: xxx.xxx.xxx.xxx
port: 6379
lettuce:
pool:
max-active: 8
max-idle: 8
max-wait: -1ms
min-idle: 0
3. Измените сериализатор redisTemplate по умолчанию.
В демонстрации, поскольку используется сериализатор jdkSerializeable по умолчанию, сериализатор не может быть сериализован, поэтому сериализатор заменяется.
StringRedisSerializer преобразует только между байтом и строкой
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public RedisTemplate<String,String> redisTemplate(){
RedisTemplate<String,String> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}
4. Создайте сервис redis restfull
@RestController
public class RedisController {
@Autowired
private RedisTemplate redisTemplate;
/**
* 取值
* @param key
* @return
*/
@RequestMapping(value = "get")
public String get(String key){
String value;
try {
value = (String) redisTemplate.opsForValue().get(key);
if(StringUtils.isNotBlank(value)){
//替换控制字符
value = value.replaceAll("[\\x00-\\x09\\x11\\x12\\x14-\\x1F\\x7F]","");
}
}catch (Exception e){
e.printStackTrace();
return null;
}
return value;
}
/**
* 写值
* @param key
* @param value
* @param seconds
* @return
*/
@RequestMapping(value = "put")
public String put(String key,String value,@RequestParam(required = false) Long seconds){
try {
if (seconds == null){
redisTemplate.opsForValue().set(key,value);
}else {
redisTemplate.opsForValue().set(key,value,seconds);
}
}catch (Exception e){
e.printStackTrace();
return "ERROR";
}
return "OK";
}
}
2. Создайте единый центр аутентификации sso
1. Реализация метода входа
Поскольку vue и центр аутентификации больше не находятся в одном домене, файл cookie нельзя использовать совместно, поэтому токен помещается в параметр и возвращается напрямую.
/**
* 登录
*
* @param sysUser
* @return
*/
@RequestMapping(value = "login")
public Map<String, Object> login(@RequestBody SysUser sysUser, HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> resultMap = new HashMap<>();
try {
if (sysUser != null && StringUtils.isNotBlank(sysUser.getUserName()) && StringUtils.isNotBlank(sysUser.getPassword())) {
SysUser result = sysUserService.getUserByLoginName(sysUser);
//登录成功
if (result != null && StringUtils.isNotBlank(result.getPassword()) && sysUser.getPassword().equals(result.getPassword())) {
//登录信息存入redis
String token = UUID.randomUUID().toString();
String userJson = JSON.toJSONString(result);
String flag = loginService.redisPut(token, userJson, 60*60*2L);
if("ERROR".equals(flag)){
throw new RuntimeException("redis调用异常1");
}
resultMap.put("code", "1");
resultMap.put("data", result);
//返回token 值
resultMap.put("token",token);
} else {
resultMap.put("code", "-1");
resultMap.put("message", "用户或密码错误");
}
} else {
resultMap.put("code", "-99");
resultMap.put("message", "参数错误");
}
return resultMap;
} catch (Exception e) {
e.printStackTrace();
resultMap.clear();
resultMap.put("code", "-999");
resultMap.put("message", "系统错误稍后重试");
return resultMap;
}
}
3. Код ключа Vue
глобальный метод vue
//设置cookie
Vue.prototype.setCookie = function(c_name,value,expiredays) {
var exdate=new Date()
exdate.setDate(exdate.getDate()+expiredays)
document.cookie=c_name+ "=" +escape(value)+
((expiredays==null) ? "" : ";expires="+exdate.toGMTString())
};
//获取cookie
Vue.prototype.getCookie=function(c_name) {
if (document.cookie.length>0)
{
var c_start=document.cookie.indexOf(c_name + "=")
if (c_start!=-1)
{
c_start=c_start + c_name.length+1
var c_end=document.cookie.indexOf(";",c_start)
if (c_end==-1) c_end=document.cookie.length
return unescape(document.cookie.substring(c_start,c_end))
}
}
return ""
};
//获取url中的参数
Vue.prototype.getUrlKey=function(name) {
return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.href) || [, ""])[1].replace(/\+/g, '%20')) || null
};
Запишите токен, возвращенный центром аутентификации, в файл cookie.
//将token 写入cookie
this.setCookie("token",repos.data.token);
ключевой код vue
<template>
</template>
<script>
export default {
name: "SsoIndex",
//钩子函数用于同步不同域之间的cookie 同步
beforeCreate:function () {
let token = this.getCookie("token");
let url = this.getUrlKey("redirect");
//如果有存在token则直接响应给后台
if(token){
location.href = url+"?token="+token;
}
//否则返回不存在
else{
location.href = url+"?token=not";
}
}
}
</script>
<style scoped>
</style>
4. Система A, код перехватчика системы B
код перехватчика инициализации конфига
**
* 拦截器初始化配置
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
/**
* 解决拦截器不能注入bean 问题
* @return
*/
@Bean
WebInterceptor WebInterceptor(){
return new WebInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(WebInterceptor()).addPathPatterns("/**");
}
}
код перехватчика
/***
* 未登录请求拦截
*/
@Component
public class WebInterceptor implements HandlerInterceptor {
@Autowired
private RedisService redisService;
/**
* 未执行请求方法前拦截
* @param request
* @param response
* @param handler
* @return
* @throws IOException
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
SysUser sysUser = (SysUser) request.getSession().getAttribute("loginUser");
//子系统不存在局部会话 尝试获取统一认证中心会话信息
if(sysUser == null){
String token = request.getParameter("token");
//如果没有token到统一认证页获取
if(StringUtils.isBlank(token)){
response.sendRedirect("http://localhost:8080/ssoIndex?redirect="+request.getRequestURL());
return false;
}
//如果token 等于not 说明未登录 跳转sso登录
else if("not".equals(token)){
response.sendRedirect("http://localhost:8080/login?redirect="+request.getRequestURL());
return false;
}
//根据 token 获取redis 登录数据
String json = redisService.redisGet(token);
//token 有效已登录
if(StringUtils.isNotBlank(json)){
try {
SysUser user = MapperUtils.json2pojo(json,SysUser.class);
//创建局部会话信息
request.getSession().setAttribute("loginUser",user);
} catch (Exception e) {
e.printStackTrace();
}
}
//验证局部会话是否创建完毕
sysUser = (SysUser) request.getSession().getAttribute("loginUser");
//没有局部会话说明认证失效跳转sso重新认证
if(sysUser == null){
response.sendRedirect("http://localhost:8080/login?redirect="+request.getRequestURL());
return false;
}
}
return true;
}
}
5. Осознайте эффект
1. Система 1
2. Система 2
Поскольку система 1 уже зарегистрирована, перейдите непосредственно к успешному входу в систему.