目录
商城业务-认证服务-自定义SpringSession完成子域session共享
商城业务-认证服务-环境搭建
1.创建认证服务
①使用spring初始化向导创建服务
②选择SpringBoot的版本,添加服务需要的依赖
需要注册中心和配置中心的依赖,因此,导入common,还需排除mybatis的依赖,因为不用数据库
2. 开启服务注册功能
①配置配置文件
②开启服务注册功能
Nginx动静分类
①配置域名
②templates中导入登录、注册的html
将注册页面的index.html改名为reg.html,登录页面的html暂时不改名方便后面测试能否获取到Ngixn的静态资源,因为SpringMVC默认访问templates下的index.html,否则需要Controller路由
③上传静态资源至Nginx
④修改访问路径(CTRL+R)
⑤配置网关
商城业务-认证服务-好玩的验证码倒计时
1.编写路径跳转的Controller
如果编写一个接口仅仅是为了跳转页面,没有数据的处理,如果这样的跳转接口多了则可以使用SpringMVC的view Controller(视图控制器)将请求与页面进行绑定
2.为了页面修改能实时看到效果,关闭thymeleaf的缓存
3. 修改首页、注册页、登录页的正确跳转
①点击谷粒商城正确跳转首页
②点击立即注册跳转注册页面
③点击登录跳转登录页面、点击注册跳转注册页面
④点击请登录跳转登录页面
⑤点击谷粒商城跳转首页页面
4.倒计时60s的编写
①为sendCode绑定点击事件
出现问题:可以多次点击,多个事件会叠加在一起
解决方案: 当被点击之后为class值添加disabled,判断class的值是否可点击
商城业务-认证服务-整合短信验证码
1.进入阿里云官网,点击云市场中的三网短信接口
2.购买0元体验
点击官方106三网短信
3. 在管理控制台中查看已购买的短信验证API
有各个参数的详细解释以及java的示例代码
点击短信接口超链接,有详细的API接口文档
Host的获取即调用地址去除/sms/smsSend和(s)
AppCode的获取
AppCode的拼接方式
smsSignId公司的签名,这个需要自己跟客服去申请,就下图圈起来的东西
可以点击去调试按钮进行一个简单的使用体验
4.简单测试
输入你的appCode,HttpUtils按照注释自行下载
将复制的HttpUtils放在第三方服务的util包下,文件上传、短信验证、物流查询都属于第三方服务
5. 编写可配置的发送短信的接口
商城业务-认证服务-验证码防刷校验
1.编写短信验证controller,方便其它服务调用
2. 认证服务远程调用发送短信验证码功能
①依赖已导入,开启远程服务调用功能
② 远程调用接口编写
3.认证服务中编写获取短信验证码的controller
4. 注册页面编写请求发送验证码功能
①为手机号码input框设置id,方便获取
②发送请求,请求后台发送短信验证码
问题1:有人拿到请求路径恶意请求
解决方案:后续会说明
5.防止一个手机号码60s内多次获取短信验证码
解决思路:将短信验证码存储在redis中,key为phoneNum,value为验证码和存储时系统的当前时间。从redis中查询为null则调用发送短信验证码,若查询不为空则判断是否超过60s,是则再次调用发送短信验证码,否则返回提示信息。
①导入redis的依赖,并配置好redis
②common的constant中编写验证码前置
③编写错误代码
④接口编写
@RestController
public class LoginController {
@Autowired
private SmsSCFeignService smsSCFeignService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/sms/sendcode")
public R sendCode(@RequestParam("phone") String phone){
// TODO 接口防刷
// 处理一个手机号码60s内多次获取短信验证码问题
String s = stringRedisTemplate.opsForValue().get(SmsConstant.SMS_CODE_CACHE_PREFIX + phone);
if (StringUtils.isNotEmpty(s)){
// 获取存储时的系统时间
Long saveTime = Long.parseLong(s.split("_")[1]);
// 单位为毫秒,因此,60秒要转化为毫秒即60000
if (saveTime - System.currentTimeMillis()< 60000){
return R.error(BizCodeEnum.SMS_CODE_EXPTION.getCode(), BizCodeEnum.SMS_CODE_EXPTION.getMsg());
}
}
String code = UUID.randomUUID().toString().substring(0,5);
stringRedisTemplate.opsForValue().set(SmsConstant.SMS_CODE_CACHE_PREFIX+phone,code+"_"+System.currentTimeMillis(),10, TimeUnit.MINUTES);
smsSCFeignService.sendCode(phone,code);
return R.ok();
}
}
⑤ 注册页面的请求发送验证码的回调函数编写
商城业务-认证服务-一步一坑的注册页环境
1.编写注册接口
2.编写Vo封装注册数据
3. 使用JSR303进行数据校验
导入依赖
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
说明:不导入此依赖,@Valid注解不生效
使用@Valid注解开启数据校验功能,将校验后的结果封装到BindingResult中
4.编写注册页面
为每个input框设置name属性,值需要与Vo的属性名一一对应
点击注册按钮没有发送请求,说明:为注册按钮绑定了单击事件,禁止了默认行为。将绑定的单击事件注释掉
5.为Model绑定校验错误信息
方法一:
方法二:
编写前端页面获取错误信息
①导入thymeleaf的名称空间
② 封装错误信息
出现问题: 转发失败,打印字符串
出现问题的原因:controller层使用了@RestController注解会自动返回json数据
解决方案:使用@controller注解
出现问题: Request method 'POST' not supported
出现问题的原因:表单的提交使用的是post请求,会原封不动的转发给reg.html,但是/reg.html(路径映射默认都是get方式访问)
解决方案:如下图所示
出现问题:刷新页面,会重复提交表单
出现问题的原因:转发,原封不对转发过去
解决方案:使用重定向
出现问题:转发,数据都封装在Model中,而重定向获取不到
解决方案:使用 RedirectAttributes
RedirectAttributes的方法讲解:Spring MVC ---- RedirectAttributes 使用,请求转发携带参数总结_zhangjian15的博客-CSDN博客
出现问题:重定向到服务端口地址
解决方案: 写完整的域名路径
说明: RedirectAttributes的addFlashAttribute()方法是将errors保存在session中,刷新一次就没了
出现问题:分布式下重定向使用session存储数据会出现一些问题
解决方案:后续会说明
商城业务-认证服务-异常机制
1.校验验证码
2. 会员服务中编写Vo接受数据
3. 编写会员服务的用户注册接口
① 查询会员你得默认等级
② 检查用户名和手机号是否唯一
这里采用异常机制处理,如果查出用户名或密码不唯一则向上抛出异常
异常类的编写
检查用户名和手机号是否存在的方法编写
如果抛出异常,则进行捕获
密码的设置,前端传来的密码是明文,存储到数据库中需要进行加密,后面会说到
商城业务-认证服务-MD5&盐值&BCrypt
首先,加密分为可逆加密和不可逆加密。密码的加密为不可逆加密
什么是MD5?
Apache.common下DigestUtils工具类的md5Hex()方法,将MD5加密后的数据转化为16进制
MD5并安全,很多在线网站都可以破解MD5,通过使用彩虹表,暴力破解。
因此,需要通过使用MD5+盐值进行加密
盐值:随机生成的数
什么是加盐?
方法1是加默认盐值:$1$xxxxxxxx
方法2是加自定义盐值
缺点:还需额外在数据库中存储盐值
因此,可以使用Spring家的BCryptPasswordEncoder,它的encode()方法使用的就是MD5+盐值进行加密,盐值是随机产生的
通过matches()方法进行密码是否一致
用户注册业务中的密码加密
商城业务-认证服务-注册完成
1.在common的exception包下,编写异常枚举
2. 进行异常的捕获
3. 远程服务接口编写
4. 远程服务调用
5.注册页错误消息提示
商城业务-认证服务-账号密码登录完成
1.编写Vo
2.数据绑定
将ul包在表单里面
3. 编写登录接口
说明:不能加@RequestBody注解,这里是页面直接提交数据,数据类型是map并非json
4.member服务的Vo编写
5. member服务用户校验接口编写
编写异常枚举
6.远程服务接口编写
7.错误信息提示
商城业务-认证服务-OAuth2.0简介
社交登录使用的是Auth2.0,Auth2.0的流程如下图所示:
商城业务-认证服务-weibo登录测试
1.搜索微博开放平台
地址:新浪微博开放平台-首页
找到微链接,点击网站接入
点击立即接入
填写应用名称
点击我的应用
App Key 和 App Secret 是获取 Access token必填的参数
填写授权回调地址和授权失败回调地址
微博Auth2.0文档地址: 授权机制说明 - 微博API
2.将QQ样式修改为微博样式
3.请求用户授权
https://api.weibo.com/oauth2/authorize?client_id=123050457758183&redirect_uri=http://www.example.com/response&response_type=code
授权成功会跳转成功页面并携带一个code码,可以根据这个code码获取Access token
通过code获取Access token
说明:
①code只能使用一次,Access token只能一段时间有效
②uuid唯一,和关联账户绑定
拿到Access token 可以获取微博开放用户的信息 ,例如用户的性别、年龄等
通过微博提供的用户信息查询接口查询用户信息
商城业务-认证服务-社交登录回调
授权认证的时序图
1.修改授权成功回调页
修改超链接中的回调地址
2. 将third-part服务下的utils包下的HttpUtils工具类复制到common中
3.编写controller专门处理社交登录请求
出现问题:微博登录接入出现错误码21322(重定向地址不匹配)
解决方案:微博登录接入出现错误码21322(重定向地址不匹配),其他解决方法_wwang_dev的博客-CSDN博客_21322 微博
清除缓存,重新测试即可
商城业务-认证服务-社交登录完成
1. 编写获取Access token的实体类
将获取到的json数据,利用json在线工具平台转化为java对象
getEntity()获取到json数据,将json数据转为字符串,将字符串转为java对象
2. 为member表新增三个字段
增加新的属性
3.会员服务创建接口处理第一次社交登录
将认证服务的SocialUserVo拷贝到member服务的vo包下
商城业务-认证服务-社交登录测试成功
1.远程服务接口编写
2. 拷贝memberEntity的属性
3.远程服务调用
商城业务-认证服务-分布式session不共享不同步问题
登录成功后,NickName的显示
在之前的单体应用中,会将登录成功后的属性保存到session中
Thymeleaf取出session
出现问题:NickName未显示
出现问题的原因:Session不能跨域使用
auth.gulimall域下的session作用域只限于auth.gulimall域,gulimall域是获取不到的,不共享的
session原理:
分布式下session会出现问题如下:
①同一个服务,复制多份,session不同步问题
②不同服务,session不能共享问题
商城业务-认证服务-分布式session解决方案原理
方案一:sessio复制,不采用
方案二:客户端存储,不采用
方案三: 利用hash一致性,进行负载均衡,可以采用但是这里不采用
方案四: 统一存储,这里采用这套方案
SpringSession已经为我们做好了
放大域名 ,SpringSession也为我们做好了
商城业务-认证服务-SpringSession整合
传送门: Spring Session - Spring Boot
SpringSession整合redis的使用步骤:
①整合依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
② boot配置
spring.session.store-type=redis # Session store type
server.servlet.session.timeout= # Session timeout. If a duration suffix is not specified,seconds is used
spring.session.redis.flush-mode=on_save # Sessions flush mode
spring.session.redis.namespace=spring:session # Namespace for keys used to store sessions
③redis连接配置
spring.redis.host=localhost # Redis server host
spring.redis.password= # Login password of the redis server
spring.redis.port=6379 # Redis server port
④Java配置
使用@EnableRedisHttpSession注解开启Spring Session with Redis功能
@EnableRedisHttpSession
public class Config {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
}
1.Auth服务按照上列步骤完成Spring Session with Redis的整合
①整合依赖
② boot配置
③redis连接配置之前已经配置过了,略去
④Java配置
测试:看是否将session存入redis中
出现问题:DefaultSerializer requires a Serializable payload but received an object of type [com.atguigu.gulimall.auth.vo.MemberRespVo]
出现的原因: MemberRespVo未实现序列化解接口
解决方案:
保存成功,redis中有数据
2.Product服务按照上列步骤完成Spring Session with Redis的整合
①导入依赖
②boot配置
③之前已配置过redis连接配置
④Java配置
3. 将MemberRespVo复制到commom中,因为,product服务还需要将Session中存储的loginUser反序列化为MemberRespVo对象
将redis中的session删除,因为,session中存储的loginUser类型为 auth服务vo包下的,需要存储common服务vo包下的类型
4. 手动修改session的作用域
商城业务-认证服务-自定义SpringSession完成子域session共享
解决子域session共享问题:
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("JSESSIONID");
serializer.setCookiePath("/");
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
return serializer;
}
解决使用json序列化方式来序列化对象数据到redis中
传送门:spring-session/SessionConfig.java at 2.4.6 · spring-projects/spring-session · GitHub
@Configuration
public class SessionConfig implements BeanClassLoaderAware {
private ClassLoader loader;
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer(objectMapper());
}
}
@Configuration
public class GulimallSessionConfig {
/**
* 子域问题共享解决
*/
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setDomainName("gulimall.com");
cookieSerializer.setCookieName("GULIMALLSESSION");
return cookieSerializer;
}
/**
* 使用json序列化方式来序列化对象数据到redis中
*/
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
}
给product服务和auth服务各配置一份
前端页面修改,需要进行非空判断
将redis和客户端的session进行清空
商城业务-认证服务-SpringSession原理
@EnableRedisHttpSession注解导入了RedisHttpSessionConfiguration.class这个配置类
在 RedisHttpSessionConfiguration.class这个配置类,为容器中注入了一个组件
sessionRepository -> sessionRedisOperations : redis操作session,实现session的增删改查
调用SpringHttpSessionConfiguration中的springSessionRepositoryFilter()方法,获取一个
SessionRepositoryFilter对象,调用doFilterInternal()对原生的request和response对象进行封装即装饰者模式,request对象调用getSession()方法就会调用wrapperRequest对象的getSession()方法
商城业务-认证服务-页面效果完成
通过账号密码登录的用户信息也保存到session中
①编写一个可修改的属性key
② 用户信息也保存到session中
③设置默认的昵称
④ 登录后,首页页面细化
已经登录的话,在进入登录页要实现跳转首页的效果
①自己编写业务逻辑,将自动页面映射注释
②编写接口
商品详情页,用户昵称显示
搜索页,用户昵称显示
①导入依赖
<!--导入Spring Session with redis 依赖-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!--导入SpringBoot整合Redis的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
②配置
③ 开启共享session功能
④ 复制配置类
⑤ 前端代码
商城业务-认证服务-单点登录简介
什么是单点登录?只要注册了登录某一个服务就可以自动登录其它所有服务,例如:注册登录了谷粒商城,则可以自动登录在线教育、众筹系统等
商城业务-认证服务-补-框架效果演示
1.码云搜索xxl,下载单点登录的开源框架
2.配置域名
ssoserver.com 作为认证中心的域名,client1.com、client2.com分别作为客户端1和客户端2的域名
3. 修改认证中心和客户端的配置文件
4.进行打包
mvn clean package -Dmaven.skip.test=true
5.启动认证中心和客户端
认证中心访问路径:http://ssoserver.com:8080/xxl-sso-server
客户端1访问路径:http://client1.com:8081/xxl-sso-web-sample-springboot
客户端2访问路径:http://client2.com:8082/xxl-sso-web-sample-springboot
商城业务-认证服务-单点登录流程-1
单点登录的核心逻辑:
单点登录的第一步流程:
1.使用初始化向导创建认证中心和客户端服务
修改客户端服务的端口号
2. 编写接口
配置认证中心的认证地址
当认证中心认证完成之后,让认证中心知道你要跳转回的地址?解决方案就是在请求参数中携带跳转回的地址
编写list页面
3. 编写处理认证的请求
编写login页面
商城业务-认证服务-单点登录流程-2
1.编写一个隐藏的input框,用于存储调回的url
2.导入redis的依赖,配置redis
3. 登录成功保存用户信息并传递token
4. 拿到令牌需要去认证中心查询用户的信息,这里只是简单保存了以下并没有模拟
商城业务-认证服务-单点登录流程-3
1.认证中心编写接口查询用户信息
2.去认证中心查询用户信息
3.复制client服务,并修改端口、服务名等信息
4.实现一次登录,处处登录的核心就是认证通过之后给浏览器留下一个痕迹,凡是访ssoserver.com这个域名的都会带上这个痕迹,通过使用cookie实现