黑马头条-day01

系列文章目录

黑马头条-day01
黑马头条-day02


此系列文章皆为黑马头条项目的学习笔记


一、前期准备

1.1 虚拟机登录信息

登录ip:192.168.200.130
用户名:root
密码:itcast

1.2 docker图形化工具portainer登录信息

访问地址http://192.168.200.130:9001

用户名:admin

密码:admin123456

1.3 mysql数据库信息

用户名:root
密码:root

1.4 nacos注册配置中心地址

192.168.200.130:8848

1.5 Swagger地址

http://localhost:51801/swagger-ui.html

1.6 knife4j地址

http://localhost:51801/doc.html

1.7 minio地址

http://192.168.200.130:9000
用户名:minio
密码:minio123

二、接口代码编写

2.1 APP登录

2.1.1 加密算法

(1)可逆加密算法-对称加密

文件加密和解密使用相同的密钥,即加密秘钥也可以用作解密密钥
在这里插入图片描述
优点: 对称加密算法的优点是算法公开、计算量小、加密速度快、加密效率高。
缺点: 没有非对称加密安全
用途: 一般用于保存用户手机号、身份证等敏感但能解密的信息。
常见的对称加密算法有:AES(号称最安全的加密算法)DES、3DES(已经被破解了,不建议使用)、Blowfish、IDEA、RC4、RC5、RC6、HS256

(2)可逆加密算法-非对称加密

两个密钥:公开密钥和私有密钥,公有密钥加密,私有密钥解密,
或者私钥加密,公钥解密。

在这里插入图片描述
优点: 非对称加密与对称加密相比,其安全性更好。
缺点: 非对称加密的缺点是加密和解密花费时间长、速度慢,只适合对少量数据进行加密
用途: 一般用于签名和认证。私钥服务器保存,用来加密,公钥客户拿着用于对于令牌或者签名的解密或者校验使用。
常见的非对称加密算法有:RSA(比较常用)、DSA(数字签名用)、ECC(移动设备用)、RS256(采用SHA-56的RSA签名)

(3)不可逆加密算法

一旦加密就不能反向解密得到密码原文。
种类: Hash加密算法,散列算法,摘要算法
用途: 一般用于校验下载文件正确性,一般在网站下载文件都能见到;存储用户敏感信息,如密码、卡号等不可解密的信息。
常见的不可加密的算法有:MD5(已经被破解)、SHA、BCrypt
文件md5生成http://www.metools.info/other/o21.html

(4)Base64编码

Base64是网络上最常见的用语传输8Bit字节代码的编码方式之一。Base64编码可用于在HTTP环境下传递较长的标识信息。采用Base64编码解码具有不可读性,即所编码的数据不会被人用肉眼所直接看到。注意:Base64只是一种编码方式,不算加密方法。
在线编码工具: https://tool.oschina.net/encrypt?type=3

2.1.2 密码加密的方式选型

(1)手动加密(md5+随机字符串–>“salt”)

md5是不可逆加密,md5相同的密码每次加密都一样,不太安全在md5的基础上手动加盐(salt)处理,这样每一次的值都不一样
但是需要把加盐后的密码和盐一起保存到数据库,这样就有一个问题,如果数据库不安全的情况下(盐被盗走了),这样的加密方式还是不行
在这里插入图片描述

不加盐测试代码
// 测试代码
@Test
public void testMd5() {
	for (int i = 0; i < 10; i++) {
		// 明文
		String pwd = "123456";
		// 密文
		String pwdEncrypt = DigestUtils.md5DigestAsHex(pwd.getBytes());
		System.out.println(pwdEncrypt);
	}
}
测试结果

发现循环十次md5加密,密文都是一样的。
在这里插入图片描述

加盐测试代码
@Test
public void testMd5AndSalt() {

	for (int i = 0; i < 10; i++) {
		// 明文
		String pwd = "123456";
		// 用工具类生成随机字符串作为盐
		String salt = RandomStringUtils.randomAlphabetic(10);
		// 明文和盐做字符串的拼接,生成新的明文密码
		String pwdNews = pwd + salt;
		// 使用工具类,对新生成的明文加密
		String pwdEncrypt = DigestUtils.md5DigestAsHex(pwdNews.getBytes());
		System.out.println(salt + "===" + pwdEncrypt);
	}
}
测试结果

