认证服务-----技术点及亮点2

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 认证服务-----技术点及亮点2

(1)导入依赖

<!-- 整合springsession 来解决分布式session不同步不共享的问题-->
<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- 整合redis-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

(2)在application.properties配置文件里配置springsession

#配置springsession
spring.session.store-type=redis
server.servlet.session.timeout=30m
#配置redis的ip地址
spring.redis.host=192.168.241.128

(3)在config配置中加入springSession配置类

package com.saodai.saodaimall.order.config;
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;
/**
* springSession配置类(所有要使用session的服务的session配置要一致)
*/
@Configuration
public class GulimallSessionConfig {
    /**
    * 配置session(主要是为了放大session作用域)
    * @return
    */
    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        //放大作用域
        cookieSerializer.setDomainName("saodaimall.com");
        cookieSerializer.setCookieName("SAODAISESSION");
        return cookieSerializer;
    }
    /**
    * 配置Session放到redis存储的格式为json(其实就是json序列化)
    * @return
    */
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
}

(4)在启动类上添加@EnableRedisHttpSession注解

package com.saodai.saodaimall.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
/**
 * 订单服务启动类
 */
@EnableFeignClients
@EnableRedisHttpSession
@EnableDiscoveryClient
@SpringBootApplication
public class SaodaimallOrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(SaodaimallOrderApplication.class, args);
    }
}


SpringSession的原理

Spring-Session的实现就是设计一个过滤器SessionRepositoryFilter,每当有请求进入时,过滤器会首先将ServletRequest 和ServletResponse 这两个对象转换成Spring内部的包装类SessionRepositoryRequestWrapper和SessionRepositoryResponseWrapper对象,它使用了一个SessionRepositoryRequestWrapper类接管了Http Session并重写了getSession方法来实现了session的创建和管理工作。将原本需要由web服务器创建会话的过程转交给Spring-Session进行创建,本来创建的会话保存在Web服务器内存中,通过Spring-Session创建的会话信息可以保存第三方的服务中,如:redis,mysql等。Web服务器之间通过连接第三方服务来共享数据,实现Session共享!

