SSO基础

文章目录

  • SSO基础
    • 1.什么是单点登录?
    • 2.回顾普通系统登录
    • 3.多系统登录的问题与解决?
      • 3.1.Session不共享问题
  • XXL-SSO框架基础入门
    • 1.什么是XXL-SSO
    • 2.特性
    • 3. 官方Demo分析
      • 3.1 SSO Server中央认证服务
      • 3.2 SSO Client应用(Cookie形式)
    • 4.总结
  • 集成SSO服务
    • 引言
    • 1. 集成xxl-sso-core
    • 2. 集成xxl-server
    • 总结
  • 改造SSO登录界面
    • 引言
    • 1. 效果图
    • 2. 登录界面代码(前端+后台)
    • 3.总结
  • SSO单点登录(Client端集成)
    • 1.首页门户集成SSO Client
    • 2. 聚合支付门户集成SSO Client
    • 3. 测试
    • 4.显示登录的用户信息
    • 5.总结
  • SSO单点登录(退出登录)
    • 1. 效果演示
    • 2.退出功能实现
    • 总结
  • XXL-SSO登录逻辑
    • 1.XXL-SSO登录逻辑
    • 2.XXL-SSO注销逻辑
  • CSRF攻击
    • 1.CSRF是什么
    • 2.CSRF可以做什么
    • 3.CSRF漏洞现状
    • 4.CSRF的原理
    • 5.CSRF示例
      • 5.1.示例1:
      • 5.2.示例2:
      • 5.3.示例3:
      • 5.4.总结
    • 6.CSRF的防御
      • 6.1. 尽量使用POST,限制GET
      • 6.2.浏览器Cookie策略
      • 6.3.加验证码
      • 6.4.Referer Check
      • 6.5.Anti CSRF Token
      • 6.6.总结
  • 跨域(CORS)
    • 1.引言
    • 2.什么是跨域(CORS)
    • 3.什么情况会跨域(CORS)
    • 4.跨域流程
    • 5.解决跨域

1.什么是单点登录?

单点登录的英文名叫做:Single Sign On(简称SSO)。

在初学/以前的时候,一般我们就单系统,所有的功能都在同一个系统上。

后来,我们为了合理利用资源和降低耦合性,于是把单系统拆分成多个子系统。

比如阿里系的淘宝天猫,很明显地我们可以知道这是两个系统,但是你在使用的时候,登录了天猫,淘宝也会自动登录。

简单来说,单点登录就是在多个系统中,用户只需一次登录,各个系统即可感知该用户已经登录,只要在一个业务中退出,所有系统都退出

2.回顾普通系统登录

众所周知,HTTP是无状态的协议,这意味着服务器无法确认用户的信息。于是乎,W3C就提出了:给每一个用户都发一个通行证,无论谁访问的时候都需要携带通行证,这样服务器就可以从通行证上确认用户的信息。通行证就是Cookie。

如果说Cookie是检查用户身上的”通行证“来确认用户的身份,那么Session就是通过检查服务器上的”客户明细表“来确认用户的身份的。Session相当于在服务器中建立了一份“客户明细表”

HTTP协议是无状态的,Session不能依据HTTP连接来判断是否为同一个用户。于是乎:服务器向用户浏览器发送了一个名为JESSIONID的Cookie,它的值是Session的id值。其实Session是依据Cookie来识别是否是同一个用户

所以,一般我们单系统实现登录会这样做:

登录:将用户信息保存在Session对象中

  • 如果在Session对象中能查到,说明已经登录
  • 如果在Session对象中查不到,说明没登录(或者已经退出了登录)
    注销(退出登录):从Session中删除用户的信息
    记住我(关闭掉浏览器后,重新打开浏览器还能保持登录状态):配合Cookie来用

3.多系统登录的问题与解决?

3.1.Session不共享问题

单系统登录功能主要是用Session保存用户信息来实现的,但我们清楚的是:多系统即可能有多个Tomcat,而Session是依赖当前系统的Tomcat,所以系统A的Session和系统B的Session是不共享的。

解决系统之间Session不共享问题有一下几种方案:

  • Tomcat集群Session全局复制(集群内每个tomcat的session完全同步)【会影响集群的性能呢,不建议
  • 根据请求的IP进行Hash映射到对应的机器上(这就相当于请求的IP一直会访问同一个服务器)【如果服务器宕机了,会丢失了一大部分Session的数据,不建议】
  • 把Session数据放在Redis中(使用Redis模拟Session)【建议】

我们可以将登录功能单独抽取出来,做成一个子系统。

总结:

  • SSO系统生成一个token,并将用户信息存到Redis中,并设置过期时间
  • 其他系统请求SSO系统进行登录,得到SSO返回的token,写到Cookie中
  • 每次请求时,Cookie都会带上,拦截器得到token,判断是否已经登录

到这里,其实我们会发现其实就两个变化:

  • 将登陆功能抽取为一个系统(SSO),其他系统请求SSO进行登录
  • 本来将用户信息存到Session,现在将用户信息存到Redis

XXL-SSO框架基础入门

1.什么是XXL-SSO

XXL-SSO 是一个分布式单点登录框架。只需要登录一次就可以访问所有相互信任的应用系统。 拥有"轻量级、分布式、跨域、Cookie+Token均支持、Web+APP均支持"等特性。现已开放源代码,开箱即用。

我们先登录XXL-SSO官网:https://www.xuxueli.com/xxl-sso/

2.特性