在这里插入图片描述

md5+盐验证密码

①获取登录密码明文
②获取表中的盐
③明文+盐拼接成新的明文
④对拼接成的明文进行md5加密
⑤加密后的密文和数据库中的密文做对比

(2)自动加盐-BCrypt密码加密

BCrypt: 自动加盐,所以也就不用把盐的值存储到数据库中了。
在这里插入图片描述

测试代码
@Test
public void testbCrypt() {

	for (int i = 0; i < 10; i++) {
		// 明文
		String pwd = "123456";
		// 生成盐 ===> 随机字符串
		String salt = BCrypt.gensalt();
		// 密文
		String pwdEncrypt = BCrypt.hashpw(pwd, salt);
		System.out.println(salt +"====="+ pwdEncrypt);
	}
}
测试结果

 ,

验证密码

①获取登录密码明文
②获取表中密码密文
③使用BCrypt.checkpw(明文,密文)方法来获取boolean类型的result
④对result进行判断

2.1.3 JWT

(1)token认证

基于token的用户认证是一种服务端无状态的认证方式(类似于存在服务端的session,就是一种有状态的认证方式),所谓服务端无状态指的是token本身包含登录用户所有的相关数据,而客户端在认证后的每次请求都会携带token,因此服务器端无需存放token数据。
当用户认证后,服务端生成一个token发给客户端,客户端可以放到cookie或localStorage等存储中,每次请求时带上token,服务端收到token通过验证即可确认用户身份。
在这里插入图片描述

(2)什么是JWT?

JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简洁、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。
JWT令牌结构:
Header.Payload.Signature
①Header:头部包括了JWT的类型,并且还定义了signature,也就是签名部分需要用到的算法
例如:
在这里插入图片描述
并且要将上面的json内容,使用Base64Url编码,得到的字符创就是JWT的第一部分
注意:JWT的头中的算法是可逆加密算法,要么对称,要么不对称,不可能出现不可逆算法

②Payload(载荷):载荷,也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比如:iss(签发者),exp(过期时间戳),sub(面相的用户)等,也可以自定义字段。
此部分不建议存放敏感信息,因为此部分可以解码还原原始内容
例如:
在这里插入图片描述
这一部分也需要使用Base64Url编码,得到一个字符串也就是jwt的第二部分。

③Singnature
第三部分是签名,此部分用于防止JWT内容被篡改。
这个部分使用base64Url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明签名算法进行签名。
例如:
在这里插入图片描述
base64UrlEncode(header):jwt令牌的第一部分
base64UrlEncode(payload):jwt令牌的第二部分
secret:签名所使用的密钥
下图是一个jwt令牌:
在这里插入图片描述

(3)生成token
测试代码
@Test
public void testCreateJwt() {

	for (int i = 0; i < 10; i++) {
		// 第三部分要使用的密钥
		String secretKey = "itcast";
		String token = Jwts.builder().signWith(SignatureAlgorithm.HS256, secretKey)
				// 内置属性 jti,表示唯一id
				.setId(UUID.randomUUID().toString())
				// 内置属性 iat,表示创建时间
				.setIssuedAt(new Date())
				// 内置属性 sub,表示面向的对象
				.setSubject("all")
				// 自定义属性 name age
				.claim("name", "zhangsan")
				.claim("age", 20)
				.compact();
		System.out.println(token);
	}
}
执行结果

在这里插入图片描述
发现生成的token是变化的,这是因为,设置了变化的id和日期,将设置id和日期的代码注释掉,就会发现,生成的token就是一样的了。

(4)解析JWT令牌(token)
测试代码
@Test
public void testParseJwt() {
	String secretKey = "itcast";
	String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4M2MxNmE5My02ODI0LTQyMzQtODNjNy1kNDY1YTk0YmQ3MmEiLCJpYXQiOjE3MDcxNTQ5MTIsInN1YiI6ImFsbCIsIm5hbWUiOiJ6aGFuZ3NhbiIsImFnZSI6MjB9.4q0JaYCIoszUqgBaQKgmcoL9id8zP2RH-fpbHnELW7c";
	Jws<Claims> claimsJws = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
	System.out.println(claimsJws.getHeader());	// 获取头
	System.out.println(claimsJws.getSignature());	// 获取签名
	System.out.println(claimsJws.getBody());	// 获取载荷
}
测试结果

