• No7.【spring-cloud-alibaba】用户登录密码加密、密码登录模式添加验证码校验


      代码地址与接口看总目录:【学习笔记】记录冷冷-pig项目的学习过程,大概包括Authorization Server、springcloud、Mybatis Plus~~~_清晨敲代码的博客-CSDN博客


    终于结束从零搭建springcloud的部分了,目前也仅仅是学习了最最基本的逻辑,同时包含了开发系统的一些基本的逻辑。接下来就按照 pig 文档将其余基本的内容再熟悉一下,看一遍和写一遍真的不一样呐~~~

    那接下来就一小模块一小模块的学习啦,加油吧少年!

    本文及以后的文章还是基于前面的No6系列文章开发的,可以看之前文章顶部的内容总结,简单了解详情~

    目录

    A1.用户登录密码加密

    B1.步骤

    B2.编码

    B3.测试

    A2.密码登录模式添加验证码校验

    B1.步骤

    B2.编码

    B3.测试


    A1.用户登录密码加密

    B1.步骤

    首先,密码加密用 AES 对称加密,使用 hutool 包就行。然后在 gateway 网关处解拦截登录请求并密用户密码。

    B2.编码

    仅修改网关模块

    1.新增网关配置文件,并添加属性解密密钥,然后在 pig-gateway-dev.yml 里面添加改配置;

    2.新增密码解密网关过滤器,在过滤器中拦截请求,将请求中的入参取出,并将密码进行解密,然后重新包装成ServerHttpRequest。

    3.在pig-gateway-dev.yml 里面请求 auth 模块的路由过滤器中添加密码解密网关过滤器。

    1. //1.添加网关配置文件,并且加到网关配置config里面
    2. @Data
    3. @RefreshScope
    4. @ConfigurationProperties("gateway")
    5. public class GatewayConfigProperties {
    6. /**
    7. * 网关解密登录前端密码 秘钥 {@link com.pig4cloud.pig.gateway.filter.PasswordDecoderFilter}
    8. */
    9. private String encodeKey;
    10. }
    11. @Configuration(proxyBeanMethods = false)
    12. @EnableConfigurationProperties(GatewayConfigProperties.class)
    13. public class GatewayConfiguration {
    14. 。。。
    15. @Bean
    16. public PasswordDecoderFilter passwordDecoderFilter(GatewayConfigProperties configProperties) {
    17. return new PasswordDecoderFilter(configProperties);
    18. }
    19. 。。。
    20. }
    1. //添加 pig-gateway-dev.yml 里面 key 值配置
    2. gateway:
    3. # AES 的密钥长度需要等于16位,否则会报错:InvalidAlgorithmParameterException: IV must be 16 bytes long.
    4. encode-key: 'thanks!pig4cloud'
    1. //2.修改 ValidateCodeGatewayFilter 类
    2. @Slf4j
    3. @RequiredArgsConstructor
    4. public class PasswordDecoderFilter extends AbstractGatewayFilterFactory {
    5. private static final List> messageReaders = HandlerStrategies.withDefaults().messageReaders();
    6. private static final String PASSWORD = "password";
    7. private static final String KEY_ALGORITHM = "AES";
    8. private final GatewayConfigProperties gatewayConfig;
    9. @Override
    10. public GatewayFilter apply(Object config) {
    11. return ((exchange, chain) -> {
    12. ServerHttpRequest request = exchange.getRequest();
    13. //1.如果不是登录请求,则直接向下执行
    14. String path = request.getURI().getPath();
    15. if (!StrUtil.containsAnyIgnoreCase(path, SecurityConstants.OAUTH_TOKEN_URL)) {
    16. return chain.filter(exchange);
    17. }
    18. //2.如果是刷新token模式的请求,则直接向下执行【因为其他模式的也有要校验的,只有刷新token不用校验】
    19. String grantType = request.getQueryParams().getFirst("grant_type");
    20. if (StrUtil.equals(grantType, SecurityConstants.REFRESH_TOKEN)) {
    21. return chain.filter(exchange);
    22. }
    23. //3.从request中解密前端传的密码
    24. Class inClass = String.class;
    25. Class outClass = String.class;
    26. ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders);
    27. Mono modifiedBody = serverRequest.bodyToMono(inClass).flatMap(this.decryptAES());
    28. //4.将解密后的生成新的request【ServerHttpRequest请求对象的请求体只能获取一次,一旦获取了就不能继续往下传递。】
    29. //todo 没明白接下来的具体实现,而且网上也没搜索到相关信息,所以留一个 todo
    30. BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass);
    31. HttpHeaders headers = new HttpHeaders();
    32. headers.putAll(exchange.getRequest().getHeaders());
    33. headers.remove(HttpHeaders.CONTENT_LENGTH);
    34. headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
    35. CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
    36. return bodyInserter
    37. .insert(outputMessage, new BodyInserterContext())
    38. .then(Mono.defer(() -> {
    39. ServerHttpRequest decorator = decorate(exchange, headers, outputMessage);
    40. return chain.filter(exchange.mutate().request(decorator).build());
    41. }));
    42. });
    43. }
    44. /**
    45. * 原文解密
    46. * @return
    47. */
    48. private Function decryptAES() {
    49. return s -> {
    50. // 1.构建前端对应解密AES 因子
    51. AES aes = new AES(Mode.CFB, Padding.NoPadding,
    52. new SecretKeySpec(gatewayConfig.getEncodeKey().getBytes(), KEY_ALGORITHM),
    53. new IvParameterSpec(gatewayConfig.getEncodeKey().getBytes()));
    54. // 2.获取请求密码的并解密
    55. Map inParamsMap = HttpUtil.decodeParamMap((String) s, CharsetUtil.CHARSET_UTF_8);
    56. // 判断入参是否有 password 入参,没有则返回非法参数,有则解密password
    57. if (inParamsMap.containsKey(PASSWORD)) {
    58. String password = aes.decryptStr(inParamsMap.get(PASSWORD));
    59. // 返回修改后报文字符
    60. inParamsMap.put(PASSWORD, password);
    61. }
    62. else {
    63. log.error("非法请求数据:{}", s);
    64. }
    65. //初始化一个Mono对象,将参数放到 mono 里面
    66. return Mono.just(HttpUtil.toParams(inParamsMap, Charset.defaultCharset(), true));
    67. };
    68. }
    69. /**
    70. * 报文转换
    71. * @return
    72. */
    73. private ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) {
    74. return new ServerHttpRequestDecorator(exchange.getRequest()) {
    75. @Override
    76. public HttpHeaders getHeaders() {
    77. long contentLength = headers.getContentLength();
    78. HttpHeaders httpHeaders = new HttpHeaders();
    79. httpHeaders.putAll(super.getHeaders());
    80. if (contentLength > 0) {
    81. httpHeaders.setContentLength(contentLength);
    82. }
    83. else {
    84. httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
    85. }
    86. return httpHeaders;
    87. }
    88. @Override
    89. public Flux getBody() {
    90. return outputMessage.getBody();
    91. }
    92. };
    93. }
    94. }
    1. //3.在pig-gateway-dev.yml 里面请求 auth 模块的路由过滤器中添加密码解密网关过滤器。
    2. # 配置到 nacos 配置中
    3. spring:
    4. cloud:
    5. nacos:
    6. gateway:
    7. discovery:
    8. locator:
    9. enabled: true # 让gateway可以发现nacos中的微服务
    10. routes:
    11. # 认证中心
    12. - id: pig-auth
    13. uri: lb://pig-auth
    14. predicates:
    15. - Path=/auth/**
    16. filters:
    17. - StripPrefix=1 #去掉特定前缀个数
    18. # 前端密码解密
    19. - PasswordDecoderFilter

    B3.测试

    在 pig 提供的 pig4cloud 加密服务 中,按照密钥和密码,生成一个已加密的密码,然后调用接口请求,成功!

      

    A2.密码登录模式添加验证码校验

    B1.步骤

    为了防止恶意登录,我们给密码模式添加验证码。安全意识较强的网站,此时一般会设置允许错误的次数,如3/5次错误即触发账户锁定1小时或者5小时不定,防止密码被暴力破解的隐患。

    验证码的获取与校验都在gateway网关处完成就行~

    首先,先添加获取校验码接口,然后添加GatewayFilter网关过滤器用来拦截请求,如果是登录请求则校验验证码是否正确,如果不是这直接跳过执行下面。

    B2.编码

    仅修改网关模块

    1.导入校验码的依赖包,我们使用 pig4cloud 项目自创的

    2.因为之前测试网关返回图片时已添加了ImageCodeHandler类,所以现在就修改这个类,设置他返回验证码图片,并将验证码数据存储到redis里面。【注意需要将这个接口加到RouterFunction里面~】

    3.因为之前测试网关过滤器GatewayFilter已添加了ValidateCodeGatewayFilter类,所以现在就修改这个类,修改 checkCode() 方法,从 redis 里面拿到对应的 code 值,然后和入参进行判断,不一致则返回错误验证码,一致则执行下面。

    4.给网关里面的 auth 路由模块添加过滤器ValidateCodeGatewayFilter;

    1. //1.导入校验码的依赖包,我们使用 pig4cloud 项目自创的
    2. <dependency>
    3. <groupId>com.pig4cloud.plugingroupId>
    4. <artifactId>captcha-spring-boot-starterartifactId>
    5. <version>${captcha.version}version>
    6. dependency>
    1. //2.因为之前测试网关返回图片时已添加了ImageCodeHandler类,所以现在就修改这个类,设置他返回验证码图片,并将验证码数据存储到redis里面。【注意需要将这个接口加到RouterFunction里面~】
    2. @Slf4j
    3. @RequiredArgsConstructor
    4. public class ImageCodeHandler implements HandlerFunction {
    5. private static final Integer DEFAULT_IMAGE_WIDTH = 100;
    6. private static final Integer DEFAULT_IMAGE_HEIGHT = 40;
    7. private final RedisTemplate redisTemplate;
    8. @SneakyThrows
    9. @Override
    10. public Mono handle(ServerRequest request) {
    11. //1.生成算数校验码
    12. ArithmeticCaptcha captcha = new ArithmeticCaptcha(DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT);
    13. String result = captcha.text();
    14. //2.保存验证码信息
    15. Optional randomStr = request.queryParam("randomStr");
    16. redisTemplate.setKeySerializer(new StringRedisSerializer());
    17. randomStr.ifPresent(s ->
    18. redisTemplate.opsForValue().set(CacheConstants.DEFAULT_CODE_KEY + s, result, SecurityConstants.CODE_TIME, TimeUnit.SECONDS));
    19. // 3.转换流信息写出
    20. FastByteArrayOutputStream os = new FastByteArrayOutputStream();
    21. captcha.out(os);
    22. // 4.统一服务器接口调用的响应
    23. return ServerResponse.status(HttpStatus.OK)
    24. .contentType(MediaType.IMAGE_JPEG)
    25. .body(BodyInserters.fromResource(new ByteArrayResource(os.toByteArray())));
    26. }
    27. }
    1. //3.因为之前测试网关过滤器GatewayFilter已添加了ValidateCodeGatewayFilter类,所以现在就修改这个类,修改 checkCode() 方法,从 redis 里面拿到对应的 code 值,然后和入参进行判断,不一致则返回错误验证码,一致则执行下面。
    2. @Slf4j
    3. @RequiredArgsConstructor
    4. public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory {
    5. private final ObjectMapper objectMapper;
    6. private final RedisTemplate redisTemplate;
    7. @Override
    8. public GatewayFilter apply(Object config) {
    9. return (exchange, chain) -> {
    10. ServerHttpRequest request = exchange.getRequest();
    11. //1.如果不是登录请求,则直接向下执行
    12. String path = request.getURI().getPath();
    13. if (!StrUtil.containsAnyIgnoreCase(path, SecurityConstants.OAUTH_TOKEN_URL)) {
    14. return chain.filter(exchange);
    15. }
    16. //2.如果是刷新token模式的请求,则直接向下执行【因为其他模式的也有要校验的,只有刷新token不用校验】
    17. String grantType = request.getQueryParams().getFirst("grant_type");
    18. if (StrUtil.equals(grantType, SecurityConstants.REFRESH_TOKEN)) {
    19. return chain.filter(exchange);
    20. }
    21. try {
    22. //3.校验验证码【密码模式登录或者短信模式登录都需要校验~】
    23. checkCode(request);
    24. }
    25. catch (Exception e) {
    26. //若有异常则返回ServerHttpResponse类型,输出为 Json 格式
    27. ServerHttpResponse response = exchange.getResponse();
    28. response.setStatusCode(HttpStatus.PRECONDITION_REQUIRED);
    29. response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
    30. final String errMsg = e.getMessage();
    31. return response.writeWith(Mono.create(monoSink -> {
    32. try {
    33. byte[] bytes = objectMapper.writeValueAsBytes(R.failed(errMsg));
    34. DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
    35. monoSink.success(dataBuffer);
    36. }
    37. catch (JsonProcessingException jsonProcessingException) {
    38. log.error("对象输出异常", jsonProcessingException);
    39. monoSink.error(jsonProcessingException);
    40. }
    41. }));
    42. }
    43. return chain.filter(exchange);
    44. };
    45. }
    46. @SneakyThrows
    47. private void checkCode(ServerHttpRequest request) {
    48. //1.校验是否有 code 值
    49. String code = request.getQueryParams().getFirst("code");
    50. if (CharSequenceUtil.isBlank(code)) {
    51. log.info("登录请求,验证码为空!");
    52. throw new RuntimeException("验证码不能为空");
    53. }
    54. //2.校验是否有 code 的唯一标识值,密码验证码模式登录从 randomStr 里取,短信模式可以从 mobile 里面取,但保证 mobile 属性必须只有一个!
    55. String randomStr = request.getQueryParams().getFirst("randomStr");
    56. if (CharSequenceUtil.isBlank(randomStr)) {
    57. randomStr = request.getQueryParams().getFirst("mobile");
    58. }
    59. //3.从 redis 里面拿到对应的验证码值
    60. String key = CacheConstants.DEFAULT_CODE_KEY + randomStr;
    61. Object codeObj = redisTemplate.opsForValue().get(key);
    62. //4.无论拿没拿到都要进行删除
    63. redisTemplate.delete(key);
    64. //5.判断两个值是否一致,不一致则抛出异常
    65. if (ObjectUtil.isEmpty(codeObj) || !code.equals(codeObj)) {
    66. throw new ValidateCodeException("验证码不合法");
    67. }
    68. }
    69. }
      1. //4.给网关里面的 auth 路由模块添加过滤器ValidateCodeGatewayFilter;
      2. # 配置到 nacos 配置中
      3. spring:
      4. cloud:
      5. nacos:
      6. gateway:
      7. discovery:
      8. locator:
      9. enabled: true # 让gateway可以发现nacos中的微服务
      10. routes:
      11. # 认证中心
      12. - id: pig-auth
      13. uri: lb://pig-auth
      14. predicates:
      15. - Path=/auth/**
      16. filters:
      17. - StripPrefix=1 #去掉特定前缀个数
      18. # 验证码处理
      19. - ValidateCodeGatewayFilter
      20. # 前端密码解密
      21. - PasswordDecoderFilter

       

      B3.测试

      给 /oauth2/token 接口添加入参 code ,先调用获取验证码的接口拿到 code 值,然后再调用登录接口~

       

       

       

       

    70. 相关阅读:
      善于利用GPT确实可以解决许多难题
      文心一言 VS 讯飞星火 VS chatgpt (97)-- 算法导论9.3 3题
      Java基于springboot+vue的流浪动物救助收养平台 nodejs 前后端分离
      用户提交job后,abaqus的inp文件处理过程
      Redis配置文件
      Abbkine BCA法 蛋白质定量试剂盒说明书
      「C系列」C 数据类型
      Linux 启动流程及相关知识
      使用docker快速安装开发环境
      前端总结35.JS封装事件库
    71. 原文地址:https://blog.csdn.net/vaevaevae233/article/details/127751545