!
也想出现在这里? 联系我们
广告区块

【Spring Cloud】新闻头条微服务项目:使用JWT+MD5+Salt进行登录验证

【Spring Cloud】新闻头条微服务项目:使用JWT+MD5+Salt进行登录验证

个人简介:

> ?个人主页:赵四司机
> ?学习方向:JAVA后端开发
> ?种一棵树最好的时间是十年前,其次是现在!
> ⏰往期文章:SpringBoot项目整合微信支付
> ?喜欢的话麻烦点点关注喔,你们的支持是我的最大动力。

前言:

最近在做一个基于SpringCloud+Springboot+Docker的新闻头条微服务项目,现在项目开发进入了尾声,我打算通过写文章的形式进行梳理一遍,并且会将梳理过程中发现的Bug进行修复,有需要改进的地方我也会继续做出改进。这一系列的文章我将会放入微服务项目专栏中,这个项目适合刚接触微服务的人作为练手项目,假如你对这个项目感兴趣你可以订阅我的专栏进行查看,需要资料可以私信我,当然要是能给我点个小小的关注就更好了,你们的支持是我最大的动力。

一:需求分析

现在无论什么应用都少不了登录验证环节,而登录环节又少不了安全与校验,一是要防止用户信息被盗取,而是要防止用户利用漏洞进行暴力登录。所以针对这两个方面我选用的方案是采用MD5加盐进行加密并采用JWT进行验证的方案。当然用户还可以选择以游客身份进行访问,但是只有在登录的情况下才能对文章进行点赞、关注及收藏等动作。

二:表结构分析

1.数据库结构

由于App端关于用户相关信息比较多,所以单独创建了一个数据库(headlines_user)进行管理,主要包括如下几张表:

表名称 说明
ap_user APP用户信息表
ap_user_fan APP用户粉丝信息表
ap_user_follow APP用户关注信息表
ap_user_realname APP实名认证信息表

关于用户登录用到的是ap_user表 ,表的结构如下:

【Spring Cloud】新闻头条微服务项目:使用JWT+MD5+Salt进行登录验证

2.实体类

  1. package com.my.model.user.pojos;
  2. import com.baomidou.mybatisplus.annotation.IdType;
  3. import com.baomidou.mybatisplus.annotation.TableField;
  4. import com.baomidou.mybatisplus.annotation.TableId;
  5. import com.baomidou.mybatisplus.annotation.TableName;
  6. import lombok.Data;
  7. import java.io.Serializable;
  8. import java.util.Date;
  9. /**
  10. *

  11. * APP用户信息表
  12. *
  13. *
  14. * @author itheima
  15. */
  16. @Data
  17. @TableName("ap_user")
  18. public class ApUser implements Serializable {
  19. private static final long serialVersionUID = 1L;
  20. /**
  21. * 主键
  22. */
  23. @TableId(value = "id", type = IdType.AUTO)
  24. private Integer id;
  25. /**
  26. * 密码、通信等加密盐
  27. */
  28. @TableField("salt")
  29. private String salt;
  30. /**
  31. * 用户名
  32. */
  33. @TableField("name")
  34. private String name;
  35. /**
  36. * 密码,md5加密
  37. */
  38. @TableField("password")
  39. private String password;
  40. /**
  41. * 手机号
  42. */
  43. @TableField("phone")
  44. private String phone;
  45. /**
  46. * 头像
  47. */
  48. @TableField("image")
  49. private String image;
  50. /**
  51. * 0 男
  52. * 1 女
  53. * 2 未知
  54. */
  55. @TableField("sex")
  56. private Boolean sex;
  57. /**
  58. * 0 未
  59. * 1 是
  60. */
  61. @TableField("is_certification")
  62. private Boolean certification;
  63. /**
  64. * 是否身份认证
  65. */
  66. @TableField("is_identity_authentication")
  67. private Boolean identityAuthentication;
  68. /**
  69. * 0正常
  70. * 1锁定
  71. */
  72. @TableField("status")
  73. private Boolean status;
  74. /**
  75. * 0 普通用户
  76. * 1 自媒体人
  77. * 2 大V
  78. */
  79. @TableField("flag")
  80. private Short flag;
  81. /**
  82. * 注册时间
  83. */
  84. @TableField("created_time")
  85. private Date createdTime;
  86. }

