@Configuration
public class WebMVCConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加自己写的拦截器,配置拦截规则
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/test")
.addPathPatterns("/comments/create/change")
.addPathPatterns("/articles/publish")
.addPathPatterns("/register");
registry.addMapping("/**").allowedOrigins("*");
}
}
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private LoginService loginService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
/**
* 1.需要判断 请求的接口路径 是否为 HandlerMethod (就是Controller方法)
* 【目的是为了放行 访问静态资源的方法。尚硅谷是在MVCConfig里面设置拦截规则的,如下】
* @Override
* public void addInterceptors(InterceptorRegistry registry) {
* registry.addInterceptor(new LoginInterceptor())
* .addPathPatterns("/**") //所有请求都被拦截包括静态资源
* .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**",
* "/js/**","/aa/**"); //放行的请求
* 2.判断token是否为空,如果为空,未登录【只是演示一下。因为前端不是每个请求的Header都带 Authorization 通过不了拦截器】
* 3.如果token不为空,验证登录 LoginService checkToken
* 4.如果认证成功 放行即可
*/
if(!(handler instanceof HandlerMethod)){
//放行静态资源
return true;
}
//2.判断token是否为空,如果为空,未登录
String token = request.getHeader("Authorization");
// 添加日志
log.info("=================request start===========================");
String requestURI = request.getRequestURI();
log.info("request uri:{}",requestURI);
log.info("request method:{}",request.getMethod());
log.info("token:{}", token);
log.info("=================request end===========================");
if(StringUtils.isBlank(token)){
Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), ErrorCode.NO_LOGIN.getMsg());
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSON.toJSONString(result));
return false;
}
//3.如果token不为空,验证登录 LoginService checkToken
SysUser sysUser = loginService.checkToken(token);
if(sysUser == null){
Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), ErrorCode.NO_LOGIN.getMsg());
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSON.toJSONString(result));
return false;
}
// 登录验证成功 放行
// 希望可以直接在Controller获取用户信息
// 【上面调用的loginService.checkToken其实已经从redis中获取用户信息了,捂脸,
// 只是不能直接return,写到session其实应该也可以,为什么用UserThreadLocal】
// 欧~,原来session其实只是在浏览器的cookie存一个id,实际上的数据还在在服务器端,很多人都是把session的值放在redis里面,有很多好处
// 这里的话,自然不合适存session,不然本质上其实还是存redis,刚刚从redis拿出来又存进去有点,捂脸
//ThreadLocal是单单这个线程可以用,就是说如果其他用户也在验证登录,那每个用户获得的用户信息都是自己的,感觉是做了一个区分
UserThreadLocal.put(sysUser);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 如果ThreadLocal中用完的信息不进行删除,会有内存泄漏的风险
UserThreadLocal.remove();
}
}
public class UserThreadLocal {
private UserThreadLocal(){}
private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>();
public static void put(SysUser sysUser){
LOCAL.set(sysUser);
}
public static SysUser get(){
return LOCAL.get();
}
public static void remove(){
LOCAL.remove();
}
}
session问题:
1.跨域名session不共享
2.分布式下,同一服务多台机器,session不同步
3.分布式下,不同服务,session不共享
解决方式:
解决问题1,3:
解决问题2:
1.hash一致性,第一次请求去了哪台机器,后面的请求就去哪台机器 或者
2.统一存储:使用redis
【被放到session中的类需要实现序列化接口,和使用原来的session一样,只是在存的时候把原来的session替换掉】
【自动延期,浏览器不关,redis刷新过期时间】
依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
配置
spring.session.store-type=redis
启动类上加注解
@EnableRedisHttpSession
被放到session中的类需要实现序列化接口
Serializable
常见使用session方式:
直接在参数中写 HttpSession session
@GetMapping(value = "/login.html")
public String loginPage(HttpSession session) {
//从session先取出来用户的信息,判断用户是否已经登录过了
Object attribute = session.getAttribute(LOGIN_USER);
//如果用户没登录那就跳转到登录页面
if (attribute == null) {
return "login";
} else {
return "redirect:http://gulimall.com";
}
}
session配置类:
解决跨域名session不共享:
session使用JSON序列化,不使用jdk序列化:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
@Configuration
public class GulimallSessionConfig {
// 设置了session的 Domain 扩大范围【是把sessionId的值存在浏览器的cookie,所有实际上是cookie的范围】
// 解决跨域名session不共享的问题
// 还可以设置很多东西
@Bean
public CookieSerializer cookieSerializer(){
DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer();
defaultCookieSerializer.setDomainName("gulimall.com");
return defaultCookieSerializer;
}
// 设置在redis中存放session的序列化器 为JSON
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(){
return new GenericJackson2JsonRedisSerializer();
}
}
原理:
/**
* 核心原理
* 1)、@EnableRedisHttpSession-导入RedisHttpSessionConfiguration配置
* 1、给容器中添加了一个组件 SessionRepository =>>>【RedisOperationsSessionRepository】=> redis操作session。
* 2、SessionRepositoryFilter => Filter:session存储过滤器;每个请求过来都必须经过filter
* 1、创建的时侯,就自动从容器中获取到了SessionRepository;
* 2、原始的request,response都被包装。SessionReposi元toryRequestWrapper,.SessionRepositoryResponseWrapper
* 3、以后获取session。request.getSession();
* //SessionRepositoryRequestWrapper
* 4.wrappedRequest.getsession() ===> ,SessionRepository中获取到的。
*
*
* 主要使用装饰者模式
*/
难点:在登录认证服务器之后,其他系统也是登录状态
1、给登录服务器留下登录痕迹(生成token放在redis中)
2、登录服务器要将token信息重定向的时候,带到url地址上
3、其他系统要处理url地址上的关键token,去redis中查询是否有token对应的数据。只要有,将token对应的用户数据保存到自己的session中
4、自己系统将用户保存在自己的会话中。
可以事先购买阿里云的一些短信服务,购买后也有说明
https://www.aliyun.com/search?scene=all&k=%E7%9F%AD%E4%BF%A1
https://market.aliyun.com/products/57000002/cmapi00049440.html?spm=5176.2020520132.101.2.5b447218EUE8v5#sku=yuncode4344000001
流程:
为什么密码不能明文放数据库:
为什么密码的加密算法也不能选可逆的:
为什么选择MD5加密:
• Message Digest algorithm 信息摘要算法
• 压缩性:任意长度的数据,算出的MD5值长度都是固定的。
• 容易计算:从原数据计算出MD5值很容易。
• 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
• 强抗碰撞:想找到两个不同的数据,使它们具有相同的MD5值,是非常困难的。
• 不可逆
为什么用MD5要加盐:
因为直接存储,有可能因为用户设置密码的简单性,导致被破解,设置盐可以加大被破解的难度
• 通过生成随机数与MD5生成字符串进行组合
• 数据库同时存储MD5值与salt值。验证正确性时使用salt进行MD5加密然后匹配即可
低级MD5加密 解密
String password = DigestUtils.md5Hex("123456" + salt);
高级MD5加密!!!使用springboot自带的工具,同一个密码,生成的MD5不一样,很难被匹配破解,但是又可以进行匹配,判断密码是否正确
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// 加密
String encode = passwordEncoder.encode("12345");
// 判断是否匹配
boolean matches = passwordEncoder.matches("12345", encode);
要提前获取qq和微信的第三方接口
流程:
1.用户点击网页,跳转到第三方网页
2.登录第三方账号(有时候登录就相当于授权了)
3.第三方账号认证授权
4.网页自动跳转到 原来的网站,并携带code【code只能用一次】
5.后台获取code之后,可以发送请求到第三方,第三方返回token【token不限次数,但有过期时间】
6.后台可以根据令牌获取 用户在第三方的公开信息,比如昵称和头像