本篇博客实现借鉴自:大厂架构演进实战之手写 CAS 单点登录
如果不想按下面的步骤一个一个粘贴,我也提供了github地址:https://github.com/XuBaozhao/sso
登陆淘宝,跳转登陆页面,登陆后,进入淘宝主页,再次登陆天猫,发现不用登陆,直接进入了天猫主页。
我省略了和数据库打交道的一点,并且也省略了验证token相同的环节,因为只是简单的熟悉流程,细节问题并不考虑在内。
看下面的结果展示,你就明白了。
(1)taobao 首先访问,被拦截,检测到没有 token,进入 ssoserver 的 checktoken 方法,此时全局 token 为空,进入登录页面,完成登录逻辑,生成 token 存入全局 token,并且将 token 存入数据库,再带着这个 token 返回 taobao。
(2)进入 taobao 拦截器,有 token,进行验证,进入 ssoserver 的 verify 方法,从数据库中查询,token 存在,则返回 true。
(3)回到 taobao 拦截器,结果为 true,将 token 存入 Cookie(给 tmall 检测使用),并将 isLogin = true 存入本地 session,返回 true,通过拦截器,进入页面,taobao 登录逻辑完成。
(4)tmall 访问,被拦截,检测到没有 token,进入 ssoserver 的 checktoken 方法,此时全局 token 存在,则对比 Cookie,如果 Cookie 中没有相等的 token,则登录,如果有相等的 token,则表示其他子项目(taobao)已登录过,tmall 不需要再次登录,带着这个 token 返回 tmall。
(5)进入 tmall 拦截器,有 token,进行验证,进入 ssoserver 的 verify 方法,从数据库中查询,token 存在,则返回 true。
(6)回到 tmall 拦截器,结果为 true,将 token 存入 Cookie,并将 isLogin = true 存入本地 session,返回 true,通过拦截器,进入页面,tmall 登录逻辑完成。
1、url输入以下:
2、enter之后:
3、输入1和1,登陆:
5、访问天猫:
6、enter后:
7、再次访问淘宝和天猫,仍然直接进入主页,不会登陆
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.3.4.RELEASE
com.bupt
ssoclienttaobao
0.0.1-SNAPSHOT
ssoclienttaobao
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.boot
spring-boot-maven-plugin
1、首先配置RestTemplate,用于进程远程RPC服务调用
package com.bupt.ssoclienttaobao.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ApplicationContextConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
2、配置拦截器:
拦截url = taobao的请求
package com.bupt.ssoclienttaobao.configuration;
import com.bupt.ssoclienttaobao.interceptor.TaobaoInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
String[] addPathPattrens = {"/taobao"};
registry.addInterceptor(new TaobaoInterceptor()).addPathPatterns(addPathPattrens);
}
}
package com.bupt.ssoclienttaobao.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@Controller
public class TaobaoController {
public static final String PAYMENT_URL = "http://localhost:8081";
@Resource
private RestTemplate restTemplate;
@GetMapping("/index")
public String index2(){
return "index";
}
// 进入淘宝页面
@GetMapping("/taobao")
public String index(){
return "index";
}
}
作用:该拦截器用于访问淘宝页面之前,首先判断自己是否登陆过(session中isLogin不空) and 天猫是否登陆过(token不空),如果以上有一点满足,则直接访问,否则进入ssoserver的登陆页面。
package com.bupt.ssoclienttaobao.interceptor;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class TaobaoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断会话是否存在
HttpSession session = request.getSession();
Boolean isLogin = (Boolean) session.getAttribute("isLogin");
// 如果session中isLogin == true,说明自己登陆过了,直接访问
if(isLogin!=null && isLogin){
return true;
}
//判断token
String token = request.getParameter("token");
// 如果token不为空,说明自己没登录,别人登陆过了,自己保存信息,也是返回true
if(!StringUtils.isEmpty(token)){
Cookie cookie = new Cookie("token", token);
response.addCookie(cookie);
session.setAttribute("isLogin", true);
return true;
}
// token为空,登录认证
response.sendRedirect("http://localhost:8081/checkToken?url=http://localhost:8080/taobao");
return false;
}
}
#thymeleaf start
#视图解析器的前缀放在这个文件夹
spring.thymeleaf.prefix=classpath:/templates/
#后缀
spring.thymeleaf.suffix=.html
#模式
spring.thymeleaf.mode=LEGACYHTML5
spring.thymeleaf.servlet.content-type=text/html
#编码格式
spring.thymeleaf.encoding=utf-8
#不用缓存
spring.thymeleaf.cache=false
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
#thymeleaf end
登录
淘宝主页
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.3.4.RELEASE
com.bupt
ssoserver
0.0.1-SNAPSHOT
ssoserver
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-thymeleaf
net.sourceforge.nekohtml
nekohtml
1.9.22
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.boot
spring-boot-maven-plugin
package com.bupt.ssoserver.controller;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.UUID;
@Controller
public class Testcontroller {
// 进入登陆页面
@GetMapping("/login")
public String index(){
return "login";
}
// 判断登陆逻辑
@PostMapping("userlogin")
public String login(HttpServletRequest httpServletRequest, HttpSession session) throws IOException {
String username = httpServletRequest.getParameter("username");
String password = httpServletRequest.getParameter("password");
if(username.equals("1") && password.equals("1")){
String token = UUID.randomUUID().toString();
// 设置servlet上下文信息
session.getServletContext().setAttribute("token", token);
// 进入淘宝主页
return "redirect:"+"http://localhost:8080/taobao"+"?token="+token;
}
return "error";
}
@GetMapping("/checkToken")
public String checkToken(HttpServletRequest request, HttpSession session){
String url = request.getParameter("url");
String token = (String) session.getServletContext().getAttribute("token");
// 如果token空,登陆
if(StringUtils.isEmpty(token)){
return "login";
}else{
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if(cookie.getValue().equals(token)){
return "redirect:"+url+"?token="+token;
}
}
return "login";
}
}
}
server.port=8081
#thymeleaf start
#视图解析器的前缀放在这个文件夹
spring.thymeleaf.prefix=classpath:/templates/
#后缀
spring.thymeleaf.suffix=.html
#模式
spring.thymeleaf.mode=LEGACYHTML5
spring.thymeleaf.servlet.content-type=text/html
#编码格式
spring.thymeleaf.encoding=utf-8
#不用缓存
spring.thymeleaf.cache=false
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
#thymeleaf end
login.html
Title
请输入用户名与密码登录
error.html
登录
error
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.3.4.RELEASE
com.bupt
ssoclienttmall
0.0.1-SNAPSHOT
ssoclienttmall
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.boot
spring-boot-maven-plugin
package com.bupt.ssoclienttmall.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ApplicationContextConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
package com.bupt.ssoclienttmall.configuration;
import com.bupt.ssoclienttmall.interceptor.TmallInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
String[] addPathPattrens = {"/tmall"};
registry.addInterceptor(new TmallInterceptor()).addPathPatterns(addPathPattrens);
}
}
package com.bupt.ssoclienttmall.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class TmallController {
@GetMapping("/tmall")
public String index(){
return "index";
}
}
package com.bupt.ssoclienttmall.interceptor;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class TmallInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断会话是否存在
HttpSession session = request.getSession();
Boolean isLogin = (Boolean) session.getAttribute("isLogin");
// 如果session中isLogin == true,直接访问,不用拦截
if(isLogin!=null && isLogin){
return true;
}
//判断token
String token = request.getParameter("token");
// 如果token不为空
if(!StringUtils.isEmpty(token)){
//验证通过
Cookie cookie = new Cookie("token", token);
response.addCookie(cookie);
session.setAttribute("isLogin", true);
return true;
}
//token为空,登录认证
response.sendRedirect("http://localhost:8081/checkToken?url=http://localhost:8082/tmall");
// SSOClientUtil.redirectToCheckToken(request, response);
return false;
}
}
server.port=8082
#thymeleaf start
#视图解析器的前缀放在这个文件夹
spring.thymeleaf.prefix=classpath:/templates/
#后缀
spring.thymeleaf.suffix=.html
#模式
spring.thymeleaf.mode=LEGACYHTML5
spring.thymeleaf.servlet.content-type=text/html
#编码格式
spring.thymeleaf.encoding=utf-8
#不用缓存
spring.thymeleaf.cache=false
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
#thymeleaf end
登录
天猫主页
......总算记录完了,我发现写文档的过程比写项目还痛苦....