java实现单点登陆(SSO)
网络域名必须完全一致,才代表同一站点。
域名映射 :访问后面的 会跳转到前面
单点登陆概念:
多系统,单一位置登录,实现多系统同时登陆。常出现在互联网和企业级平台中。
单点登陆一般是用于互相授信的系统,实现单一位置登录,全系统有效。
三方登录:某系统使用其他系统的用户,实现本系统登录的方式,如在jd上使用wx登录。解决信息孤岛(共享用户的名字,而不是密码,用户在每个系统都有自己的密码)和用户不对等的实现方案。
登录方式:
session跨域
摒弃系统(tomcat)提供的session,使用自定义的类似session的机制来保存客户端数据的一种解决方案。
如:通过设置cookie的domain实现cookie的跨域传递,在cookie中传递一个自定义的session id,这个session id 是客户端的,将这个标记作为key,将客户端需要保存的数据作为value,在服务端进行保存(数据库保存或者在nosql保存)。
什么是跨域:客户端请求的时候,请求的服务器,不是同一个IP、端口、域名、主机名的时候,都称为跨域。
什么是域:在应用模型,一个完整的,有独立访问路径的功能集合称为一个域,如:百度称为一个应用系统,百度下有若干域,搜索引擎(www.baidu.com)百度贴吧(tieba.baidu.com)百度知道(zhidao.baidu.com)。域信息、有时也称为多级域名。域的划分,以IP、端口、域名、主机名
整个项目采用springboot+maven,只实现了单点登陆而已哦~
maven包(放在父工程即可):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sso</groupId>
<artifactId>sso-use-cookie</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>sso-main</module>
<module>sso-vip</module>
<module>sso-cart</module>
<module>sso-login</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</dependency>
</dependencies>
</project>
项目整体:
登录模块(port=9000):
LoginController:
package com.sso.login.controller;
import com.sso.login.poho.User;
import com.sso.login.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,"zhangsan","123456"));
dbUsers.add(new User(1,"lisi","123456"));
dbUsers.add(new User(0,"wangwu","123456"));
}
@PostMapping
public String doLogin(User user, HttpSession session, HttpServletResponse response){
String target = (String) session.getAttribute("target");
//模拟从数据库中通过登陆的用户名和密码去查找数据库中用户
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("codeshop.com");
response.addCookie(cookie);
LoginCacheUtil.loginUser.put(token,first.get());
}
else{
//登陆失败
session.setAttribute("msg","用户名或密码错误");
return "login";
}
//重定向到target地址
return "redirect:" + target;
}
@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.sso.login.controller;
import com.sso.login.poho.User;
import com.sso.login.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 {
/**
* 跳转到登录页面
* @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.codeshop.com:9010";
}
//如果是已经登陆的用户再次访问登录页面,需要重定向
if(cookie != null){
//token
String value = cookie.getValue();
User user = LoginCacheUtil.loginUser.get(value);
if(user != null){
return "redirect:" + target;
}
}
//TODO:要做target地址是否合法的校验
//重定向地址
session.setAttribute("target",target);
return "login";
}
}
User:
package com.sso.login.poho;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Data//添加getter/setter
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)//添加链式调用
public class User {
private Integer id;
private String username;
private String password;
}
LoginCacheUtil:
package com.sso.login.utils;
import com.sso.login.poho.User;
import java.util.HashMap;
import java.util.Map;
public class LoginCacheUtil {
public static Map<String , User> loginUser = new HashMap<>();
}
LoginApp :
package com.sso.login;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class LoginApp {
public static void main(String[] args) {
SpringApplication.run(LoginApp.class,args);
}
}
index.html:
<!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>
aoolication.yml:
server:
port: 9000
主页模块(port=9010)
ViewController :
package com.sso.main.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 LOGIN_INFO_ADDRESS = "http://login.codeshop.com: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(LOGIN_INFO_ADDRESS + token, Map.class);
session.setAttribute("loginUser",result);
}
}
return "index";
}
}
MainApp :
package com.sso.main;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class MainApp {
public static void main(String[] args) {
SpringApplication.run(MainApp.class,args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
index.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml" xmlns:http="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>欢迎来到Code Shop</h1>
<span>
<a th:if="${session.loginUser == null}" href="http://login.codeshop.com:9000/view/login?target=http://www.codeshop.com:9010/view/index">登录</a>
<a th:unless="${session.loginUser == null}" href="#">退出</a>
</span>
<p th:unless="${session.loginUser == null}">
<span style="color: deepskyblue;" th:text="${session.loginUser.username}"></span>已登录
</p>
</body>
</html>
application.yml:
server:
port: 9010
vip模块(port=9011)
ViewController :
package com.oss.vip.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://login.codeshop.com: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(USER_INFO_ADDRESS + token, Map.class);
session.setAttribute("loginUser",result);
}
}
return "index";
}
}
VipApp :
package com.oss.vip;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class VipApp {
public static void main(String[] args) {
SpringApplication.run(VipApp.class,args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
index.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Vip</title>
</head>
<body>
<h1>欢迎来到Vip系统</h1>
<span>
<a th:if="${session.loginUser == null}" href="http://login.codeshop.com:9000/view/login?target=http://vip.codeshop.com:9011/view/login">登录</a>
<a th:unless="${session.loginUser == null}" href="#">退出</a>
</span>
<p th:unless="${session.loginUser == null}">
<span style="color: deepskyblue;" th:text="${session.loginUser.username}"></span>已登录
</p>
</body>
</html>
application.yml:
server:
port: 9011
购物车模块(port=9012)
ViewController :
package com.oss.cart.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://login.codeshop.com: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(USER_INFO_ADDRESS + token,Map.class);
session.setAttribute("loginUser",result);
}
}
return "index";
}
}
CartApp :
package com.oss.cart;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class CartApp {
public static void main(String[] args) {
SpringApplication.run(CartApp.class,args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
index.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Cart</title>
</head>
<body>
<h1>欢迎来到Cart页面</h1>
<span>
<a th:if="${session.loginUser == null}" href="http://login.codeshop.com:9000/view/login?target=http://cart.codeshop.com:9012/view/login">登录</a>
<a th:unless="${session.loginUser == null}" href="#">退出</a>
</span>
<p th:unless="${session.loginUser == null}">
<span style="color: deepskyblue;" th:text="${session.loginUser.username}"></span>已登录
</p>
</body>
</html>
application.yml:
server:
port: 9012