三:思路分析

【Spring Cloud】新闻头条微服务项目:使用JWT+MD5+Salt进行登录验证

首先用户的任何请求都会经过网关,这时候网关就会进行拦截认证,假如用户现在的请求时登录,那这时候可以直接放行让用户去登录,否则的话就会检验用户请求头中是否包含有效的token信息,假如包含则直接放行,否则进行拦截并让用户重新登录。

而用户登录时候首先会根据用户账号到数据库中查询该用户信息,然后将用户输入的密码与数据库中获得的Salt进行合并加密并与数据库中的密码(已加盐加密)进行比对,如果比对失败则提示相关信息,比对成功则判断用户状态,只有未被锁定方可继续操作。用户账号状态正常的话才能根据用户id生成token并保存然后放行。

四:代码实现

1.网关配置

(1)引入依赖

在tbug-headlines-gateway模块引入以下依赖

  1. dependencies>
  2. dependency>
  3. groupId>org.springframework.cloudgroupId>
  4. artifactId>spring-cloud-starter-gatewayartifactId>
  5. dependency>
  6. dependency>
  7. groupId>com.alibaba.cloudgroupId>
  8. artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
  9. dependency>
  10. dependency>
  11. groupId>com.alibaba.cloudgroupId>
  12. artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
  13. dependency>
  14. dependency>
  15. groupId>io.jsonwebtokengroupId>
  16. artifactId>jjwtartifactId>
  17. dependency>
  18. dependencies>

(2)搭建工程

在tbug-headlines-gateway模块下创建工程tbug-headlines-app-gateway,整体架构如下:

【Spring Cloud】新闻头条微服务项目:使用JWT+MD5+Salt进行登录验证

(3)Nacos配置

在nacos的配置中心创建dataid为headlines-app-gateway的yml配置

