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

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 认证服务-----技术点及亮点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是数据库加密的密码

目录
相关文章
|
11月前
|
JSON 缓存 NoSQL
认证服务-----技术点及亮点1
认证服务-----技术点及亮点
57 0
|
安全 Java 关系型数据库
案例之认证服务搭建|学习笔记
快速学习案例之认证服务搭建
104 0
案例之认证服务搭建|学习笔记
|
安全 Java 数据安全/隐私保护
案例之认证服务security配置|学习笔记
快速学习案例之认证服务security配置
74 0
案例之认证服务security配置|学习笔记
|
JSON 安全 Java
分布式整合之认证服务配置文件编写和测试|学习笔记
快速学习分布式整合之认证服务配置文件编写和测试
195 0
分布式整合之认证服务配置文件编写和测试|学习笔记
|
15小时前
|
算法 安全 大数据
隐私计算实训营第5讲-------隐私求交和隐语PSI介绍以及开发实践
隐私求交(Private Set Intersection, PSI)是利用密码学技术在不暴露数据集以外信息的情况下找到两集合的交集。隐语SPU支持三种PSI算法:ECDH(适合小数据集)、KKRT(基于Cuckoo Hashing和OT Extension,适合大数据集)和BC22PCG(使用伪随机相关生成器)。ECDH基于椭圆曲线 Diffie-Hellman,KKRT利用OT Extension实现高效处理,而BC22PCG通过压缩满足特定相关性的随机数减少通信量。此外,还有基于Oblivious Pseudo-Random Function (OPRF)的PSI协议。
155 0
|
15小时前
|
算法 数据库
隐私计算实训营第6讲-------隐语PIR介绍及开发实践丨隐私计算实训营 第1期
隐匿查询(PIR)允许用户在不暴露查询内容的情况下检索服务器数据库。PIR分为单服务器和多服务器方案,以及Index PIR和Keyword PIR两类。隐语目前实现了单服务器的SealPIR(用于Index PIR)和Labeled PSI(用于Keyword PIR)。SealPIR优化点包括:数据打包、查询向量压缩、支持多维和多个查询。未来,隐语PIR的计划包括性能提升、多服务器方案和新算法的探索。
84 3
|
15小时前
|
Linux Docker 容器
隐私计算实训营第4讲-------快速上手隐语SecretFlow的安装和部署
考虑到很多小伙伴可能是初学者之前并没有安装docker 以及docker-compose的经验,本文记录如何在Linux系统上快速的部署docker以及更换国内镜像源。在部署完成以后展示了隐语从源码编译部署以及secretnote的安装,简单快速,非常实用。
101 1
|
15小时前
|
存储 供应链 区块链
swap去中心化博饼交易所系统开发|细节详情|技术原理
区块链技术的去中心化和不可篡改性确保了供应链数据的安全性
|
6月前
|
存储 JSON 算法
学成在线----认证服务
学成在线----认证服务
|
8月前
|
存储 Cloud Native 关系型数据库
论数据库的传统与未来之争之溯源溯本----AWS系列专栏
论数据库的传统与未来之争之溯源溯本----AWS系列专栏
61 0