@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
    @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
         //使用HttpServletRequest 、HttpServletResponse和servletContext创建一个SessionRepositoryRequestWrapper
    SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
        request, response, this.servletContext);
    SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
        wrappedRequest, response);
    try {
      filterChain.doFilter(wrappedRequest, wrappedResponse);
    }
    finally {
            //保存session信息
      wrappedRequest.commitSession();
    }
  }
}
@Override
    public HttpSessionWrapper getSession(boolean create) {
            //获取当前Request作用域中代表Session的属性,缓存作用避免每次都从sessionRepository获取
      HttpSessionWrapper currentSession = getCurrentSession();
      if (currentSession != null) {
        return currentSession;
      }
            //查找客户端中一个叫SESSION的cookie,拿到sessionId,通过sessionRepository对象根据sessionId去Redis中查找
      S requestedSession = getRequestedSession();
            //如果从redis中查询到了值
      if (requestedSession != null) {
                //客户端存在sessionId 并且未过期
        if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
          requestedSession.setLastAccessedTime(Instant.now());
          this.requestedSessionIdValid = true;
          currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
          currentSession.setNew(false);
                    //将Session设置到request属性中
          setCurrentSession(currentSession);
          return currentSession;
        }
      }
      else {
        // This is an invalid session id. No need to ask again if
        // request.getSession is invoked for the duration of this request
        if (SESSION_LOGGER.isDebugEnabled()) {
          SESSION_LOGGER.debug(
              "No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
        }
        setAttribute(INVALID_SESSION_ID_ATTR, "true");
      }
            //不创建Session就直接返回null
      if (!create) {
        return null;
      }
      if (SESSION_LOGGER.isDebugEnabled()) {
        SESSION_LOGGER.debug(
            "A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
                + SESSION_LOGGER_NAME,
            new RuntimeException(
                "For debugging purposes only (not an error)"));
      }
            //执行到这了说明需要创建新的Session
            // 通过sessionRepository创建RedisSession这个对象
      S session = SessionRepositoryFilter.this.sessionRepository.createSession();
      session.setLastAccessedTime(Instant.now());
      currentSession = new HttpSessionWrapper(session, getServletContext());
      setCurrentSession(currentSession);
      return currentSession;
    }
  // 通过sessionRepository创建RedisSession这个对象
  @Override
  public RedisSession createSession() {
    Duration maxInactiveInterval = Duration
        .ofSeconds((this.defaultMaxInactiveInterval != null)
            ? this.defaultMaxInactiveInterval
            : MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
    RedisSession session = new RedisSession(maxInactiveInterval);
    session.flushImmediateIfNecessary();
    return session;
  }

好文参考:Spring-Session实现session共享原理及解析_五霸哥的博客-CSDN博客_session共享如何实现

小技术

使用视图映射器来实现页面跳转

传统写法是在控制器里实现

自定义一个配置类来实现WebMvcConfigurer接口,然后重写addViewControllers方法来增加视图映射器

使用配置文件来动态配置属性值

这样就可以通过在配置文件里修改对应的值来改变属性值,核心注解是@ConfigurationProperties(prefix=""),@Data注解也要加

使用Feign远程调用服务

添加openFeign依赖并且在启动了通过@EnableFeignClients注解开启远程调用端即可用feign远程调用服务

定义一个远程调用的接口,通过@FeignClient注解来指定调用哪个服务,把第三方服务控制器的方法签名拿过来即可,注意路径一定要写对,特别是如果有父路径不要忘了写

在需要远程调用的服务器里注入刚写的远程接口,然后调用就可,例如这里是认证中心调用第三方服务的发生验证码的接口


使用异常机制

 /**
     *    会员注册
     */
    @Override
    public void register(MemberUserRegisterVo vo) {
        MemberEntity memberEntity = new MemberEntity();
        //设置默认等级
        MemberLevelEntity levelEntity = memberLevelDao.getDefaultLevel();
        memberEntity.setLevelId(levelEntity.getId());
        //设置其它的默认信息
        //检查用户名和手机号是否唯一。感知异常,异常机制(异常机制就是问题就抛出具体异常,没问题就继续执行下面的语句)
        checkPhoneUnique(vo.getPhone());
        checkUserNameUnique(vo.getUserName());
        memberEntity.setNickname(vo.getUserName());
        memberEntity.setUsername(vo.getUserName());
        //密码进行MD5盐值加密(盐值加密同一个数据的每次加密结果是不一样的,通过match方法来密码校验)
        // (注意这里不能用md5直接加密放数据库,因为彩虹表可以破解md5,所谓彩虹表就是通过大量的md5数据反向退出md5
        // 注意MD5是不可逆,但是可暴力通过彩虹表破解)
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode = bCryptPasswordEncoder.encode(vo.getPassword());
        memberEntity.setPassword(encode);
        memberEntity.setMobile(vo.getPhone());
        memberEntity.setGender(0);
        memberEntity.setCreateTime(new Date());
        //保存数据
        this.baseMapper.insert(memberEntity);
    }
    /**
     * 检查手机号是否重复的异常机制方法
     * @param phone
     * @throws PhoneException
     */
    @Override
    public void checkPhoneUnique(String phone) throws PhoneException {
        Long phoneCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
        //usernameCount > 0表示手机号已经存在
        if (phoneCount > 0) {
            throw new PhoneException();
        }
    }
    /**
     * 检查用户名是否重复的异常机制方法
     * @param userName
     * @throws UsernameException
     */
    @Override
    public void checkUserNameUnique(String userName) throws UsernameException {
        Long usernameCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", userName));
        //usernameCount > 0表示用户名已经存在
        if (usernameCount > 0) {
            throw new UsernameException();
        }
    }

上面定义检查用户名和电话号码的异常机制方法的具体实现

  /**
     * 会员注册功能
     * @param vo
     * @return
     */
    @PostMapping(value = "/register")
    public R register(@RequestBody MemberUserRegisterVo vo) {
        try {
            memberService.register(vo);
        } catch (PhoneException e) {
            //BizCodeEnum.PHONE_EXIST_EXCEPTION=存在相同的手机号  15002
            return R.error(BizCodeEnum.PHONE_EXIST_EXCEPTION.getCode(),BizCodeEnum.PHONE_EXIST_EXCEPTION.getMessage());
        } catch (UsernameException e) {
            //BizCodeEnum.USER_EXIST_EXCEPTION=商品库存不足  21000
            return R.error(BizCodeEnum.USER_EXIST_EXCEPTION.getCode(),BizCodeEnum.USER_EXIST_EXCEPTION.getMessage());
        }
        return R.ok();
    }

使用异常机制的原因就是希望控制器能够发现并处理异常

package com.saodai.saodaimall.member.exception;
public class UsernameException extends RuntimeException {
    public UsernameException() {
        super("存在相同的用户名");
    }
}
package com.saodai.saodaimall.member.exception;
public class PhoneException extends RuntimeException {
    public PhoneException() {
        super("存在相同的手机号");
    }
}

把上面两个单独的异常抽取出来封装成异常类

使用MD5盐值加密

加密

先创建一个加密器BCryptPasswordEncoder,然后调用他的encode方法把需要加密的密码放进去就会自动生成一串加密后的值

注意同一个密码每次生成的值是不一样的

解密

从数据库里面拿到加密的数据后调用matches方法就可以匹配两个密码是否一致,如果一致那就返回true,不一致返回false,前面是password是旧密码(没加密的密码),后面的passwordDb是数据库加密的密码

目录
相关文章
|
JSON 缓存 NoSQL
认证服务-----技术点及亮点1
认证服务-----技术点及亮点
78 0
|
3月前
|
数据采集 安全 容灾
《阿里云产品手册2022-2023 版》——号码认证服务
《阿里云产品手册2022-2023 版》——号码认证服务
|
移动开发 API 开发工具
秒懂云通信:如何使用阿里云号码认证服务(小白指南)
手把手教你如何使用阿里云号码认证服务,超详细控制台步骤解析,快速上手!
2988 0
秒懂云通信:如何使用阿里云号码认证服务(小白指南)
|
安全 数据安全/隐私保护 开发者
阿里云通信发布全新号码认证服务, 重新定义手机号码认证的方式
12月12日,阿里云通信宣布号码认证服务正式商用,将重新定义手机号码认证的方式。因移动应用实名制的政策要求,手机号码认证在移动APP的注册、登录等场景用的越来越多。而对于开发者来说,能完成手机号码认证的选择并不多,一般是借助短信、语音的基础通信通道,自己实现短信验证码或语音验证码来实现。
25052 0
|
4月前
|
运维 小程序 前端开发
好的商业模式-----小程序定制资料,加一张好看的海报,在推广中就可以找到用户中了,云服务部署收5000,部署是一种服务,定制化,游戏开发创者,仲裁劳务会剪视频好,提供服务,想增加一些新功能收费,会说
好的商业模式-----小程序定制资料,加一张好看的海报,在推广中就可以找到用户中了,云服务部署收5000,部署是一种服务,定制化,游戏开发创者,仲裁劳务会剪视频好,提供服务,想增加一些新功能收费,会说
|
6月前
|
算法 数据库
隐私计算实训营第6讲-------隐语PIR介绍及开发实践丨隐私计算实训营 第1期
隐匿查询(PIR)允许用户在不暴露查询内容的情况下检索服务器数据库。PIR分为单服务器和多服务器方案,以及Index PIR和Keyword PIR两类。隐语目前实现了单服务器的SealPIR(用于Index PIR)和Labeled PSI(用于Keyword PIR)。SealPIR优化点包括:数据打包、查询向量压缩、支持多维和多个查询。未来,隐语PIR的计划包括性能提升、多服务器方案和新算法的探索。
335 3
|
6月前
|
Linux Docker 容器
隐私计算实训营第4讲-------快速上手隐语SecretFlow的安装和部署
考虑到很多小伙伴可能是初学者之前并没有安装docker 以及docker-compose的经验,本文记录如何在Linux系统上快速的部署docker以及更换国内镜像源。在部署完成以后展示了隐语从源码编译部署以及secretnote的安装,简单快速,非常实用。
226 1
|
6月前
|
安全 中间件 区块链
BRC-20铭文系统开发详细教程/步骤方案/需求设计/规则玩法/源码程序
中间件技术:使用中间件技术可以简化BRC20铭文智能合约跨链系统的开发过程。中间件提供了一些常用功能的封装,例如处理网络通信、