一、引言
众所周知,一个网页系统,最基础的功能就是登录,作为一名JAVA程序员,从写下Hello Word 到变成一个技术大牛的这个过程中,登录功能不一定是写的最多,但是一定会是最常见的,本篇文章简单介绍了一下SSO的概念及原理,然后使用SpringBoot实现了一个简单的SSO系统。系统使用token的形式,依靠cookie携带token向sso服务器进行验证,验证通过后允许访问请求地址。
二、SSO介绍
众所周知,我们在最开始写业务的时候,可能就只有一个模块,但是随着开发时间的增加,代码会越来越多,会十分的冗余,那么这个时候我们就会把代码拆分成一个又一个的小模块,每一个模块就是一个系统,比方说我们做了一个电商系统,他里面分了购物车系统,商品系统,购买系统等等,那么我们登录后,访问另一个系统的时候,如果还要再一次的登录的话,就会十分的麻烦,所以在这里我们引进了SSO单点登录系统,只要一个系统登陆成功,另一个系统就不需要登录了。
三、常见例子
我们日常生活中用的最多的电商APP估计是淘宝,在这里我们用淘宝举一个例子
首先,我们登录淘宝的主界面,并且登录
在这里,我们可以看见淘宝的域名,taobao.com,这个时候,我们来看看购物车,我们会发现,进入购物车菜单后,网站的域名发生了变化
那么,我们就可以知道,在这里,就发生了单点登录,只要我们登录了主界面,在访问其他系统,比如说购物车,收藏夹什么的,也算我们登录了,那么就不需要再做第二遍,这样节省了很多的事情,那么我们今天就要学习这一点是如何做到的。
三、SSO代码实现
目前常见的登录有两种,一种是把用户信息存在session中,在镜进行操作的时候判断session是否存在,还有一种是通过token来进行判断。我们知道session是不共享的,那么,我们是不是可以把token存在cookie中,当一个系统登录了,去访问另一个系统的时候,我们判断一下token是否正确,如果正确,就放行,错误,则拦截,在这里我们简单的实现一下
给本机赋域名
在写代码之前,我们要先给我们的主机赋予一个域名,这样才方便我们接下来的工作
我们先查看一下我们所在局域网的ip
打开我的电脑,找到此路径:C:\Windows\System32\drivers\etc
然后打开host文件,进行编写,域名可以自己随便取名字,搞完后保存退出
编写代码
我们需要两个服务来模拟,所以我们先自己建立好下面两个文件,然后给client设置端口8081,server设置8082
然后,我们把其余的代码补充,先是client下的
User类
package com.znb.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String username;
private String password;
}
LoginCacheUtil
package com.znb.utils;
import com.znb.pojo.User;
import java.util.HashMap;
import java.util.Map;
public class LoginCacheUtil {
public static Map<String , User> loginUser = new HashMap<>();
}
LoginController
package com.znb.controller;
import com.znb.pojo.User;
import com.znb.utils.LoginCacheUtil;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
@Controller
@RequestMapping("/login")
public class LoginController {
private static Set<User> dbUsers;
static {
dbUsers = new HashSet<>();
dbUsers.add(new User(0,"xiaoyan","000000"));
dbUsers.add(new User(1,"tangsan","000000"));
dbUsers.add(new User(2,"yecheng","000000"));
}
@PostMapping
public String doLogin(User user, HttpSession session, HttpServletResponse response){
//模拟从数据库中通过登陆的用户名和密码去查找数据库中用户
Optional<User> first = dbUsers.stream().filter(dbUser -> dbUser.getUsername().equals(user.getUsername()) &&
dbUser.getPassword().equals(user.getPassword()))
.findFirst();
//判断用户是否登陆
if(first.isPresent()){
//保存用户的登录信息
String token = UUID.randomUUID().toString();
Cookie cookie = new Cookie("TOKEN",token);
cookie.setDomain("codeicee.com");
response.addCookie(cookie);
LoginCacheUtil.loginUser.put(token,first.get());
}
else{
//登陆失败
session.setAttribute("msg","用户名或密码错误");
return "login";
}
//重定向到target地址
return "index" ;
}
@GetMapping("info")
@ResponseBody
public ResponseEntity<User> getUserInfo(String token){
if(!StringUtils.isEmpty(token)){
User user = LoginCacheUtil.loginUser.get(token);
return ResponseEntity.ok(user);
}else {
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
}
}
}
ViewController
package com.znb.controller;
import com.znb.pojo.User;
import com.znb.utils.LoginCacheUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
/**
* 页面跳转逻辑
*/
@Controller
@RequestMapping("/view")
public class ViewController {
@RequestMapping("/")
public String index(){
return "index";
}
/**
* 跳转到登录页面
* @return
*/
@GetMapping("/login")
public String toLogin(@RequestParam(required = false,defaultValue = "")String target,
HttpSession session,
@CookieValue(required = false,value = "TOKEN")Cookie cookie){
if(StringUtils.isEmpty(target)){
target = "http://www.codeicee.com:8081";
}
//如果是已经登陆的用户再次访问登录页面,需要重定向
if(cookie != null){
//token
String value = cookie.getValue();
User user = LoginCacheUtil.loginUser.get(value);
if(user != null){
return "product";
}
}
//重定向地址
session.setAttribute("target",target);
return "login";
}
}
index
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
</body>
</html>
login
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8"/>
<title>Login Module</title>
</head>
<body>
<h1>欢迎来到登录页面</h1>
<p style="color: red;" th:text="${session.msg}"></p>
<form action="/login" method="POST">
用户名:<input name="username" value=""/>
密码:<input name="password" value=""/>
<button type="submit">登录</button>
</form>
</body>
</html>
product
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>产品</h1>
</body>
</html>
然后是server
ViewController
package com.znb.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
import java.util.Map;
@Controller
@RequestMapping("/view")
public class ViewController {
@Autowired
private RestTemplate restTemplate;
private final String USER_INFO_ADDRESS = "http://codeicee.com:8081/login/info?token=";
@GetMapping("/index")
public String toIndex(@CookieValue(required = false,value = "TOKEN") Cookie cookie,
HttpSession session){
if(cookie != null){
String token = cookie.getValue();
if(!StringUtils.isEmpty(token)){
Map result = restTemplate.getForObject(USER_INFO_ADDRESS + token,Map.class);
session.setAttribute("loginUser",result);
}
}
return "index";
}
}
把两个项目都启动后,我们会发现,当client登录后server的也登录成功了