单点登录
==============================================================================
单点登录三种常见的方式
已知每个微服务都有自己的tomcat,session是不共享的,为了解决多模块共享数据和登录问题
session广播:sessoin复制
淘汰
- 使用cookie+redis实现
:cookie客户版技术,每次发送请求,带着cookie发送,redis基于key-vaule存储
- 使用token
:按照一定规则生成字符串,字符串包含用户信息
==============================================================================
使用cookie+redis实现
在项目中任何一个模块,登陆后,把数据放到2个地方
步骤
1.redis 中
key
:生成唯一随机值(IP,用户等信息) ,value
:用户数据
2. cookie:吧redis
里面生成的key值放到cookie
中
3. 当其他模块访问,发送带cookie
的请求时,获取cookie
值,拿cookie
值去redis
中查询,也就是key
,查到就可以返回正常数据,没有就是null
构建
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
yml
平时redis怎么设置端口和ip就怎么设置
操作
当你使用
HttpSession
session中的
session.setAttribute();就可以实现redis存储,读取也是从
session.getAttribute()`中读取
==============================================================================
使用token
- 给登陆服务器留下登录痕迹
- 登录服务器要将token放到重定向url地址上
- 其他系统要处理url地址上的关键token,只要有,则将token对应的用户保存到自己的分布式session中
- 自己的系统将用户保存在自己的会话中
demo测试
单点登录服务器:
@Controller
public class loginController {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 根据token去redis中获取用户信息
* @param token
* @return
*/
@ResponseBody
@GetMapping("/info")
public String getUserInfo(@RequestParam("token")String token){
String s = redisTemplate.opsForValue().get(token);
return s;
}
/**
* 感知登录功能
* @param url 客户端的地址
* @param model
* @param sso_token
* @return
*/
@GetMapping("/login.html")
public String loginPage(@RequestParam("redirect_url") String url,
Model model,
@CookieValue(value = "sso_token",required = false)String sso_token) {
if(!StringUtils.isEmpty(sso_token)){
//说明之前有人登录过,留下痕迹
return "redirect:" + url+"?token="+sso_token;
}
model.addAttribute("url", url);
return "login";
}
/**
*
* @param username
* @param password
* @param url 客户端的地址
* @param response 设置cookie信息,做感知准备
* @return
*/
@PostMapping("/doLogin")
public String doLogin(@RequestParam("username") String username,
@RequestParam("password") String password,
@RequestParam("url") String url,
HttpServletResponse response) {
//模拟登录成功
if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
//user save
String uuid = UUID.randomUUID().toString().replace("-", "");
//redis中存放token
redisTemplate.opsForValue().set(uuid,username);
//当前域名下的cookie存放token信息给@GetMapping("/login.html")做感知登录,
//1. 给登陆服务器留下登录痕迹
response.addCookie(new Cookie("sso_token", uuid));
//登录成功跳转到之前的页面
//2. 登录服务器要将token放到重定向url地址上
return "redirect:" + url+"?token="+uuid;
}
return "login";
}
}
多系统客户端模拟:
@Controller
public class hellocontroller {
@Value("${sso.server.url}")
private String ssoServerUrl;
/**
* 测试无需登录
* @return
*/
@ResponseBody
@GetMapping("/hello")
public String hello(){
return "hello";
}
/**
* 客户端
* @param model
* @param session
* @param token
* @return
*/
@GetMapping("/employees")
public String employees(Model model,
HttpSession session,
@RequestParam(value = "token",required = false) String token){
//3. 客户系统要处理url地址上的关键token,只要有,则将token对应的用户保存到自己的分布式session中
if(!StringUtils.isEmpty(token)){
RestTemplate restTemplate = new RestTemplate();
//发送请求给单点登陆服务器获取用户信息
ResponseEntity<String> forEntity = restTemplate.getForEntity("http://sso.com:8080/info?token=" + token, String.class);
String body = forEntity.getBody();
//4. 自己的系统将用户保存在自己的会话中
session.setAttribute("loginUser",body);
}
Object user = session.getAttribute("loginUser");
if(user==null){
//没有登录,跳转到登录服务器登录
return "redirect:"+ssoServerUrl+"?redirect_url=http://client1.com:8081/employees";
}
//模拟渲染页面数据
/**
*<!DOCTYPE html>
* <html lang="en" xmlns:th="http://www.thymeleaf.org">
* <head>
* <meta charset="UTF-8">
* <title>Title</title>
* </head>
* <body>
* <h1>欢迎:[[${session.loginUser}]]</h1>
* <ul>
* <li th:each="emp : ${emps}">name :[[${emp}]]</li>
* </ul>
* </body>
* </html>
*/
List<String> emps =new ArrayList<>();
emps.add("aa");
emps.add("bb");
emps.add("cc");
model.addAttribute("emps", emps);
return "list";
}
}
==============================================================================
引入JWT
项目模块进行登录后,按照一定规则把用户信息包含到字符串中,把字符串返回。
- 可以通过cookie返回
- 可以通过地址栏返回
http://ip:port/xxxx?token=xxxxxx
形式- 再去访问其他模块,取到之前生成的
token字符串
,通过一定的算法,解析,获取到用户信息,即可登录
jwt
是给我们规定好了规则,使用jwt
规则生成字符串,包含用户信息,也可以自己写个算法
构建
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
创建工具类
public class JwtUtils {
//设置token过期时间
public static final long EXPIRE = 1000 * 60 * 60 * 24;
//密钥随便添,或者每个公司项目的密钥自己设置
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";
//生成token字符串方法
public static String getJwtToken(String id, String nickname){
String JwtToken = Jwts.builder()
//设置jwt的头信息 不用改
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setSubject("my_project_user")//分类 按照自己的项目
.setIssuedAt(new Date())//设置token过期时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.claim("id", id) //设置token主题部分,存储用户信息
.claim("nickname", nickname)
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact();
return JwtToken;
}
/**
* 判断token是否存在与有效
* @param jwtToken
* @return
*/
public static boolean checkToken(String jwtToken) {
if(StringUtils.isEmpty(jwtToken)) return false;
try {
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 判断token是否存在与有效
* @param request
* @return
*/
public static boolean checkToken(HttpServletRequest request) {
try {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return false;
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 根据token获取会员id
* @param request
* @return
*/
public static String getMemberIdByJwtToken(HttpServletRequest request) {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
Claims claims = claimsJws.getBody();
return (String)claims.get("id");
}
}
进阶:
https://blog.csdn.net/weixin_45031570/article/details/124880833?spm=1001.2014.3001.5502