【Spring Cloud】新闻头条微服务项目:使用JWT+MD5+Salt进行登录验证

  1. spring:
  2. cloud:
  3. gateway:
  4. globalcors:
  5. add-to-simple-url-handler-mapping: true
  6. corsConfigurations:
  7. '[/**]':
  8. allowedHeaders: "*"
  9. allowedOrigins: "*"
  10. allowedMethods:
  11. - GET
  12. - POST
  13. - DELETE
  14. - PUT
  15. - OPTION
  16. routes:
  17. # 用户微服务
  18. - id: user
  19. uri: lb://headlines-user
  20. predicates:
  21. - Path=/user/**
  22. filters:
  23. - StripPrefix= 1

(3)配置文件

application.yml

  1. server:
  2. port: 51601
  3. spring:
  4. application:
  5. name: headlines-app-gateway
  6. cloud:
  7. nacos:
  8. discovery:
  9. server-addr: 49.234.52.192:8848
  10. config:
  11. server-addr: 49.234.52.192:8848
  12. file-extension: yml

该配置主要是配置你前面安装的nacos注册中心。

(5)相关代码

认证过滤器:

  1. package com.my.app.gateway.filter;
  2. import com.my.app.gateway.util.AppJwtUtil;
  3. import io.jsonwebtoken.Claims;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.apache.commons.lang.StringUtils;
  6. import org.springframework.cloud.gateway.filter.GatewayFilterChain;
  7. import org.springframework.cloud.gateway.filter.GlobalFilter;
  8. import org.springframework.core.Ordered;
  9. import org.springframework.http.HttpStatus;
  10. import org.springframework.http.server.reactive.ServerHttpRequest;
  11. import org.springframework.http.server.reactive.ServerHttpResponse;
  12. import org.springframework.stereotype.Component;
  13. import org.springframework.web.server.ServerWebExchange;
  14. import reactor.core.publisher.Mono;
  15. @Component
  16. @Slf4j
  17. public class AuthorizeFilter implements Ordered, GlobalFilter {
  18. @Override
  19. public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  20. //1.获取request和response对象
  21. ServerHttpRequest request = exchange.getRequest();
  22. ServerHttpResponse response = exchange.getResponse();
  23. //2.判断是否是登录
  24. if(request.getURI().getPath().contains("/login")){
  25. //放行
  26. return chain.filter(exchange);
  27. }
  28. //3.获取token
  29. String token = request.getHeaders().getFirst("token");
  30. //4.判断token是否存在
  31. if(StringUtils.isBlank(token)){
  32. response.setStatusCode(HttpStatus.UNAUTHORIZED);
  33. return response.setComplete();
  34. }
  35. //5.判断token是否有效
  36. try {
  37. Claims claimsBody = AppJwtUtil.getClaimsBody(token);
  38. //是否是过期
  39. int result = AppJwtUtil.verifyToken(claimsBody);
  40. if(result == 1 || result == 2){
  41. response.setStatusCode(HttpStatus.UNAUTHORIZED);
  42. return response.setComplete();
  43. }
  44. //获取token解析后的用户信息
  45. Object userId = claimsBody.get("id");
  46. //在header中添加新的信息
  47. ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders -> {
  48. httpHeaders.add("userId",userId + "");
  49. }
  50. ).build();
  51. //重置header
  52. exchange.mutate().request(serverHttpRequest).build();
  53. }catch (Exception e){
  54. e.printStackTrace();
  55. response.setStatusCode(HttpStatus.UNAUTHORIZED);
  56. return response.setComplete();
  57. }
  58. //6.放行
  59. return chain.filter(exchange);
  60. }
  61. /**
  62. * 优先级设置 值越小 优先级越高
  63. * @return
  64. */
  65. @Override
  66. public int getOrder() {
  67. return 0;
  68. }
  69. }

JWT工具类:

  1. package com.my.app.gateway.util;
  2. import io.jsonwebtoken.*;
  3. import javax.crypto.SecretKey;
  4. import javax.crypto.spec.SecretKeySpec;
  5. import java.util.*;
  6. public class AppJwtUtil {
  7. // TOKEN的有效期一天(S)
  8. private static final int TOKEN_TIME_OUT = 3_600;
  9. // 加密KEY
  10. private static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";
  11. // 最小刷新间隔(S)
  12. private static final int REFRESH_TIME = 300;
  13. // 生产ID
  14. public static String getToken(Long id){
  15. Map claimMaps = new HashMap();
  16. claimMaps.put("id",id);
  17. long currentTime = System.currentTimeMillis();
  18. return Jwts.builder()
  19. .setId(UUID.randomUUID().toString())
  20. .setIssuedAt(new Date(currentTime)) //签发时间
  21. .setSubject("system") //说明
  22. .setIssuer("heima") //签发者信息
  23. .setAudience("app") //接收用户
  24. .compressWith(CompressionCodecs.GZIP) //数据压缩方式
  25. .signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式
  26. .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000)) //过期时间戳
  27. .addClaims(claimMaps) //cla信息
  28. .compact();
  29. }
  30. /**
  31. * 获取token中的claims信息
  32. *
  33. * @param token
  34. * @return
  35. */
  36. private static Jws getJws(String token) {
  37. return Jwts.parser()
  38. .setSigningKey(generalKey())
  39. .parseClaimsJws(token);
  40. }
  41. /**
  42. * 获取payload body信息
  43. *
  44. * @param token
  45. * @return
  46. */
  47. public static Claims getClaimsBody(String token) {
  48. try {
  49. return getJws(token).getBody();
  50. }catch (ExpiredJwtException e){
  51. return null;
  52. }
  53. }
  54. /**
  55. * 获取hearder body信息
  56. *
  57. * @param token
  58. * @return
  59. */
  60. public static JwsHeader getHeaderBody(String token) {
  61. return getJws(token).getHeader();
  62. }
  63. /**
  64. * 是否过期
  65. *
  66. * @param claims
  67. * @return -1:有效,0:有效,1:过期,2:过期
  68. */
  69. public static int verifyToken(Claims claims) {
  70. if(claims==null){
  71. return 1;
  72. }
  73. try {
  74. claims.getExpiration()
  75. .before(new Date());
  76. // 需要自动刷新TOKEN
  77. if((claims.getExpiration().getTime()-System.currentTimeMillis())>REFRESH_TIME*1000){
  78. return -1;
  79. }else {
  80. return 0;
  81. }
  82. } catch (ExpiredJwtException ex) {
  83. return 1;
  84. }catch (Exception e){
  85. return 2;
  86. }
  87. }
  88. /**
  89. * 由字符串生成加密key
  90. *
  91. * @return
  92. */
  93. public static SecretKey generalKey() {
  94. byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());
  95. SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
  96. return key;
  97. }
  98. public static void main(String[] args) {
  99. /* Map map = new HashMap();
  100. map.put("id","11");*/
  101. System.out.println(AppJwtUtil.getToken(1102L));
  102. Jws jws = AppJwtUtil.getJws("eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQqEMAwA_5KzhURNt_qb1KZYQSi0wi6Lf9942NsMw3zh6AVW2DYmDGl2WabkZgreCaM6VXzhFBfJMcMARTqsxIG9Z888QLui3e3Tup5Pb81013KKmVzJTGo11nf9n8v4nMUaEY73DzTabjmDAAAA.4SuqQ42IGqCgBai6qd4RaVpVxTlZIWC826QA9kLvt9d-yVUw82gU47HDaSfOzgAcloZedYNNpUcd18Ne8vvjQA");
  103. Claims claims = jws.getBody();
  104. System.out.println(claims.get("id"));
  105. }
  106. }