在这里插入图片描述
如果token被篡改,或者token过期了,在解析的时候,会报SignatureException(签名异常)和ExpiredJwtException(过期异常)

(5)JWT工具类的使用

heima-leadnews-utils服务下的AppJwtUtil类。
因为在工具类中设置了压缩方式,所以载荷在网站解码的时候解不了了,只能通过parse来解析。

// 测试生成token
// 参数是用户id
String token = AppJwtUtil.getToken(1L);

// 测试解析token
String token2 = "里面是token字符串";
Claims claimsBody = AppJwtUtil.getClaimsBody(token2);
// 0:表示无效
// 1:表示有效,且在有效期范围内
int result = AppJwtUtil.verifyToken(claimsBody);
(6)JWT和token的区别

JWT是Token无状态认证技术的具体实现规范

  • JWT组成:Header、Payload、Signature
  • JWT场景:登录成功生成Token、访问西戎携带Token服务端验证Token合法性
  • JWT解析异常:Token被篡改、Token过期了

2.1.4 Bcrypt加密算法实现用户登录验证

(1)流程图

在这里插入图片描述

(2)接口设计

在这里插入图片描述

(3)代码实现
controller层
@RestController
@RequestMapping("api/v1/login")
public class ApUserLoginController {

	@Autowired
	private ApUserService apUserService;

	@PostMapping("login_auth")
	public ResponseResult login(@RequestBody LoginDto dto) {

		return apUserService.login(dto);
	}
}
service层

定义接口

public interface ApUserService extends IService<ApUser> {

	/**
	 * 用户登录(正常用户或者游客)
	 * @param dto
	 * @return
	 */
	ResponseResult login(LoginDto dto);
}

接口实现类

@Slf4j
@Service
public class ApUserServiceImpl extends ServiceImpl<ApUserMapper, ApUser> implements ApUserService {


	@Override
	public ResponseResult login(LoginDto dto) {

		// 1.如果密码和手机号都不为空的话,那么就是处理普通用户登录
		if (StringUtils.isNotBlank(dto.getPassword()) && StringUtils.isNotBlank(dto.getPhone())){
			/*// 设置查询条件
			LambdaQueryWrapper<ApUser> lambdaQueryWrapper = new LambdaQueryWrapper<>();
			lambdaQueryWrapper.eq(ApUser::getPhone, dto.getPhone());
			// 根据条件查询一条数据
			ApUser apUser = this.getOne(lambdaQueryWrapper);*/
			// 以后就这样写
			ApUser apUser = this.getOne(Wrappers.<ApUser>lambdaQuery().eq(ApUser::getPhone, dto.getPhone()));

			// 如果用户为空,则说明手机号填写错误
			if (apUser == null) {
				// 返回错误信息
				return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"手机号填写错误");
			}
			// 获取密码参数
			String textPass = dto.getPassword();
			// 表中的密码
			String pass = apUser.getPassword();
			// 使用BCrypt算法校验密码
			boolean result = BCrypt.checkpw(textPass, pass);
			// 如果密码错误
			if (!result) {
				// 返回密码错误信息
				return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
			}
			// 用户名和密码都正确
			// 生成token
			String token = AppJwtUtil.getToken(apUser.getId().longValue());
			// 封装到map集合中
			Map map = new HashMap();
			// 因为密码信息比较敏感,所以要把用户的密码设置为空
			apUser.setPassword("");
			map.put("user", apUser);
			map.put("token", token);
			// 返回
			return ResponseResult.okResult(map);

		} else {
			// 如果手机号或者密码都为空的话,就是游客登录模式
			// 直接为游客用户按照id=0L生成token
			String token = AppJwtUtil.getToken(0L);
			// 把token放入集合中
			Map map = new HashMap();
			map.put("token", token);
			// 返回结果
			return ResponseResult.okResult(map);
		}
	}
}

三、postman测试

在这里插入图片描述

