在单点登录方案设计一篇中,我们谈到了目前市面上常用的一些单点登录方案的实现,关于单点登录,只需要把握一个核心的要点即可,那就是:一处登录,处处登录,登录之后,即同域下其他各个系统都能统一拿到用户的基本信息
关于cookie,想必大家也很熟悉了,cookie中可以存储会话信息,将用户的基本信息存储进去之后,就可以在前后端交互中进行传输了
本篇将分享基于cookie如何实现单点登录,本篇以实际案例为主进行演示
以一个大家熟悉的购物业务,实际项目中,一个商城系统可能包含诸多模块的业务,比如用户中心,专门负责用户的认证功能,购物车、会员、物流等多个板块,用户只需要一次登录之后,就可以在各个业务模块的界面中来回切换
JDK8 , maven , idea
整个工程结构如上图所示, 各个模块的说明如下:
后续可以在此基础上继续增加,比如积分模块,物流模块等
1、顶级pom依赖
org.springframework.boot
spring-boot-starter-parent
2.2.1.RELEASE
org.springframework.boot
spring-boot-starter-web
2.2.1.RELEASE
该模块主要负责用户的登录,退出,并将用户会话信息存储至token,同时为其他各个模块提供用户信息查询功能
1、pom依赖
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-tomcat
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-devtools
runtime
true
org.apache.httpcomponents
httpclient
4.5.13
2、yml配置
server:
port: 9000
3、登录页面
提供一个基于thymeleaf的html登录模板页
login
登陆页面
4、提供一个controller
ViewController 用户进行跳转至登录页,作为引导控制器
/**
* 页面跳转逻辑
*/
@Controller
@RequestMapping("/view")
public class ViewController {
/**
* 跳转到登陆页面,设置重定向的地址,可以携带cookie-TOKEN,如果有cookie,不用跳转到登陆页面,直接重定向
* @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://127.0.0.1:9010";
}
//若已经登陆的用户登陆系统时,直接重定向到target
if (cookie != null){
String value = cookie.getValue();
User user = LoginCacheUtil.loginMap.get(value);
if (user != null){
return "redirect:" + target;
}
}
//重定向地址,将地址保存起来
session.setAttribute("target",target);
return "login";
}
}
LoginController 实际处理登录逻辑的控制器,提供登录,登出,以及查询用户信息的接口,这里为了模拟数据库的用户,就直接在程序中模拟初始化一些用户数据
@Controller
@RequestMapping("/login")
public class LoginController {
private static Set dbUsers;
/**
* 模拟数据库的用户列表
*/
static {
dbUsers = new HashSet<>();
dbUsers.add(new User(0,"zhangsan","123456"));
dbUsers.add(new User(1,"lisi","123456"));
dbUsers.add(new User(2,"wangwu","123456"));
}
@PostMapping
public String doLogin(User user, HttpSession session, HttpServletResponse response){
String target = (String) session.getAttribute("target");
User res = null;
for (User dbUser : dbUsers){
if(dbUser.getUsername().equals(user.getUsername()) && dbUser.getPassword().equals(user.getPassword())){
res = dbUser;
}
}
//用户登陆成功,保存(TOKEN,用户)
if (res != null){
//保存用户登陆信息
String token = UUID.randomUUID().toString();
Cookie cookie = new Cookie("TOKEN",token);
cookie.setDomain("127.0.0.1");
response.addCookie(cookie);
LoginCacheUtil.loginMap.put(token,user);
} else {
session.setAttribute("msg","用户名或密码错误");
return "login";
}
//重定向到target地址
return "redirect:" + target;
}
/**
* 给其他子系统开发一个接口,根据token获取登陆的用户信息
* @param token
* @return
*/
@GetMapping("/info")
public ResponseEntity getUserInfo(String token){
if (!StringUtils.isEmpty(token)){
User user = LoginCacheUtil.loginMap.get(token);
return ResponseEntity.ok(user);
}else {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
@GetMapping("/logout")
public String logout(@CookieValue(value = "TOKEN")Cookie cookie, @RequestParam("target") String target,
HttpSession session,HttpServletResponse response){
//删除用户的登陆信息
LoginCacheUtil.loginMap.remove(cookie.getValue());
//删除session.loginUser
session.removeAttribute("loginUser");
//设置cookie过期
Cookie newCookie = new Cookie("TOKEN",null);
newCookie.setMaxAge(0);
newCookie.setPath("/");
response.addCookie(newCookie);
return "redirect:"+target;
}
}
1、pom依赖
同 sso-login
2、yml配置
server:
port: 9010
3、配置简单的html
由于需要进行页面展示,并模拟登录过程,这里使用了thymeleaf模板,使用比较简单,main模块的页面主要负责登录之后跳转后的主页面,简单起见,只需要对登录之后的用户信息做一下展示即可
首页
欢迎来到main首页
登陆
退出
已登陆
4、编写一个controller
当用户登录成功后,需要进行页面跳转,即跳转到main的页面,由于是不同的业务模块,实际项目中,可能是分布式部署的话,为了拿到用户的登录信息,这里简单采用restTemplate的方式
@Controller
@RequestMapping("/view")
public class ViewController {
@Autowired
private RestTemplate restTemplate;
private final static String REMOTE_LOGIN_INFO_ADDRESS = "http://127.0.0.1:9000/login/info?token=";
@GetMapping("/index")
public String toIndex(@CookieValue(required = false, value = "TOKEN")Cookie cookie, HttpSession session){
if (cookie != null){
String token = cookie.getValue();
//根据login子系统暴露的info方法去根据token获取user
if (!StringUtils.isEmpty(token)){
Map result = restTemplate.getForObject(REMOTE_LOGIN_INFO_ADDRESS + token, Map.class);
session.setAttribute("loginUser",result);
}
}
return "index";
}
}
sso - main 和sso-login模块写好之后,我们就可以简单做个测试了,分别启动这两个模块
1、访问登录入口页面,http://127.0.0.1:9010/view/index
2、点击登录
登录之后,由sso-main模块中的页面上的a链接,携带一个完整的href地址跳转到sso-login的登录主页面,我们的实现逻辑是,进入登录页面时,由于携带了完整的url信息,里面包含了从哪个页面(这里从main模块)过来的,那么在sso-login登录成功之后,还能跳转回去原来的页面
3、输入程序中初始化的用户名和密码
在sso-login的doLogin接口处理完成之后,将会把登录用户的信息写入session并带来页面上进行展示;
同时用户登录成功后,在接口中我们生成了一个token,用一个map进行存储,token作为key,user对象作为value,方便在后续其他模块访问的时候,直接从map中快速获取,关于这一点,在实际开发项目中,可以考虑使用threadLocal进行存储
如何验证我们的单点登录是好使的呢?还没有完,上面只是完成了用户的登录,接下来我们再将另外2个模块的业务逻辑编写完整
该模块作为会员业务模块,另外的cart购物车模块也是如此
1、pom依赖
如上的sso-login
2、yml配置
server:
port: 9011
3、html页面
Vip
欢迎来到VIP页面
登陆
退出
已登陆
该页面主要展示登录成功后的用户信息
4、提供一个controller,用于从sso-login中获取用户信息
@Controller
@RequestMapping("/view")
public class ViewController {
@Autowired
private RestTemplate restTemplate;
private final static String REMOTE_LOGIN_INFO_ADDRESS = "http://127.0.0.1:9000/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(REMOTE_LOGIN_INFO_ADDRESS + token, Map.class);
session.setAttribute("loginUser",result);
}
}
return "index";
}
}
接下来启动该模块,下面开始测试,
1、在已经登录的情况下,输入:http://localhost:9011/view/index
已经登录的情况下,VIP模块的controller中通过rest接口可以拿到cookie中用户的信息,因此直接将用户信息取出进行页面展示
2、在未登录的情况下,输入:http://localhost:9011/view/index
最后我们将cart模块也仿照vip博客搭建好,启动项目,然后再次做测试,
3、访问car模块主页面:http://127.0.0.1:9012/view/index
由于第二步中进行了退出,这时候,访问cart页面时,也是需要登录的状态
4、cart中点击登录,并进行登录操作
登录成功后,我们再次刷新cart页面,这时候显示已登录
通过以上案例的展示,我们基于cookie的方式实现了模拟单点登录的效果,此种方案,在不少生产级的项目中仍有使用,关于cookie实现单点登录,做一下简单的说明
优点:
缺点: