1.SSO介绍
单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。这种方式减少了由登录产生的时间消耗,辅助了用户管理,是目前比较流行的。
2.单点登录
2.1 原理
2)实现步骤:
1.用户通过用户名和密码访问jt-web服务器.
2.JT-WEB服务器通过JT-SSO校验用户名和密码是否正确.
3.如果用户名和密码正确,则将数据保存到redis中. TICKET密钥:USERJSON,之后将密钥返回给用户即可.
4.JT-WEB服务器将密钥信息保存到用户的Cookie中 并且设定Cookie的共享/有效时间.
2. 2 UserController
/**
* 业务需求:
* 实现用户单点登录操作
* 1.url地址:http://www.jt.com/user/doLogin?r=0.43530970885614617
* 2.请求参数: username: asdasdfas
* password: asdfasdfa
* 3.返回值结果: SysResult对象
*
* 实现Cookie数据存储
* 1.获取用户名和密码进行数据校验
* 2.获取后端的密钥信息 非空????
* 3.如果一切正常,则将数据存储到Cookie中. 路径/有效期/共享问题
*
* 关于Cookie说明:
* 1.cookie只能看到自己域名下的cookie 私有的.
* 2.setPath说明
* setPath("/") 一般都是/ 读取cookie权限的设定,根目录中的请求 读取cookie
* setPath("/user") url地址路径/user下时才能获取cookie信息.
* url1: http://www.jt.com/findUser;
* url2: http://www.jt.com/user/findUser;
*
*/
@RequestMapping("/doLogin")
@ResponseBody
public SysResult userLogin(User user, HttpServletResponse response){
String ticket = userService.findUserByUP(user);
if(!StringUtils.hasLength(ticket)){
//如果数据为null则表示用户名和密码错误...
return SysResult.fail();
}
//需要将数据保存到cookie中
Cookie cookie = new Cookie("JT_TICKET", ticket);
cookie.setPath("/");
cookie.setMaxAge(7*24*60*60); //设定有效期 7天有效 单位秒
cookie.setDomain("jt.com"); //主要域名中由jt.com则可以共享数据
response.addCookie(cookie); //将数据写入客户端
return SysResult.success();
}
2.3 UserService
/**
* 1.校验用户名和密码是否正确 不存在直接返回null
* 2.动态生成密钥 将用户信息转化为JSON
* 3.将数据保存到redis中 7天有效.
* 4.返回密钥ticket信息.
* @param user
* @return
*/
@Override
public String findUserByUP(User user) {
String md5Pass =
DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
user.setPassword(md5Pass);
//1.根据对象中不为null的属性当做where条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
User userDB = userMapper.selectOne(queryWrapper);
//2.判断对象是否有值
if(userDB==null){
return null;
}
//3.表示用户名和密码正确 开启单点登录操作
String ticket = UUID.randomUUID()
.toString().replace("-", "");
//转化之前应该将数据进行脱敏处理
userDB.setPassword("123456");
String userJSON = ObjectMapperUtil.toJSON(userDB);
//4.将数据保存到redis中
jedisCluster.setex(ticket, 7*24*60*60, userJSON);
return ticket;
}
3. 用户信息回显
3.1 说明
如果用户登录成功之后,则通过cookie数据利用JSONP跨域方式,实现数据的动态获取.
3.2 页面JS
3.3 UserController
/**
* 跨域请求:完成用户信息获取
* URL网址: http://sso.jt.com/user/query/dca70b16a1c54aea9ebb0b27621250de?callback=jsonp1608021961735&_=1608021961777
* 参数: 参数1: ticket信息 参数2:callback
* 返回值: SysResult对象(用户数据......)
*/
@RequestMapping("/query/{ticket}")
public JSONPObject findUserByTicket(@PathVariable String ticket,
String callback){
//如何获取用户信息? 从redis中获取数据
if(jedisCluster.exists(ticket)){
String userJSON = jedisCluster.get(ticket);
SysResult sysResult = SysResult.success(userJSON);
return new JSONPObject(callback, sysResult);
}else{
return new JSONPObject(callback, SysResult.fail());
}
}
4. 用户退出操作
4.1 说明
1).当用户点击退出按钮时,应该重定向到系统首页
2).删除redis中的数据
3).删除Cookie中的数据
4.2 UserController
/**
* 完成用户退出操作
* 1.重定向到系统首页
* 2.要求删除redis中的数据 K-V结构 先获取key
* 3.动态获取Cookie中的数据
* 4.删除Cookie中的数据
* url地址: http://www.jt.com/user/logout.html
*
*/
@RequestMapping("/logout")
public String logout(HttpServletRequest request,HttpServletResponse response){
Cookie[] cookies = request.getCookies();
if(cookies !=null && cookies.length>0){
for (Cookie cookie : cookies){
if(JT_TICKET.equals(cookie.getName())){
String ticket = cookie.getValue();
//删除redis的数据
jedisCluster.del(ticket);
//删除cookie的数据
cookie.setMaxAge(0); //0 立即删除 -1 关闭浏览器之后删除
cookie.setPath("/");
cookie.setDomain("jt.com");
response.addCookie(cookie);
}
}
}
return "redirect:/";
}
5. 封装cookie API
作用: API主要的作用 简化Cookie调用的过程. 1.获取Cookie对象 2.可以根据cookieName 获取cookie的值 3.新增cookie 删除cookie
public class CookieUtil {
//1.获取cookie对象
public static Cookie getCookie(HttpServletRequest request,String cookieName){
Cookie[] cookies = request.getCookies();
if(cookies !=null && cookies.length>0){
for(Cookie cookie : cookies){
if(cookieName.equals(cookie.getName())){
return cookie;
}
}
}
return null;
}
//2.获取Cookie值
public static String getCookieValue(HttpServletRequest request,String cookieName){
Cookie cookie = getCookie(request,cookieName);
return cookie ==null?null:cookie.getValue();
}
//3.新增Cookie/删除Cookie写法
public static void addCookie(HttpServletResponse response,String cookieName,String cookieValue,String path,String domain,Integer seconds){
Cookie cookie = new Cookie(cookieName,cookieValue);
cookie.setPath(path);
cookie.setDomain(domain);
cookie.setMaxAge(seconds);
response.addCookie(cookie);
}
}
6. 重构用户退出操作
/**
* 完成用户退出操作
* 1.重定向到系统首页
* 2.要求删除redis中的数据 K-V结构 先获取key
* 3.动态获取Cookie中的数据
* 4.删除Cookie中的数据
* url地址: http://www.jt.com/user/logout.html
*
*/
@RequestMapping("/logout")
public String logout(HttpServletRequest request,HttpServletResponse response){
String ticket = CookieUtil.getCookieValue(request, JT_TICKET);
if(StringUtils.hasLength(ticket)){
//1.删除redis
jedisCluster.del(ticket);
//2.删除cookie
CookieUtil.addCookie(response,JT_TICKET,
"", "/", "jt.com", 0);
}
return "redirect:/";
/*Cookie[] cookies = request.getCookies();
if(cookies !=null && cookies.length>0){
for (Cookie cookie : cookies){
if(JT_TICKET.equals(cookie.getName())){
String ticket = cookie.getValue();
//删除redis的数据
jedisCluster.del(ticket);
//删除cookie的数据
cookie.setMaxAge(0); //0 立即删除 -1 关闭浏览器之后删除
cookie.setPath("/");
cookie.setDomain("jt.com");
response.addCookie(cookie);
}
}
}
return "redirect:/";
*/
}
7. 用户权限控制
7.1 说明
当用户在没有登录的条件下不允许访问敏感业务. 购物车操作/订单操作等. 如何实现???
答:使用拦截器的机制
7.2 拦截器配置
拦截哪些请求:
7.3 编辑拦截器
package com.jt.interceptor;
import com.jt.util.CookieUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import redis.clients.jedis.JedisCluster;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component //将拦截器交给spring容器管理
public class UserInterceptor implements HandlerInterceptor {
private static final String JT_TICKET="JT_TICKET";
@Autowired
private JedisCluster jedisCluster;
/**
* 返回值说明:
* 1.false 表示拦截 一般都要配合重定向的方式使用.
* 2.true 表示放行
*
* 如何实现业务:
* 判断用户是否登录: Cookie数据 检查redis中的数据.
* 重定向到系统登录页面.
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.校验Cookie中是否有结果
Cookie cookie = CookieUtil.getCookie(request, JT_TICKET);
//2.校验Cookie是否有效
if(cookie != null){
String ticket = cookie.getValue();
if(StringUtils.hasLength(ticket)){
//执行后续任务 校验redis中是否有结果
if(jedisCluster.exists(ticket)){
//表示用户登录过 直接返回true
return true;
}
}
//没有结果,则cookie数据有误,应该删除
CookieUtil.addCookie(response, JT_TICKET, "", "/", "jt.com", 0);
}
//3.如果数据为空,则重定向到系统首页
response.sendRedirect("/user/login.html");
return false; //表示拦截....
}
}
8. ThreadLocal 本地线程
8.1 作用:
在线程内部(一个线程)实现数据的共享.
8.2 ThreadLocal工具API
package com.jt.util;
import com.jt.pojo.User;
public class UserThreadLocal {
private static ThreadLocal<User> threadLocal = new ThreadLocal<>();
public static void setUser(User user){
threadLocal.set(user);
}
public static User getUser(){
return threadLocal.get();
}
public static void remove(){
threadLocal.remove();
}
}
8.3 重构拦截器
package com.jt.interceptor;
import com.jt.pojo.User;
import com.jt.util.CookieUtil;
import com.jt.util.ObjectMapperUtil;
import com.jt.util.UserThreadLocal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import redis.clients.jedis.JedisCluster;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component //将拦截器交给spring容器管理
public class UserInterceptor implements HandlerInterceptor {
private static final String JT_TICKET="JT_TICKET";
@Autowired
private JedisCluster jedisCluster;
/**
* 返回值说明:
* 1.false 表示拦截 一般都要配合重定向的方式使用.
* 2.true 表示放行
*
* 如何实现业务:
* 判断用户是否登录: Cookie数据 检查redis中的数据.
* 重定向到系统登录页面.
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.校验Cookie中是否有结果
Cookie cookie = CookieUtil.getCookie(request, JT_TICKET);
//2.校验Cookie是否有效
if(cookie != null){
String ticket = cookie.getValue();
if(StringUtils.hasLength(ticket)){
//执行后续任务 校验redis中是否有结果
if(jedisCluster.exists(ticket)){
String json = jedisCluster.get(ticket);
User user = ObjectMapperUtil.toObject(json,User.class);
//利用Request对象将数据进行传递 最为常见的参数传递的方式
request.setAttribute("JT_USER", user);
//ThreadLocal机制
UserThreadLocal.setUser(user);
//表示用户登录过 直接返回true
return true;
}
}
//没有结果,则cookie数据有误,应该删除
CookieUtil.addCookie(response, JT_TICKET, "", "/", "jt.com", 0);
}
//3.如果数据为空,则重定向到系统首页
response.sendRedirect("/user/login.html");
return false; //表示拦截....
}
//为了防止内存泄露,将多余的数据删除
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除request对象
request.removeAttribute("JT_USER");
//移除threadLocal数据
UserThreadLocal.remove();
}
}
8.4 用户信息传递
8.5 关于ThreadLocal线程说明
思考题: JT-WEB中ThreadLocal.set(xxx),问 在jt-cart中的service能否执行ThreadLocal.get()???
A. 不能