四、接口工具-Swagger

Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风格的Web服务(https://swagger.io/)。它的主要作用是:
1.使得前后端分离开发更加方便,有利于团队协作
2.接口文档在线自动生成,降低后端开发人员编写接口文档的负担
3.功能测试

(1)Springboot集成Swagger

引入依赖,在heima-leadnews-model和heima-leadnews-common模块中引入该依赖

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
</dependency>

只需要在heima-leadnews-common中进行配置即可,因为其他微服务工程都直接或间接依赖即可。
在heima-leadnews-common工程中添加一个配置类

package com.heima.common.swagger;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class Swagger2Configuration {

   @Bean
   public Docket buildDocket() {
      return new Docket(DocumentationType.SWAGGER_2)
              .apiInfo(buildApiInfo())
              .select()
              // 要扫描的API(Controller)基础包
              .apis(RequestHandlerSelectors.basePackage("com.heima"))
              .paths(PathSelectors.any())
              .build();
   }

   private ApiInfo buildApiInfo() {
      Contact contact = new Contact("黑马程序员","","");
      return new ApiInfoBuilder()
              .title("黑马头条-平台管理API文档")
              .description("黑马头条后台api")
              .contact(contact)
              .version("1.0.0").build();
   }
}

联调
在heima-leadnews-common模块中的resources目录中新增以下目录和文件
在这里插入图片描述

文件:resources/META-INF/Spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.heima.common.swagger.Swagger2Configuration

(2)Swagger常用注解

在Java类中添加Swagger的注解即可生成Swagger接口文档,常用Swagger注解如下:

@Api:修饰整个类,描述Controller的作用

@ApiOperation:描述一个类的一个方法,或者说一个接口

@ApiParam:单个参数的描述信息

@ApiModel:用对象来接收参数

@ApiModelProperty:用对象接收参数时,描述对象的一个字段

@ApiResponse:HTTP响应其中1个描述

@ApiResponses:HTTP响应整体描述

@ApiIgnore:使用该注解忽略这个API

@ApiError :发生错误返回的信息

@ApiImplicitParam:一个请求参数

@ApiImplicitParams:多个请求参数的描述信息

@ApiImplicitParam属性:
在这里插入图片描述
在这里插入图片描述

五、knife4j

基于Swagger改进优化。
原因:Swagger在线的接口文档没法去导出markdown,pdf这种离线文件。
Knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是Swagger-bootstrap-ui。
核心功能: 文档说明、在线调试、个性化配置、离线文档、接口排序、

(1)快速集成

在heima-leadnews-common模块中的pom.xml文件中引入knife4j的依赖,如下:

<dependency>
     <groupId>com.github.xiaoymin</groupId>
     <artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>

创建Swagger配置文件

在这里插入图片描述
以上有两个注解需要特别说明,如下表:
在这里插入图片描述
在这里插入图片描述

六、全局过滤器实现token校验

6.1 校验流程

在这里插入图片描述

6.2 代码实现

@Component
public class AuthFilter implements GlobalFilter, Ordered {
	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		// 获取请求
		ServerHttpRequest request = exchange.getRequest();
		// 获取响应
		ServerHttpResponse response = exchange.getResponse();
		// 判断是否是登录接口,是的话,直接放行
		String path = request.getURI().getPath();
		// 最后的 / 是要加上的,因为我们前端的请求路径是有的
		if (path.equals("/user/api/v1/login/login_auth/")){
			// 路径匹配上了,放行
			return chain.filter(exchange);
		}
		// 非登录接口,就要看是否携带token(无权访问)
		String token = request.getHeaders().getFirst("token");
		// token为空,不存在返回401()
		if (StringUtils.isBlank(token)){
			// 设置状态码
			response.setStatusCode(HttpStatus.UNAUTHORIZED);
			// 表明信息处理完成
			return response.setComplete();
		}

		// token 不为空,要对token进行核实
		int result = AppJwtUtil.verifyToken(AppJwtUtil.getClaimsBody(token));
		if (result == 0) {
			// 响应401,非法请求
			response.setStatusCode(HttpStatus.UNAUTHORIZED);
			// 表明信息处理完成
			return response.setComplete();
		}

		// 放行
		return chain.filter(exchange);
	}

	@Override
	public int getOrder() {
		return 0;	// 过滤器执行优先级,值越小,优先级越高
	}
}

  • 15
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值