2.微服务搭建

(1)搭建工程

在tbug-headlines-service下创建工程tbug-headlines-user

【Spring Cloud】新闻头条微服务项目:使用JWT+MD5+Salt进行登录验证

(2)配置信息

①application.yml

  1. server:
  2. port: 51801
  3. spring:
  4. application:
  5. name: headlines-user
  6. cloud:
  7. nacos:
  8. discovery:
  9. server-addr: 49.234.52.192:8848
  10. config:
  11. server-addr: 49.234.52.192:8848
  12. file-extension: yml

②Nacos配置

在Nacos中添加id为headlines-user的配置项,配置信息如下

  1. spring:
  2. redis:
  3. host: 49.234.52.192
  4. password: 440983
  5. port: 6379
  6. datasource:
  7. driver-class-name: com.mysql.jdbc.Driver
  8. url: jdbc:mysql://localhost:3306/headlines_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
  9. username: root
  10. password: 440983
  11. # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
  12. mybatis-plus:
  13. mapper-locations: classpath*:mapper/*.xml
  14. # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
  15. type-aliases-package: com.my.model.user.pojos

(3)功能代码

Controller层:

  1. package com.my.user.controller.v1;
  2. import com.my.model.common.dtos.ResponseResult;
  3. import com.my.model.user.dtos.LoginDto;
  4. import com.my.user.service.ApUserService;
  5. import io.swagger.annotations.Api;
  6. import io.swagger.annotations.ApiOperation;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.web.bind.annotation.PostMapping;
  9. import org.springframework.web.bind.annotation.RequestBody;
  10. import org.springframework.web.bind.annotation.RequestMapping;
  11. import org.springframework.web.bind.annotation.RestController;
  12. @RestController
  13. @RequestMapping("/api/v1/login")
  14. @Api(value = "app端用户登录",tags = "app端用户登录")
  15. public class ApUserLoginController {
  16. @Autowired
  17. private ApUserService apUserService;
  18. @PostMapping("/login_auth")
  19. @ApiOperation("用户登录")
  20. public ResponseResult login(@RequestBody LoginDto dto){
  21. return apUserService.login(dto);
  22. }
  23. }

mapper层:

  1. package com.my.user.mapper;
  2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  3. import com.my.model.user.pojos.ApUser;
  4. import org.apache.ibatis.annotations.Mapper;
  5. @Mapper
  6. public interface ApUserMapper extends BaseMapper {
  7. }

service层:

  1. package com.my.user.service;
  2. import com.baomidou.mybatisplus.extension.service.IService;
  3. import com.my.model.admin.dtos.UserAuditDto;
  4. import com.my.model.common.dtos.ResponseResult;
  5. import com.my.model.user.dtos.LoginDto;
  6. import com.my.model.user.pojos.ApUser;
  7. public interface ApUserService extends IService {
  8. /**
  9. * app端登录功能
  10. * @param dto
  11. * @return
  12. */
  13. ResponseResult login(LoginDto dto);
  14. }
  1. package com.my.user.service.impl;
  2. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  3. import com.baomidou.mybatisplus.core.metadata.IPage;
  4. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  5. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  6. import com.my.model.admin.dtos.UserAuditDto;
  7. import com.my.model.common.dtos.ResponseResult;
  8. import com.my.model.common.enums.AppHttpCodeEnum;
  9. import com.my.model.user.dtos.LoginDto;
  10. import com.my.model.user.pojos.ApUser;
  11. import com.my.user.mapper.ApUserMapper;
  12. import com.my.user.service.ApUserService;
  13. import com.my.utils.common.AppJwtUtil;
  14. import lombok.extern.slf4j.Slf4j;
  15. import org.springframework.stereotype.Service;
  16. import org.springframework.transaction.annotation.Transactional;
  17. import org.springframework.util.DigestUtils;
  18. import java.nio.charset.StandardCharsets;
  19. import java.util.HashMap;
  20. import java.util.Map;
  21. @Service
  22. @Transactional
  23. @Slf4j
  24. public class ApUserServiceImpl extends ServiceImpl implements ApUserService {
  25. /**
  26. * app端登录功能
  27. * @param dto
  28. * @return
  29. */
  30. @Override
  31. public ResponseResult login(LoginDto dto) {
  32. //1.正常登录 用户名和密码
  33. if(!dto.getPhone().isEmpty() && !dto.getPassword().isEmpty()){
  34. //1.1根据用户名获取用户信息
  35. LambdaQueryWrapper lqw = new LambdaQueryWrapper();
  36. lqw.eq(ApUser::getPhone,dto.getPhone());
  37. ApUser user = this.getOne(lqw);
  38. //1.2没有该用户信息
  39. if(user == null) {
  40. return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"用户信息不存在");
  41. }
  42. //1.3比对用户密码
  43. String password = user.getPassword();
  44. String salt = user.getSalt();
  45. String loginPassword = DigestUtils.md5DigestAsHex((dto.getPassword() + salt).getBytes(StandardCharsets.UTF_8));
  46. if(!loginPassword.equals(password)) {
  47. //密码不正确
  48. return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR,"密码错误");
  49. }
  50. //1.4查看用户状态
  51. if(user.getStatus()) {
  52. //用户被锁定
  53. return ResponseResult.errorResult(AppHttpCodeEnum.NO_OPERATOR_AUTH,"用户已被锁定");
  54. }
  55. //1.5设置token
  56. String token = AppJwtUtil.getToken(user.getId().longValue());
  57. Map map = new HashMap();
  58. map.put("token",token);
  59. map.put("user",user);
  60. return ResponseResult.okResult(map);
  61. }else {
  62. //2.游客登录
  63. Map map = new HashMap();
  64. map.put("token",AppJwtUtil.getToken(0L));
  65. return ResponseResult.okResult(map);
  66. }
  67. }
  68. }

下篇预告:App端文章的加载

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_45750572/article/details/126136810
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
有新私信 私信列表
搜索