1、简洁:API直观简洁,可快速上手
2、轻量级:环境依赖小,部署与接入成本较低
3、单点登录:只需要登录一次就可以访问所有相互信任的应用系统
4、分布式:接入SSO认证中心的应用,支持分布式部署
5、HA:Server端与Client端,均支持集群部署,提高系统可用性
6、跨域:支持跨域应用接入SSO认证中心
7、Cookie+Token均支持:支持基于Cookie基于Token两种接入方式,并均提供Sample项目
8、Web+APP均支持:支持Web和APP接入
9、实时性:系统登陆、注销状态,全部Server与Client端实时共享
10、CS结构:基于CS结构,包括Server"认证中心"与Client"受保护应用"
11、记住密码:未记住密码时,关闭浏览器则登录态失效;记住密码时,支持登录态自动延期,在自定义延期时间的基础上,原则上可以无限延期
12、路径排除:支持自定义多个排除路径,支持Ant表达式,用于排除SSO客户端不需要过滤的路径

3. 官方Demo分析

首先我们从Github 克隆XXL-SSO的源码到本地(https://github.com/xuxueli/xxl-sso.git):

下载完源码,我们可以看到目录结构如下:

3.1 SSO Server中央认证服务

打开xxl-sso-server目录,可以看到有如下结构:

他们分别表示:

打开xxl-sso-server的配置文件,可以看到需要配置Redis地址,在这里配置好Redis地址:

启动xxl-sso-server

日志文件的位置!

可以看到启动成功:

3.2 SSO Client应用(Cookie形式)

SSO 认证中心已经配置好并打开了,下面我们来看看SSO Client端。

打开samples下的xxl-sso-web-sample-springboot项目,并配置redis路径(与认证中心的一致):

在上图可以看到xxl.sso.server对应的值为:http://xxlssoserver.com:8080/xxl-sso-server,这里用到了域名,所以要在我们本地localhost文件里配置域名

启动成功:

浏览器输入:http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot

可以看到自动跳转到了SSO 认证服务中心的登录页面了,url地址变为如下,可以看到携带了一个redirect_url,指的就是登录成功后重定向的地址:

http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot/

为了更好的验证单点登录,我们复制xxl-sso-web-sample-springboot项目命名为xxl-sso-web-sample-springboot8083,并设置端口号为8083

并在hosts文件增加配置:

启动复制的项目

好了,可以开始验证了。首先浏览器输入Client1服务地址:http://xxlssoclient1.com:8081/xxl-sso-web-sample-springboot,会自动跳转到授权中心:

点击登录,可看到登录成功,而且登录成功后的sessionid在地址栏也能看到。

接下来看看Client2是否需要再次登录,浏览器输入:http://xxlssoclient2.com:8083/xxl-sso-web-sample-springboot

可以看到Client2也登录成功了,而且sessionid与Client1的一样。

最后,我们看看浏览器的Cookie信息,观察发现他们的sessionid也是一致的:

clinent1

client2

打开Redis可视化窗口,可以看到Redis服务器有保存SessionId:

4.总结

本文主要讲解了单点登录的相关概念,已经使用xxl-sso框架来做演示。

集成SSO服务

引言

主要讲解了SSO单点登录的一些概念,以及使用国产的XXL-SSO单点登录例子来熟悉了单点登录的整个流程。

本文将把XXL-SSO框架集成到我们的项目中,本文先集成SSO 认证服务。

1. 集成xxl-sso-core

本来我是不打算把xxl-core集成到电商项目的,阅读文档里也没发现有最新的版本发布到仓库,只是更新了代码。远程maven仓库最新的版本为1.1.0,而代码最新版本为1.1.1了,如下图:

所以我打算把xxl-sso-core最新的代码直接复制到我们的项目使用。

首先在电商项目通用模块里添加xxl-core模块:

把xxl-core源码复制过去,包括maven依赖:

复制成功,没报错。

2. 集成xxl-server

在基础设施包里新增xxl-sso-server:

添加xxl-core的maven依赖:

<dependency><groupId>com.guoranxinxian</groupId><artifactId>guoranxinxian-shop-common-xxlsso-core</artifactId><version>1.0-SNAPSHOT</version>
</dependency><!-- freemarker --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency>

复制代码和resources里面的内容:

修改配置文件:

### web
server.port=8099
#server.servlet.context-path=/xxl-sso-server### resources
spring.mvc.servlet.load-on-startup=0
spring.mvc.static-path-pattern=/static/**
spring.resources.static-locations=classpath:/static/### freemarker
spring.freemarker.templateLoaderPath=classpath:/templates/
spring.freemarker.suffix=.ftl
spring.freemarker.charset=UTF-8
spring.freemarker.request-context-attribute=request
spring.freemarker.settings.number_format=0.############# xxl-sso
xxl.sso.redis.address=redis://127.0.0.1:6379
xxl.sso.redis.expire.minute=1440
eureka.client.service-url.defaultZone=http://127.0.0.1:8080/eurekaspring.application.name=guoranxinxian-shop-basics-xxlsso-server

启动类增加@EnableEurekaClient注解,启动注册中心,和SSO Server:

package com.xxl.sso.server;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;@SpringBootApplication
@EnableEurekaClient
public class XxlSsoServerApplication {public static void main(String[] args) {SpringApplication.run(XxlSsoServerApplication.class, args);}
}


浏览器输入地址:http://localhost:8099/,会自动跳转到认证授权中心登录页面


点击Login,登录成功:

总结

本文主要讲解集成SSO认证服务。

改造SSO登录界面

引言

在上一篇主要讲解了如何集成SSO认证中心,集成成功后,登录界面和登录成功界面如下图所示:

登录

登录成功

但是这个登录和主界面并不是我们想要的,本文先来来讲解如何改造登录界面。

注意:我在hosts文件里添加了如下内容,之后的博客都用这些域名:

1. 效果图

下面先贴上效果图(主界面先暂时替代,涉及其它的知识点,下篇博客继续完善):

登录界面

登录成功界面

2. 登录界面代码(前端+后台)

先贴上前端代码(核心代码,注意里面携带了redirect_url,隐藏起来了),改造原来自带的登录页面

<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta http-equiv="X-UA-Compatible" content="ie=edge" /><link rel="shortcut icon" href="/static/img/page-common/favicon.ico" type="image/x-icon" /><title>果然新鲜 - 登录</title><link rel="stylesheet" type="text/css" href="/static/css/page-common.css" /><link rel="stylesheet" type="text/css" href="/static/css/page-login.css" /><link rel="stylesheet" type="text/css" href="/static/css/page-login-header.css" />
</head><body>
<!-- 网页头部开始 -->
<script type="text/javascript" src="/static/js/page-login-header.js" charset="UTF-8"></script>
<!-- 网页头部结束 --><!-- 网页主体开始 -->
<div class="fresh-main-fluid" style="width: 100%;height:100%;background:#2663b6;"><div class="fresh-main fresh-center fresh-clearfix"><div class="fresh-body-1"><div class="fresh-img"> <img src="/static/img/page-login/bg.png" /> </div><div class="fresh-loginbox"><h2>账号登录<span style="color: red">${error!''}</span></h2><form action="doLogin" method="post"><div class="fresh-loginbox-text"> <p>手机号</p><div> <img src="/static/img/page-login/denglu.png" /><input type="text" name="mobile" value="${(loginVo.mobile)!''}" id="mobile" placeholder="请输入手机号码" /></div></div><div class="fresh-loginbox-text"> <p>密码</p><div> <img src="/static/img/page-login/mima.png" /><input type="password" name="password" id="password" value="${(loginVo.password)!''}" placeholder="请输入密码" /></div></div><div class="fresh-loginbox-text"> <p>验证码</p><div> <img src="/static/img/page-login/mima.png" /><input type="text" name="graphicCode" id="graphicCode" placeholder="请输入验证码" /><img src="/getVerify" style="width: 80px;" id="getverification" onclick="getVerify(this);"/></div></div><div class="fresh-login-forget"> <a href="forget.html">忘记密码</a> </div><div class="fresh-login-submit"><input type="hidden" name="redirect_url" value="${RequestParameters['redirect_url']!''}" /><input type="submit" value="登录" /></div><div class="fresh-login-thirdlogin"> <a href="#">——&nbsp;&nbsp;第三方登录&nbsp;&nbsp; ——</a> </div><div class="fresh-login-loginmode"><div> <a href="/qqAuth"> <img src="/static/img/page-login/qq.png" /> </a><a href="#"> <img src="/static/img/page-login/weixin.png" /> </a><a href="#"> <img src="/static/img/page-login/weibo.png" /> </a></div></div><div class="fresh-login-Register"> <a href="register.html">立即注册</a> </div></form></div></div></div>
</div>
<!-- 网站主体结束 --><!-- 网页底部开始 -->
<script type="text/javascript" src="/static/js/page-footer.js" charset="UTF-8"></script>
<!-- 网页底部结束 --><script type="text/javascript" src="/static/plugins/jquery/jquery-1.12.4.min.js"></script><script>//获取验证码function getVerify(obj) {obj.src = "getVerify?" + Math.random();}</script></body>
</html>

WebController层代码(现在业务系统查询用户是否存在,然后使用XXL-SSO框架登录):

package com.xxl.sso.server.controller;import com.guoranxinxian.api.BaseResponse;
import com.guoranxinxian.common.base.BaseWebController;
import com.guoranxinxian.common.util.RandomValidateCodeUtil;
import com.guoranxinxian.common.util.WebBeanUtils;
import com.guoranxinxian.constants.Constants;
import com.guoranxinxian.member.dto.input.UserLoginInDTO;
import com.guoranxinxian.member.dto.output.UserLoginInOutDTO;
import com.xxl.sso.core.conf.Conf;
import com.xxl.sso.core.login.SsoWebLoginHelper;
import com.xxl.sso.core.store.SsoLoginStore;
import com.xxl.sso.core.store.SsoSessionIdHelper;
import com.xxl.sso.core.user.XxlSsoUser;
import com.xxl.sso.server.controller.req.vo.LoginVo;
import com.xxl.sso.server.feign.MemberLoginServiceFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.UUID;@Controller
public class WebController extends BaseWebController {/*** 跳转到登陆页面页面*/private static final String MB_LOGIN_FTL = "login";@Autowiredprivate MemberLoginServiceFeign memberLoginServiceFeign;/*** 重定向到首页*/private static final String REDIRECT_INDEX = "redirect:/";@RequestMapping("/")public String index(Model model, HttpServletRequest request, HttpServletResponse response) {XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response);if (xxlUser == null) {return "redirect:/login";} else {model.addAttribute("xxlUser", xxlUser);return "index";}}@RequestMapping(Conf.SSO_LOGIN)public String login(Model model, HttpServletRequest request, HttpServletResponse response) {// login checkXxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(request, response);if (xxlUser != null) {// success redirectString redirectUrl = request.getParameter(Conf.REDIRECT_URL);if (redirectUrl!=null && redirectUrl.trim().length()>0) {String sessionId = SsoWebLoginHelper.getSessionIdByCookie(request);String redirectUrlFinal = redirectUrl + "?" + Conf.SSO_SESSIONID + "=" + sessionId;;return "redirect:" + redirectUrlFinal;} else {return "redirect:/";}}model.addAttribute("errorMsg", request.getParameter("errorMsg"));model.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));return "login";}/*** 接受请求参数** @return*/@PostMapping("/doLogin")public String postLogin(@ModelAttribute("loginVo") @Validated LoginVo loginVo,BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes, HttpServletRequest request,HttpServletResponse response, HttpSession httpSession, String ifRemember) {if (bindingResult.hasErrors()) {// 如果参数有错误的话// 获取第一个错误!String errorMsg = bindingResult.getFieldError().getDefaultMessage();setErrorMsg(model, errorMsg);return MB_LOGIN_FTL;}// 1.图形验证码判断String graphicCode = loginVo.getGraphicCode();if (!RandomValidateCodeUtil.checkVerify(graphicCode, httpSession)) {setErrorMsg(model, "图形验证码不正确!");return MB_LOGIN_FTL;}// 2.将vo转换dto调用会员登陆接口UserLoginInDTO userLoginInpDTO = WebBeanUtils.voToDto(loginVo, UserLoginInDTO.class);userLoginInpDTO.setLoginType(Constants.MEMBER_LOGIN_TYPE_PC);String info = webBrowserInfo(request);userLoginInpDTO.setDeviceInfor(info);BaseResponse<UserLoginInOutDTO> login = memberLoginServiceFeign.ssoLogin(userLoginInpDTO);if (!isSuccess(login)) {setErrorMsg(model, login.getMsg());return MB_LOGIN_FTL;}UserLoginInOutDTO data = login.getData();XxlSsoUser xxlUser = new XxlSsoUser();xxlUser.setUserid(data.getToken());xxlUser.setUsername(data.getUserName());xxlUser.setVersion(UUID.randomUUID().toString().replaceAll("-", ""));xxlUser.setExpireMinute(SsoLoginStore.getRedisExpireMinute());xxlUser.setExpireFreshTime(System.currentTimeMillis());// 设置sessionidString sessionId = SsoSessionIdHelper.makeSessionId(xxlUser);// 认证服务登录boolean ifRem = (ifRemember != null && "on".equals(ifRemember)) ? true : false;SsoWebLoginHelper.login(response, sessionId, xxlUser, ifRem);// 4、return, redirect sessionIdString redirectUrl = request.getParameter(Conf.REDIRECT_URL);if (redirectUrl != null && redirectUrl.trim().length() > 0) {String redirectUrlFinal = redirectUrl + "?" + Conf.SSO_SESSIONID + "=" + sessionId;return "redirect:" + redirectUrlFinal;} else {return "redirect:/";}}@RequestMapping(Conf.SSO_LOGOUT)public String logout(HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttributes) {// logoutSsoWebLoginHelper.logout(request, response);redirectAttributes.addAttribute(Conf.REDIRECT_URL, request.getParameter(Conf.REDIRECT_URL));return "redirect:/login";}}

3.总结

本文主要讲解了XXL-SSO认证服务的登录界面改造。

SSO单点登录(Client端集成)

1.首页门户集成SSO Client

1.Maven添加xxl-sso-core模块:

<dependency><artifactId>guoranxinxian-shop-common-xxlsso-core</artifactId><groupId>com.guoranxinxian</groupId><version>1.0-SNAPSHOT</version>
</dependency>

2.配置applicatoin.yml,完整内容如下(注意要在hosts文件里配置好域名):

3.添加配置文件

spring.redis.hostName=127.0.0.1
spring.redis.port=6379xxl.sso.logout.path=/logout
xxl.sso.server=http://guoranxinxian.ssoserver.com:8099
xxl-sso.excluded.paths=
package com.guoranxinxian.config;import com.xxl.sso.core.conf.Conf;
import com.xxl.sso.core.filter.XxlSsoWebFilter;
import com.xxl.sso.core.util.JedisUtil;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class XxlSsoConfig implements DisposableBean {@Value("${xxl.sso.server}")private String xxlSsoServer;@Value("${xxl.sso.logout.path}")private String xxlSsoLogoutPath;@Value("${xxl-sso.excluded.paths}")private String xxlSsoExcludedPaths;@Value("${spring.redis.host}")private String redisHost;@Value("${spring.redis.port}")private String port;@Beanpublic FilterRegistrationBean xxlSsoFilterRegistration() {// xxl-sso, redis initJedisUtil.init(String.format("redis://%s:%s", redisHost, port));// xxl-sso, filter initFilterRegistrationBean registration = new FilterRegistrationBean();registration.setName("XxlSsoWebFilter");registration.setOrder(1);registration.addUrlPatterns("/*");registration.setFilter(new XxlSsoWebFilter());registration.addInitParameter(Conf.SSO_SERVER, xxlSsoServer);registration.addInitParameter(Conf.SSO_LOGOUT_PATH, xxlSsoLogoutPath);registration.addInitParameter(Conf.SSO_EXCLUDED_PATHS, xxlSsoExcludedPaths);return registration;}@Overridepublic void destroy() throws Exception {// xxl-sso, redis closeJedisUtil.close();}}

2. 聚合支付门户集成SSO Client

创建聚合支付门户模块guoranxinxian-shop-portal-pay-web,具体的代码不再详述,可以clone代码下来看,SSO Client方式与上面一样:

3. 测试

1.启动Eureka服务、SSO认证服务、会员服务门户服务聚合支付服务`。

2.浏览器访问门户服务(注意:hosts文件已经配置了域名)http://guoranxinxian.com:8080/,浏览器自动跳转到登录界面:

3.输入登录信息,执行登录操作,登录成功,可以看到登录成功后,地址栏的url也发生改变了http://guoranxinxian.com:8080/?xxl_sso_sessionid=27_c11ef89924a4465cbf395bfefcafc63d:

同时,看下cookie信息,也把session id自动写入了浏览器的cookie:

4.访问聚合支付门户http://guoranxinxian.pay.com:8079/,可以看到直接就跳转到了聚合支付的首页了,而且浏览器的Session id与门户服务的session id一样:

4.显示登录的用户信息

     @GetMapping("/")public String index(HttpServletRequest request, HttpServletResponse response, Model model){XxlSsoUser xxlUser = (XxlSsoUser) request.getAttribute(Conf.SSO_USER);if (xxlUser != null && StringUtils.isNotEmpty(xxlUser.getUserid())) {DataResults<Users> results = usersFeign.getByUserId(Long.valueOf(xxlUser.getUserid()));if(results.getData()!=null){String mobile = results.getData().getMobile();// 对手机号码实现脱敏String desensMobile = mobile.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");model.addAttribute("desensMobile", desensMobile);}}model.addAttribute("goods_fresh_fruits",itemServiceFeign.findGoodsByCategory1Id(1001).getData()); // 新鲜水果 1001model.addAttribute("goods_fresh_fish",itemServiceFeign.findGoodsByCategory1Id(1038).getData()); // 海鲜水产 1038List<Content> content_top= (List<Content>) redisTemplate.opsForValue().get("redis_content_top");if(content_top==null||content_top.size()==0){content_top=contentServiceFeign.findContentBycategoryId(1).getData();redisTemplate.opsForValue().set("redis_content_top",content_top,3, TimeUnit.MINUTES);  //3分刷新缓存}model.addAttribute("content_top",content_top); // 轮播图model.addAttribute("content_fresh_fruits",contentServiceFeign.findContentBycategoryId(3).getData()); // 新鲜水果主体return "index";}

<li th:if="${desensMobile==null}"><a href="login.html">您好,请登录</a></li><li th:if="${desensMobile!=null}"><a href="login.html" th:text="|您好,${desensMobile}|">您好,请登录</a></li>
<li>
<a href="register.html">免费注册</a></li><li><a href="home-order.html">我的订单</a></li><li th:if="${desensMobile!=null}"><a href="javascript:void(0);" onclick="logout();">退出</a></li><li><a href="home-person-footprint.html">我的足迹</a></li>

5.总结

本文主要讲解SSO Client集成与测试。

SSO单点登录(退出登录)

1. 效果演示

首先启动Eureka注册中心、SSO服务、会员服务、门户服务、聚合支付服务

登录门户,浏览器输入http://guoranxinxian.com:8080,登录成功。

访问聚合支付门户,浏览器输入:http://guoranxinxian.pay.com:8079/,可以看到没走登录直接就进入了。

好的,可以看退出效果的演示了,在门户首页点击退出

点击后,自动跳转到了登录页了:

刷新聚合支付页面,可以看到也自动跳转到了登录页面了:

从上面演示效果可以看出:一端退出,所有端都退出。

2.退出功能实现

前端代码(核心代码):

 <!--引入JQuery--><script type="text/javascript" src="plugins/jquery/jquery-1.12.4.min.js"></script><script type="text/javascript">function logout() {if(confirm("确定退出吗?")){$.ajax({type: "delete",//url: "exit",url: "ssoExit",contentType: "application/json",dataType: "json",success: function (result) {if(result.code==200){window.location.href = "/";}},error: function (result) {}});}}</script>

Controller层代码:

@RestController
public class LogoutController {@DeleteMapping("/ssoExit")@ResponseBodypublic DataResults logout(HttpServletRequest request, HttpServletResponse response, Model model) {// logoutXxlSsoUser xxlUser = (XxlSsoUser) request.getAttribute(Conf.SSO_USER);SsoWebLoginHelper.logout(request, response);return DataResults.success(ResultCode.SUCCESS);}
}

退出成功后,可以看到浏览器Cookie信息为空,Redis保存的内容也移除了,数据库更新为未登录。

Cookie

Redis

总结

本文主要讲解SSO单点退出的功能。

XXL-SSO登录逻辑

1.XXL-SSO登录逻辑


代码逻辑描述

  1. 访问pro.com,获取pro.com域的cookie(xxl_sso_sessionid,由userId_随机数码组成)为空,从请求参数获取cookie为空;
  2. 获取用户信息为空,重定向sso服务;
  3. sso服务,获取sso.com域cook’ie为空,获取用户信息为空,跳转登陆页
  4. 登录页输入用户名密码登陆,登陆成功,
1、创建用户对象,
2、创建sessionid(userId_user版本号),
3、response设置cookie,
4、radis设置key(xxl_sso_sessionid,#,usrid组成),用户对象, 失效时间
  1. 重定向pro.com?xxl_sso_sessionid=xxl_sso_sessionid;
  2. 获取pro.com域的cookie(xxl_sso_sessionid,由userId_随机数码组成)为空,从请求参数获取cookie,根据cookie查询raids获取用户对象;
  3. 如果当前时间超过刷新时间一半的时候,重新设置radis数据的有效时间;设置pro.com,cookie值
  4. 跳转请求页面;
  5. 访问pro1.com,获取pro1.com,cookie以及url参数cookie失败,获取对象失败,重定向sso.com服务
  6. sso服务,获取sso.com域cook’ie,根据cookie查询raids获取用户对象
  7. 重定向pro1.com?xxl_sso_sessionid=xxl_sso_sessionid;
  8. 后面逻辑与6,7,8相同
  9. 再次访问pro.com,pro1.com,只需要验证本域下的cookie;

2.XXL-SSO注销逻辑

代码逻辑

  1. 用户注销pro.com,销毁pro.com下的cookie;重定向sso.com,销毁sso.com下的cookie,删除radis下的用户信息,跳转登录页。
  2. 用户访问pro1.com,从pro1.com下获取cookie,从raids查询用户信息失败,无法返回用户信息登陆失败,重定向sso.com服务,获取sso.com域下cookie失败,从raids查询用户信息失败,跳转登陆页。

cookie可能会受到防跨站请求伪造(CSRF)攻击,token可以解决这个问题

举个CSRF攻击的例子,在网页中有这样的一个链接
(http://bank.com?withdraw=1000&to=tom),假设你已经通过银行的验证并且cookie中存在验证信息,同时银行网站没有CSRF保护。一旦用户点了这个图片,就很有可能从银行向tom这个人转1000块钱。

但是如果银行网站使用了token作为验证手段,攻击者将无法通过上面的链接转走你的钱。(因为攻击者无法获取正确的token)

CSRF攻击

1.CSRF是什么

CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。

2.CSRF可以做什么

你这可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账…造成的问题包括:个人隐私泄露以及财产安全。

3.CSRF漏洞现状

CSRF这种攻击方式在2000年已经被国外的安全人员提出,但在国内,直到06年才开始被关注,08年,国内外的多个大型社区和交互网站分别爆出CSRF漏洞,如:NYTimes.com(纽约时报)、Metafilter(一个大型的BLOG网站),YouTube和百度HI…而现在,互联网上的许多站点仍对此毫无防备,以至于安全业界称CSRF为“沉睡的巨人”。

4.CSRF的原理

下图简单阐述了CSRF攻击的思想:

从上图可以看出,要完成一次CSRF攻击,受害者必须依次完成两个步骤:

  1. 登录受信任网站A,并在本地生成Cookie。
  2. 在不登出A的情况下,访问危险网站B。

看到这里,你也许会说:“如果我不满足以上两个条件中的一个,我就不会受到CSRF的攻击”。是的,确实如此,但你不能保证以下情况不会发生:

  1. 你不能保证你登录了一个网站后,不再打开一个tab页面并访问另外的网站。

  2. 你不能保证你关闭浏览器了后,你本地的Cookie立刻过期,你上次的会话已经结束。(事实上,关闭浏览器不能结束一个会话,但大多数人都会错误的认为关闭浏览器就等于退出登录/结束会话了…)

5.CSRF示例

5.1.示例1:

银行网站A,它以GET请求来完成银行转账的操作,如:http://www.mybank.com/Transfer.php?toBankId=11&money=1000

危险网站B,它里面有一段HTML的代码如下:

<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>

首先,你登录了银行网站A,然后访问危险网站B,噢,这时你会发现你的银行账户少了1000块…

为什么会这样呢?

原因是银行网站A违反了HTTP规范,使用GET请求更新资源。在访问危险网站B的之前你已经登录了银行网站A,而B中的<img/>以GET的方式请求第三方资源(这里的第三方就是指银行网站了,原本这是一个合法的请求,但这里被不法分子利用了),所以你的浏览器会带上你的银行网站A的Cookie发出Get请求,去获取资源“http://www.mybank.com/Transfer.php?toBankId=11&money=1000”,结果银行网站服务器收到请求后,认为这是一个更新资源操作(转账操作),所以就立刻进行转账操作…

5.2.示例2:

为了杜绝上面的问题,银行决定改用POST请求完成转账操作。

银行网站A的WEB表单如下:

<form action="Transfer.php" method="POST"><p>ToBankId: <input type="text" name="toBankId" /></p><p>Money: <input type="text" name="money" /></p><p><input type="submit" value="Transfer" /></p>
</form>

后台处理页面Transfer.php如下:

<?php    
session_start();    
if (isset($_REQUEST['toBankId'] && isset($_REQUEST['money'])) {       buy_stocks($_REQUEST['toBankId'], $_REQUEST['money']);    } 
?>

危险网站B,仍然只是包含那句HTML代码:

<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
和示例1中的操作一样,你首先登录了银行网站A,然后访问危险网站B,结果.....和示例1一样,你再次没了1000块~T_T,这次事故的原因是:银行后台使用了$_REQUEST去获取请求的数据,而$_REQUEST既可以获取GET请求的数据,也可以获取POST请求的数据,这就造成了在后台处理程序无法区分这到底是GET请求的数据还是POST请求的数据。在PHP中,可以使用$_GET和$_POST分别获取GET请求和POST请求的数据。在JAVA中,用于获取请求数据request一样存在不能区分GET请求数据和POST数据的问题。

5.3.示例3:

经过前面2个惨痛的教训,银行决定把获取请求数据的方法也改了,改用$_POST,只获取POST请求的数据,后台处理页面Transfer.php代码如下:

<?phpsession_start();if (isset($_POST['toBankId'] && isset($_POST['money'])){buy_stocks($_POST['toBankId'], $_POST['money']);}?>

然而,危险网站B与时俱进,它改了一下代码:

<html><head>
<script type="text/javascript">function steal(){iframe = document.frames["steal"];iframe.document.Submit("transfer");}</script></head><body onload="steal()"><iframe name="steal" display="none"><form method="POST" name="transfer" action="http://www.myBank.com/Transfer.php"><input type="hidden" name="toBankId" value="11"><input type="hidden" name="money" value="1000"></form></iframe></body>
</html>

如果用户仍是继续上面的操作,很不幸,结果将会是再次不见1000块…因为这里危险网站B暗地里发送了POST请求到银行!

5.4.总结

上面3个例子,CSRF主要的攻击模式基本上是以上的3种,其中以第1,2种最为严重,因为触发条件很简单,一个<img>就可以了,而第3种比较麻烦,需要使用JavaScript,所以使用的机会会比前面的少很多,但无论是哪种情况,只要触发了CSRF攻击,后果都有可能很严重。

CSRF攻击的本质原因

CSRF攻击是源于Web的隐式身份验证机制!Web的身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的。CSRF攻击的一般是由服务端解决。

6.CSRF的防御

6.1. 尽量使用POST,限制GET

GET接口太容易被拿来做CSRF攻击,看第一个示例就知道,只要构造一个img标签,而img标签又是不能过滤的数据。接口最好限制为POST使用,GET则无效,降低攻击风险。

当然POST并不是万无一失,攻击者只要构造一个form就可以,但需要在第三方页面做,这样就增加暴露的可能性。

6.2.浏览器Cookie策略

IE6、7、8、Safari会默认拦截第三方本地Cookie(Third-party Cookie)的发送。但是Firefox2、3、Opera、Chrome、Android等不会拦截,所以通过浏览器Cookie策略来防御CSRF攻击不靠谱,只能说是降低了风险。

PS:Cookie分为两种,Session Cookie(在浏览器关闭后,就会失效,保存到内存里),Third-party Cookie(即只有到了Exprie时间后才会失效的Cookie,这种Cookie会保存到本地)。

6.3.加验证码

验证码,强制用户必须与应用进行交互,才能完成最终请求。在通常情况下,验证码能很好遏制CSRF攻击。但是出于用户体验考虑,网站不能给所有的操作都加上验证码。因此验证码只能作为一种辅助手段,不能作为主要解决方案。

6.4.Referer Check

Referer Check在Web最常见的应用就是“防止图片盗链”。同理,Referer Check也可以被用于检查请求是否来自合法的“源”(Referer值是否是指定页面,或者网站的域),如果都不是,那么就极可能是CSRF攻击。

但是因为服务器并不是什么时候都能取到Referer,所以也无法作为CSRF防御的主要手段。但是用Referer Check来监控CSRF攻击的发生,倒是一种可行的方法。

6.5.Anti CSRF Token

现在业界对CSRF的防御,一致的做法是使用一个Token。
例子:

  1. 用户访问某个表单页面。

  2. 服务端生成一个Token,放在用户的Session中,或者浏览器的Cookie中。

  3. 在页面表单附带上Token参数。

  4. 用户提交请求后, 服务端验证表单中的Token是否与用户Session(或Cookies)中的Token一致,一致为合法请求,不是则非法请求。

这个Token的值必须是随机的,不可预测的。由于Token的存在,攻击者无法再构造一个带有合法Token的请求实施CSRF攻击。另外使用Token时应注意Token的保密性,尽量把敏感操作由GET改为POST,以form或AJAX形式提交,避免Token泄露。

6.6.总结

CSRF攻击是攻击者利用用户的身份操作用户帐户的一种攻击方式,通常使用Anti CSRF Token来防御CSRF攻击,同时要注意Token的保密性和随机性。

跨域(CORS)

1.引言

我们在开发过程中经常会遇到前后端分离而导致的跨域问题,导致无法获取返回结果。跨域就像分离前端和后端的一道鸿沟,君在这边,她在那边,两两不能往来.

2.什么是跨域(CORS)

跨域(CORS)是指不同域名之间相互访问。跨域,指的是浏览器不能执行其他网站的脚本,它是由浏览器的同源策略所造成的,是浏览器对于JavaScript所定义的安全限制策略。

3.什么情况会跨域(CORS)

  • 同一协议, 如http或https
  • 同一IP地址, 如127.0.0.1
  • 同一端口, 如8080

以上三个条件中有一个条件不同就会产生跨域问题。

4.跨域流程

参考地址:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS

5.解决跨域

配置当次请求允许跨域

解决方法:在网关中定义“CorsConfig”类,该类用来做过滤,允许所有的请求跨域。

package com.microservice.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;//配置过滤器,解决跨域问题
@Configuration
public class CorsConfig {private CorsConfiguration buildConfig() {CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.addAllowedOrigin("*"); //允许任何域名使用corsConfiguration.addAllowedHeader("*"); //允许任何头corsConfiguration.addAllowedMethod("*"); //允许任何方法(post、get等)return corsConfiguration;}@Beanpublic CorsFilter corsFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", buildConfig());return new CorsFilter(source);}
}

一篇了解SSO单点登录相关推荐

  1. Spring Cloud云架构 - SSO单点登录之OAuth2.0登录流程(2)

    上一篇是站在巨人的肩膀上去研究OAuth2.0,也是为了快速帮助大家认识OAuth2.0,闲话少说,我根据框架中OAuth2.0的使用总结,画了一个简单的流程图(根据用户名+密码实现OAuth2.0的 ...

  2. 学习CAS实现SSO单点登录

    学习CAS实现SSO单点登录 网上找了几篇比较详细的教程,在这记录一下: 原理: CAS实现SSO单点登录原理 教程: 1.CAS实现单点登录(SSO)经典完整教程 2.SSO之CAS单点登录实例演示 ...

  3. JAVA springboot ssm b2b2c多用户商城系统源码-SSO单点登录之OAuth2.0登录流程(2)

    上一篇是站在巨人的肩膀上去研究OAuth2.0,也是为了快速帮助大家认识OAuth2.0,闲话少说,我根据框架中OAuth2.0的使用总结,画了一个简单的流程图(根据用户名+密码实现OAuth2.0的 ...

  4. Spring Cloud云架构 - SSO单点登录之OAuth2.0 根据token获取用户信息(4)

    上一篇我根据框架中OAuth2.0的使用总结,画了SSO单点登录之OAuth2.0 登出流程,今天我们看一下根据用户token获取yoghurt信息的流程: /** * 根据token获取用户信息 * ...

  5. java ssm 多租户_(十一)java B2B2C 源码 多级分销springmvc mybatis多租户电子商城系统- SSO单点登录之OAuth2.0登录流程(2)...

    上一篇是站在巨人的肩膀上去研究OAuth2.0,也是为了快速帮助大家认识OAuth2.0,闲话少说,我根据框架中OAuth2.0的使用总结,画了一个简单的流程图(根据用户名+密码实现OAuth2.0的 ...

  6. (八)java版spring cloud+spring boot+redis多租户社交电子商务平台 -SSO单点登录之OAuth2.0登录认证(2)...

    电子商务平台源码请加企鹅求求:一零三八七七四六二六.上一篇是站在巨人的肩膀上去研究OAuth2.0,也是为了快速帮助大家认识OAuth2.0,闲话少说,我根据框架中OAuth2.0的使用总结,画了一个 ...

  7. Docker 创建 Bamboo6.7.1 以及与 Crowd3.3.2 实现 SSO 单点登录

    目录 目录 1.介绍 1.1.什么是 Bamboo? 2.Bamboo 的官网在哪里? 3.如何下载安装? 4.对 Bamboo 进行配置 4.1.获取授权许可 4.2.一般配置 4.3.数据库配置 ...

  8. springsecurity oauth2.0 集成sso单点登录

    前言 在前两篇中,我们基本上了解springsecurity 的授权码模式和密码模式的工作流程,其实来说,掌握了授权码模式,再基于springsecurity 做单点登录的集成就是一件非常容易的事情 ...

  9. 【实战】从零搭建SSO单点登录服务器 - CAS认证流程

    前言 因系统逐渐增多,各个业务系统间无法共享用户状态,每个系统都需要用户登录.这对于用户来说很不友好,于是需要搭建一个SSO单点登录服务器,来做统一的登录.注销. 写这个系列的文章有两个目的: 记录自 ...

最新文章

  1. CTF web题总结--绕过正则表达式
  2. 小程序之图片懒加载[完美方案,你不来看看?]
  3. RabbitMQ持久化交换机队列
  4. Asp.Net中虚拟文件系统的使用
  5. android 使用以太网共享4g网络_案例 | 东土科技Aquam系列重新定义列车骨干网络!...
  6. Linux环境下搭建FTP服务器
  7. 利用@jsonView注解来实现自定义返回字段
  8. php页面怎么去登录,php中登录后跳转回原来要访问的页面实例
  9. 无需共享存储发布高性能的虚拟桌面
  10. php mysql 任务队列_PHP+MySQL实现消息队列步骤详解
  11. windows远程桌面神器
  12. ios上传图片遇见了一个TimeoutError(DOM Exception 23)异常
  13. 后渗透篇:清理windows入侵痕迹总结【详细】
  14. 号码被标记,各平台取消方法
  15. java 按照笔画排序,怎样用java把名单按姓氏笔画排序
  16. iMeta | ggClusterNet微生物网络分析和可视化保姆级教程
  17. 乐千业:税务筹划对企业的经营到底有多重要呢
  18. 杭电oj1052题:Tian Ji -- The Horse Racing
  19. 北大青鸟深圳嘉华分享MySQL基础知识
  20. 有啊怎么才能抗衡淘宝

热门文章

  1. Onlyoffice 添加中文字体,并修改字体大小显示为小四、四号等中文
  2. GdPicture.NET SDK Crack,编辑、捕获或打印文档
  3. 相遇周期 HDU - 1713
  4. Python 绘制中国地图并标上国家名
  5. 常用CAD快捷键命令大全
  6. Ubuntu 16.04 MongoDB数据库备份与恢复
  7. EpilepsyGAN:具有隐私保护的合成癫痫脑活动-2021(同26)
  8. 记录一下自己常用的数据库,以备不时之需
  9. 如何获取国外动态住宅ip并使用?
  10. 调用webservice接口,报错:(十六进制值0x01)是无效的字符。