spring session 登录注册模块 拦截器 验证码 MD5 社交登录

拦截器,未登录不能访问(但是静态资源要放行)

MVC配置类
@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();
    }
}

ThreadLocal

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共享原理【分布式】

session问题:

1.跨域名session不共享

2.分布式下,同一服务多台机器,session不同步

3.分布式下,不同服务,session不共享

解决方式:

解决问题1,3:

  • 第一次设置session的时候,设置最大域名,这样访问的时候也会带上其他域名的cookie

解决问题2:

1.hash一致性,第一次请求去了哪台机器,后面的请求就去哪台机器 或者

2.统一存储:使用redis

统一存储:使用spring session

【被放到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

流程:

  • 用户点击注册,后端校验参数是否合法
  • 若合法,用户点击发送验证码【前端要倒计时】
  • 后端先查看redis中是否有当前手机号少于60秒的验证码,有则不发送
  • 若没有,则调用第三方接口,发送验证码【验证码是本地生成的,而且也要在尾部加上当前系统时间设置到redis,格式如:验证码_当前系统时间】
  • 用户在网页输入验证码,发送给后端,后端取出redis中验证码部分 比对【若redis中没有则提示用户验证码错误】

MD5加密用户密码:

为什么密码不能明文放数据库:

  • 防止数据库被别人攻破泄漏密码。

为什么密码的加密算法也不能选可逆的:

  • 因为别人攻破数据库之后,知道加密的算法就可以获取密码

为什么选择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);

OAuth2.0 社交登录(qq,微信):

要提前获取qq和微信的第三方接口

流程:

1.用户点击网页,跳转到第三方网页

2.登录第三方账号(有时候登录就相当于授权了)

3.第三方账号认证授权

4.网页自动跳转到 原来的网站,并携带code【code只能用一次】

5.后台获取code之后,可以发送请求到第三方,第三方返回token【token不限次数,但有过期时间】

6.后台可以根据令牌获取 用户在第三方的公开信息,比如昵称和头像

你可能感兴趣的:(java,java,spring,servlet)