单点登陆系统SSO(Single Sign On)

什么是单点登陆系统

  • 一处登录,多处使用
    即用户登陆某系统后,访问其他相互信任的应用系统时不需要再次手动登录

为什么要有单点登陆系统

服务器集群时,每个服务器有自己独立的session,用户每次只能在其中一台服务器登录

  • 解决集群情况下的session共享问题
集群服务器配置session复制单点登录系统
广播session信息将登录信息存放到redis中,系统间共享存放token的cookie
容易引起网络风暴,集群节点受限(5个左右)SSO需要提供服务供其他系统调用

单点登陆系统做了什么

  • 将当前登录映射到该用户在其他应用中的登录
    1. SSO查询数据库进行校验
    2. SSO生成token作为用户唯一标识(类似sessionId)
    3. token作为key,用户名密码作为value存入redis,设置过期时间
    4. 存入成功后将token写入cookie
    5. 其他系统从cookie中取token,通过jsonp向SSO跨域发送token进行查询
    6. SSO从缓存中读取数据,若过期则重新登陆,否则更新过期时间并用jsonp返回相关返回已登陆
    7. 用户退出登录时向SSO发送token,删除redis中的用户信息

项目中实现单点登陆系统

  • 第一次登录
    	@Override
    	public TaotaoResult login(String username, String password, HttpServletRequest request,
    			HttpServletResponse response) {
    		//校验用户名密码是否正确
    		TbUserExample example = new TbUserExample();
    		Criteria criteria = example.createCriteria();
    		criteria.andUsernameEqualTo(username);
    		List<TbUser> list = userMapper.selectByExample(example);
    		if (list == null || list.isEmpty()) {
    			return TaotaoResult.build(400, "用户名或密码错误");
    		}
    		TbUser user = list.get(0);
    		if(!user.getPassword().equals(DigestUtils.md5DigestAsHex(password.getBytes()))) {
    			return TaotaoResult.build(400, "用户名或密码错误");
    		}
    		//校验成功后生成token
    		String token = UUID.randomUUID().toString();
    		//把用户信息写入redis
    		//key:REDIS_SESSION:{TOKEN}   value:user转json
    		user.setPassword(null);
    		jedisClient.set(REDIS_SESSION_KEY + ":" + token, JsonUtils.objectToJson(user));
    		//设置用户信息的过期时间
    		jedisClient.expire(REDIS_SESSION_KEY + ":" + token, SESSION_EXPIRE);
    		//将token写入cookie
    		CookieUtils.setCookie(request, response, "TT_TOKEN", token);
    		
    		return TaotaoResult.ok(token);
    	}
    
  • 其他系统再次登录
    SSO查询redis
    public TaotaoResult getUserByToken(String token) {
    		// 根据token取用户信息
    		String json = jedisClient.get(REDIS_SESSION_KEY + ":" + token);
    		// 判断是否查询到结果
    		if (StringUtils.isBlank(json)) {
    			return TaotaoResult.build(400, "用户session已经过期");
    		}
    		
    		//若缓存中存在用户信息,则更新token的过期时间
    		jedisClient.expire(REDIS_SESSION_KEY + ":" + token, SESSION_EXPIRE);
    		//并返回对应用户信息
    		TbUser user = JsonUtils.jsonToPojo(json, TbUser.class);
    		return TaotaoResult.ok(user);
    
    	}
    
    当前系统跨域调用SSO查询服务
    @RequestMapping("/user/token/{token}")
    @ResponseBody
    public Object getUserByToken(@PathVariable String token, String callback) {
    	try {
    		TaotaoResult result = loginService.getUserByToken(token);
    		//支持jsonp调用
    		if (StringUtils.isNotBlank(callback)) {
    			MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(result);
    			mappingJacksonValue.setJsonpFunction(callback);
    			return mappingJacksonValue;
    		}
    		return result;	
    	} catch (Exception e) {
    		e.printStackTrace();
    		return TaotaoResult.build(500, ExceptionUtil.getStackTrace(e));
    	}
    }
    
    
  • 门户系统整合SSO
    其他系统跳转到SSO登页面,或SSO登录成功后跳转到其他系统页面:js中指定跳转url即可

登录拦截器

  • 使用场景: 在进行某操作前必须先登录

  • 实现步骤:

    1. 当前页面拦截请求url(创建一个类XXX继承HandlerInterceptor)
    2. 当前页面从cookie中取token,若token已过期则跳转到登录
    3. 若有则向SSO发送跨域请求
    4. SSO根据传过来的token到redis查询信息并返回响应数据
    5. 若响应数据显示token已过期,则跳转到登录
    6. 否则获取取返回数据中的用户信息
    7. 回调到当前页面
    //配置拦截器设置
    <mvc:interceptors>
    	<mvc:interceptor>
    		//需要拦截的url
    		<mvc:mapping path="/item/*" />
    		<bean class="com.taotao.portal.LoginInterceptor"></bean>
    	</mvc:interceptor>
    </mvc:interceptors>
    
    public class LoginInterceptor implements HandlerInterceptor {
    		
    			@Autowired
    			private UserService userService;
    			@Value("SSO_LOGIN_URL")
    			private String SSO_LOGIN_URL;
    		
    			@Override
    			public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    					throws Exception {
    				// 拦截请求url,查询用户信息
    				TbUser user = userService.getUserByToken(request, response);
    				// 如果无法取到用户数据,跳转到登录页面
    				if (user == null) {
    					//为了让登录后回到当前页面,将当前页面的url传给SSO以便回调
    					response.sendRedirect(SSO_LOGIN_URL+"?redirect="+request.getRequestURL());
    					return false;
    				}
    				// 若取到用户信息,放行
    				return true;
    			}
    		
    			@Override
    			public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
    					ModelAndView modelAndView) throws Exception {
    			}
    		
    			@Override
    			public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
    					throws Exception {
    			}
    		
    		}
    
    public TbUser getUserByToken(HttpServletRequest request, HttpServletResponse response) {
    	try {
    		//从cookie中取token
    		String token = CookieUtils.getCookieValue(request, "TT_TOKEN");
    		//判断浏览器token是否过期
    		if (StringUtils.isBlank(token)) {
    			return null;
    		}
    		//调用sso的服务在redis中查询用户信息
    		String json = HttpClientUtils.doGet(SSO_BASE_URL + SSO_USER_TOKEN_SERVICE + token);
    		TaotaoResult result = TaotaoResult.format(json);
    		if (result.getStatus() != 200) {
    			return null;
    		}
    		//取用户对象数据
    		result = TaotaoResult.formatToPojo(json, TbUser.class);
    		TbUser user = (TbUser) result.getData();
    					
    		return user;
    					
    	} catch (Exception e) {
    		e.printStackTrace();
    		return null;
    	}
    }
    
@RequestMapping("/page/login")
public String showLogin(String redirectURL, Model model) {
	//将回调页面的url传给前端,通过前端js回调
	model.addAttribute("redirect", redirectURL);
	return "login";
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值