单点登录(single sign on),解决了分布式下用户登录的信息管理问题,可以自行增强安全策略,并且登录的跨域也不会再成为问题。
业务流程:
创建两个不同的模块:
一个作为客户端,一个作为登陆服务器,都需要引入redis
对于客户端代码如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.List;
@Controller
public class HelloController {
@Autowired
StringRedisTemplate redisTemplate;
//无需登录就可访问
@ResponseBody
@GetMapping(value = "/hello")
public String hello() {
return "hello";
}
@GetMapping(value = "/employees")
public String employees(Model model, HttpSession session,
//登录成功才会带这个token
@RequestParam(value = "token",required = false) String token) {
if(!StringUtils.isEmpty(token)){
//1.通过token得到具体的用户信息
//使用restTemplate远程访问登录服务器得到用户信息,我这里做测试都是localhost所以调不了,直接从redis中取的数据
//RestTemplate restTemplate=new RestTemplate();
//String username = restTemplate.getForEntity("http://sso.mroldx.cn:8080/userinfo?token=" + token, String.class).getBody();
String username = redisTemplate.opsForValue().get(token);
session.setAttribute("loginUser",username);
}
Object loginUser = session.getAttribute("loginUser");
if(loginUser!=null){
//有登录用户的信息,所以正常往下走
List<String> emps = new ArrayList<>();
emps.add("张三");
emps.add("李四");
model.addAttribute("emps", emps);
return "employees";
}else {
//没登陆就跳转到登陆服务器进行登录
//添加redirect_url=http://localhost:8081/employees是为了登录成功后能自动返回相应页面
//这里的redirect_url的值根据不同的网站使用不同的url
return "redirect:http://localhost:8082/login.html?redirect_url=http://localhost:8081/employees";
}
}
}
对于登录服务器代码如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
@Controller
public class LoginController {
@Autowired
StringRedisTemplate redisTemplate;
//供远程调用以得到用户信息
@ResponseBody
@GetMapping("/userinfo")
public String userinfo(@RequestParam(value = "token") String token) {
return redisTemplate.opsForValue().get(token);
}
@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";
}
@PostMapping("/doLogin")
public String doLogin(@RequestParam("username") String username,
@RequestParam("password") String password,
@RequestParam("redirect_url") String url,
HttpServletResponse response){
if(!StringUtils.isEmpty(username)&&!StringUtils.isEmpty(password)){
//登陆成功
String uuid = UUID.randomUUID().toString().replace("-","");
redisTemplate.opsForValue().set(uuid,username);
//在cookie中留下标记,证明了已经登录
Cookie sso_token = new Cookie("sso_token",uuid);
response.addCookie(sso_token);
//从哪里来,到哪里去
return "redirect:"+url+"?token="+uuid;
}
//登录失败,继续在登录页
return "login";
}
}
1.访问employees,需要登录。
1.1查看请求中是否带了token
1.1.1带了token,根据token远程访问登录服务器得到用户信息,并在session中加入用户信息。
1.1.2不带token,流程继续。
1.2查看session中是否带有用户信息
1.2.1带用户信息,留在当前页面
1.2.2不带用户信息,带着当前页的url到登录页
1.2.2.1登录页的cookie中含token,直接带着token返回到employees页
1.2.2.2cookie中不带token,继续留在本页。
1.2.2.2.1登录成功,登录服务器的cookie中放入token,并将token放入请求中返回到employees页
1.2.2.2.2登录失败,重新登录
可以看出来,第一次访问employees不带token,要来回访问登陆服务器多次,这样似乎很麻烦,但这样做可以在登录服务器的cookie中保留用户登录的标识,这样其它网站访问同一个登录服务器时可以确认用户是否登录过,这样就能实现一处登录,处处使用的效果。