【Java】从0实现一个基于SpringBoot的个人博客系统

news2024/11/17 10:51:35

从0实现一个基于SpringBoot的个人博客系统

  • 项目介绍
  • 准备工作
    • 数据准备
    • 创建项目
    • 准备前端页面
    • 编写配置文件
  • 项目公共模块
    • 实体类
    • 公共层
    • 业务代码
      • 持久层
      • 实现博客列表
      • 实现博客列表
        • 约定前后端交互接口
      • 实现博客详情
        • 约定前后端交互接口
        • 实现服务器代码
      • 实现登录
        • JWT令牌
          • JWT令牌生成和校验
          • 实现用户登录
            • 约定前后端交互接⼝
            • 实现服务器代码
      • 实现强制要求登录
        • 添加拦截器
      • 实现显示用户信息
        • 约定前后端交互接口
        • 实现服务器代码
      • 实现发布博客
        • 实现服务器代码
      • 实现删除/编辑博客
        • 约定前后端交互接口
          • 实现服务器代码
      • 加密/加盐
        • 写加密/解密工具类

项目介绍

使⽤SSM框架实现⼀个简单的博客系统
共5个⻚⾯

  1. ⽤⼾登录
  2. 博客发表⻚
  3. 博客编辑⻚
  4. 博客列表⻚
  5. 博客详情⻚

功能描述:
⽤⼾登录成功后, 可以查看所有⼈的博客. 点击 <<查看全⽂>> 可以查看该博客的正⽂内容. 如果该博客作者为当前登录⽤⼾, 可以完成博客的修改和删除操作, 以及发表新博客

页面预览

用户登录

在这里插入图片描述

博客详情
在这里插入图片描述
博客列表
在这里插入图片描述

博客发布
在这里插入图片描述

准备工作

数据准备

建表sql

-- 建表SQL
create database if not exists java_blog_spring charset utf8mb4;
-- ⽤⼾表
DROP TABLE IF EXISTS java_blog_spring.user;
CREATE TABLE java_blog_spring.user(
                                      `id` INT NOT NULL AUTO_INCREMENT,
                                      `user_name` VARCHAR ( 128 ) NOT NULL,
                                      `password` VARCHAR ( 128 ) NOT NULL,
                                      `github_url` VARCHAR ( 128 ) NULL,
                                      `delete_flag` TINYINT ( 4 ) NULL DEFAULT 0,
                                      `create_time` DATETIME DEFAULT now(),
                                      `update_time` DATETIME DEFAULT now(),
                                      PRIMARY KEY ( id ),
                                      UNIQUE INDEX user_name_UNIQUE ( user_name ASC )) ENGINE = INNODB DEFAULT CHARACT SET = utf8mb4 COMMENT = '⽤⼾表';
-- 博客表
drop table if exists java_blog_spring.blog;
CREATE TABLE java_blog_spring.blog (
                                       `id` INT NOT NULL AUTO_INCREMENT,
                                       `title` VARCHAR(200) NULL,
                                       `content` TEXT NULL,
                                       `user_id` INT(11) NULL,
                                       `delete_flag` TINYINT(4) NULL DEFAULT 0,
                                       `create_time` DATETIME DEFAULT now(),
                                       `update_time` DATETIME DEFAULT now(),
                                       PRIMARY KEY (id))
    ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '博客表';

创建项目

创建SpringBoot项⽬, 添加Spring MVC 和MyBatis对应依赖

准备前端页面

编写配置文件

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/java_blog_spring?characterEncoding=utf8&useSSL=false
    username: root
    password: 111111
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  configuration:
    map-underscore-to-camel-case: true #驼峰自动转换
    log-impl:  org.apache.ibatis.logging.stdout.StdOutImpl #打印sql语句
  mapper-locations: classpath:mapper/**Mapper.xml
# 设置⽇志⽂件的⽂件名
logging:
  file:
    name: spring-blog.log

项目公共模块

项⽬分为控制层(Controller), 服务层(Service), 持久层(Mapper).
在这里插入图片描述

实体类

实体类主要有Blog和User

@Data
public class BlogInfo {
    private Integer id;
    private String title;
    private String content;
    private Integer userId;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
    private boolean isLoginUser;

    public String getCreateTime() {
        return DateUtils.formatDate(createTime);
    }
}

@Data
public class UserInfo {
    private Integer id;
    private String userName;
    private String password;
    private String githubUrl;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
}

公共层

  1. 统一返回结果实体类

业务状态码:200 业务处理成功 -1业务处理失败 -2用户未登录
msg:业务处理失败时,返回的错误信息
data:业务返回数据

@Data
public class Result<T> {
    private int code;
    private String errorMsg;
    private T data;

    public static <T> Result<T> success(T data){
        Result result = new Result();
        result.setCode(Constants.RESULT_SUCCESS);
        result.setData(data);
        result.setErrorMsg("");
        return result;
    }

    public static <T> Result<T> fail(String errorMsg){
        Result result = new Result();
        result.setCode(Constants.RESULT_FAIL);
        result.setErrorMsg(errorMsg);
        return result;
    }

    public static <T> Result<T> fail(String errorMsg, T data){
        Result result = new Result();
        result.setCode(Constants.RESULT_FAIL);
        result.setErrorMsg(errorMsg);
        result.setData(data);
        return result;
    }
}
  1. 统一返回结果
/**
 * 统一返回结果
 */
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if(body instanceof Result){
            return body;
        }
        if(body instanceof String){
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.writeValueAsString(Result.success(body));
        }
        return Result.success(body);
    }
}

  1. 统一异常处理
@ResponseBody
@ControllerAdvice
public class ErrorAdvice {
    @ExceptionHandler
    public Result errorHandler(Exception e){
        Result result = new Result<>();
        result.setErrorMsg("内部错误,请联系管理员");
        result.setCode(Constants.RESULT_FAIL);
        return result;
    }
}

业务代码

持久层

根据需求, 先⼤致计算有哪些DB相关操作, 完成持久层初步代码, 后续再根据业务需求进⾏完善

  1. ⽤⼾登录⻚

a. 根据⽤⼾名查询⽤⼾信息

  1. 博客列表⻚

a. 根据id查询user信息 b. 获取所有博客列表

  1. 博客详情⻚

a. 根据博客ID查询博客信息 b. 根据博客ID删除博客(修改delete_flag=1)

  1. 博客修改⻚

a. 根据博客ID修改博客信息

  1. 发表博客

a. 插⼊新的博客数据

实现博客列表

@Mapper
public interface BlogInfoMapper {
    /**
     * 获取博客列表
     */
    @Select("select * from blog where delete_flag = 0 order by create_time desc")
    List<BlogInfo> queryBlogList();
    /**
     *根据博客id 获取博客详情
     */
    @Select("select * from blog where id = #{id} and delete_flag = 0")
    BlogInfo queryById(Integer id);
    /**
     * 编辑博客
     */
    @Update("update blog set title = #{title}, content = #{content} where id = #{id}")
    Integer update(BlogInfo blogInfo);

    /**
     * 删除博客
     */
    @Update("update blog set delete_flag = 1 where id = #{id}")
    Integer deleteBlog(Integer id);
    /**
     * 添加博客
     */
    @Insert("insert into blog(title,content,user_id) values(#{title}, #{content}, #{userId})")
    Integer insertBlog(BlogInfo blogInfo);

}
@Mapper
public interface UserInfoMapper {
    /**
     * 根据用户名 查询用户信息
     */
    @Select("select * from user where user_name = #{userName} and delete_flag = 0")
    UserInfo queryByName(String userName);

    /**
     * 根据用户ID 查询用户信息
     */
    @Select("select * from user where id = #{id} and delete_flag = 0")
    UserInfo queryById(Integer id);
}

实现博客列表

约定前后端交互接口

[请求] /blog/getlist
[响应]
{
“code”: 200,
“msg”: “”,
“data”: [{
“id”: 1,
“title”: “第⼀篇博客”,
“content”: “111我是博客正⽂我是博客正⽂我是博客正⽂”,
“userId”: 1,
“deleteFlag”: 0,
“createTime”: “2023-10-21 16:56:57”,
“updateTime”: “2023-10-21T08:56:57.000+00:00”
},

]
}
客户端给服务器发送一个/blog/getList的HTTP请求 服务器给客户端返回一个JSON格式的数据

@RequestMapping("/getList")
    public List<BlogInfo> getBlogList() {
        return blogService.getBlogList();
    }
public List<BlogInfo> getBlogList(){
        return blogInfoMapper.queryBlogList();
    }

实现博客详情

⽬前点击博客列表⻚的 “查看全⽂” , 能进⼊博客详情⻚, 但是这个博客详情⻚是写死的内容. 我们期望能够根据当前的 博客 id 从服务器动态获取博客内容.

约定前后端交互接口

[请求] /blog/getBlogDetail?blogId=1
[响应] {
“code”: 200,
“msg”: “”,
“data”: {
“id”: 1,
“title”: “第⼀篇博客”,
“content”: “111我是博客正⽂我是博客正⽂我是博客正⽂”,
“userId”: 1,
“deleteFlag”: 0,
“createTime”: “2023-10-21 16:56:57”,
“updateTime”: “2023-10-21T08:56:57.000+00:00”
}
}

实现服务器代码
@RequestMapping("/getBlogDetail")
public Blog getBlogDeatail(Integer blogId){
 return blogService.getBlogDeatil(blogId);
}
public BlogInfo getBlogDetail(Integer blogId) {
        return blogInfoMapper.queryById(blogId);
    }

实现登录

分析
传统思路:
• 登陆⻚⾯把⽤⼾名密码提交给服务器.
• 服务器端验证⽤⼾名密码是否正确, 并返回校验结果给后端
• 如果密码正确, 则在服务器端创建 Session . 通过 Cookie 把 sessionId 返回给浏览器.
问题
集群环境下⽆法直接使⽤Session.

解决方案

使用令牌

令牌其实就是⼀个⽤⼾⾝份的标识, 名称起的很⾼⼤上, 其实本质就是⼀个字符串.

服务器具备⽣成令牌和验证令牌的能⼒我们使⽤令牌技术, 继续思考上述场景:

  1. ⽤⼾登录 ⽤⼾登录请求, 经过负载均衡, 把请求转给了第⼀台服务器, 第⼀台服务器进⾏账号密码验证, 验证成功后, ⽣成⼀个令牌, 并返回给客⼾端.
  2. 客⼾端收到令牌之后, 把令牌存储起来. 可以存储在Cookie中, 也可以存储在其他的存储空间(⽐如localStorage)
  3. 查询操作 ⽤⼾登录成功之后, 携带令牌继续执⾏查询操作, ⽐如查询博客列表. 此时请求转发到了第⼆台机器, 第⼆台机器会先进⾏权限验证操作. 服务器验证令牌是否有效, 如果有效, 就说明⽤⼾已经执⾏了登录操作, 如果令牌是⽆效的, 就说明⽤⼾之前未执⾏登录操作.
JWT令牌

令牌本质就是⼀个字符串, 他的实现⽅式有很多, 我们采⽤⼀个JWT令牌来实现
介绍
JWT全称: JSON Web Token
官⽹: https://jwt.io/
JSON Web Token(JWT)是⼀个开放的⾏业标准(RFC 7519), ⽤于客⼾端和服务器之间传递安全可靠的信息.
其本质是⼀个token, 是⼀种紧凑的URL安全⽅法.

jwt组成

Header(头部) 头部包括令牌的类型(即JWT)及使⽤的哈希算法(如HMAC SHA256或RSA)
Payload(负载) 负载部分是存放有效信息的地⽅, ⾥⾯是⼀些⾃定义内容. ⽐如:{“userId”:“123”,“userName”:“zhangsan”} , 也可以存在jwt提供的现场字段, ⽐如exp(过期时间戳)等.
Signature(签名) 此部分⽤于防⽌jwt内容被篡改, 确保安全性防⽌被篡改, ⽽不是防⽌被解析.JWT之所以安全, 就是因为最后的签名. jwt当中任何⼀个字符被篡改, 整个令牌都会校验失败.就好⽐我们的⾝份证, 之所以能标识⼀个⼈的⾝份, 是因为他不能被篡改, ⽽不是因为内容加密.(任何⼈都可以看到⾝份证的信息, jwt 也是)

JWT令牌生成和校验
  1. 引⼊JWT令牌的依赖
<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is
preferred -->
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency> 
  1. 使⽤Jar包中提供的API来完成JWT令牌的⽣成和校验
@Slf4j
public class JwtUtils {
    //三十分钟过期时间
    public static final long expiration = 30 * 60 * 1000;
    public static final String secretString = "dVnsmy+SIX6pNptQdeclDSJ26EMSPEIhvZYKBTTug4k=";
    public static final Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));

    public static String genToken(Map<String,Object> claim){
        String token = Jwts.builder()
                .setClaims(claim)//设置载荷信息
                .setExpiration(new Date(System.currentTimeMillis() + expiration))//设置过期时间
                .signWith(key)//设置密钥
                .compact();
        return token;
    }

    public static Claims parseToken(String token){
        if(token == null){
            return null;
        }
        //构建解析器
        JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
        Claims claims = null;
        try {
            claims = build.parseClaimsJws(token).getBody();
        }catch (Exception e){
            log.error("解析token失败 token:{}",token);
        }
        return claims;
    }

    public static Integer getUserIdFromToken(String token){
        Claims claims = parseToken(token);
        if(claims == null){
            return null;
        }
        return (Integer) claims.get("id");
    }
}
实现用户登录

学习令牌的使⽤之后, 接下来我们通过令牌来完成⽤⼾的登录

  1. 登陆⻚⾯把⽤⼾名密码提交给服务器.
  2. 服务器端验证⽤⼾名密码是否正确, 如果正确, 服务器⽣成令牌, 下发给客⼾端.
  3. 客⼾端把令牌存储起来(⽐如Cookie, local storage等), 后续请求时, 把token发给服务器
  4. 服务器对令牌进⾏校验, 如果令牌正确, 进⾏下⼀步操作
约定前后端交互接⼝

[请求]/user/login username=test&password=123
[响应]{
“code”: 200,
“msg”: “”,
“data”: "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJ6aGFuZ3NhbiIsImlhdC
}
//验证成功, 返回token, 验证失败返回 “”

实现服务器代码

创建UserController

@RequestMapping("/login")
    public Result login(String userName, String password){
        /**
         * 参数校验
         * 密码校验
         * 生成token 并返回
         */
        if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){
            return Result.fail("用户名或密码为空");
        }
        //获取数据库中的密码
        UserInfo userInfo = userService.queryByName(userName);
        if(userInfo == null || userInfo.getId() < 0){
            return Result.fail("用户不存在");
        }
//        if(!password.equals(userInfo.getPassword())){
//            return Result.fail("密码错误!");
//        }
        boolean isOk = SecurityUtils.verify(password,userInfo.getPassword());
        if(!isOk){
            return Result.fail("密码错误!");
        }
        //生成token
        Map<String,Object> claim = new HashMap<>();
        claim.put("id",userInfo.getId());
        claim.put("name",userInfo.getUserName());
        String token = JwtUtils.genToken(claim);
        return Result.success(token);
    }

实现强制要求登录

当⽤⼾访问 博客列表⻚ 和 博客详情⻚ 时, 如果⽤⼾当前尚未登陆, 就⾃动跳转到登陆⻚⾯.我们可以采⽤拦截器来完成, token通常由前端放在header中, 我们从header中获取token, 并校验token是否合法

添加拦截器
@Slf4j
@Configuration
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //此处进行用户强制登陆校验
        //1.从header中获取token
        //验证token(约定前端发送请求时 header之中有key为user_token 值为token的键值对)
        String token = request.getHeader("user_token");
        log.info("从header中获取token,token:{}",token);
        Claims claims = JwtUtils.parseToken(token);
        if(claims == null){
            //token是不合法的
            response.setStatus(401);
            return false;
        }
        return true;
    }
}

@Configuration
public class WebConfig implements WebMvcConfigurer {
    public static final List<String> excludepath = Arrays.asList(
            "/user/login",
            "/**/*.html",
            "/pic/**",
            "/js/**",
            "/css/**",
            "/blog-editormd/**"
    );
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor).addPathPatterns("/**")
                .excludePathPatterns(excludepath);
    }
}

实现显示用户信息

约定前后端交互接口

在博客列表页 获取当前文章作者的用户信息

请求]
/user/getUserInfo
[响应]
{
userId: 1,
username: test

}

在博客详情页 获取当前文章作者的用户信息

[请求]
/user/getAuthorInfo?blogId=1
[响应]
{
userId: 1,
username: test
}

实现服务器代码

在UserController添加代码

//获取登录用户的信息
    @RequestMapping("/getUserInfo")
    public UserInfo userInfo(HttpServletRequest request){
        //1.从token 中获取用户Id
        //2.根据用户Id 获取用户信息
        String token = request.getHeader("user_token");
        Integer userId = JwtUtils.getUserIdFromToken(token);
        if(userId == null){
            return null;
        }
        return userService.queryById(userId);
    }

    //获取当前作者的信息
    @RequestMapping("/getAuthorInfo")
    public UserInfo getAuthorInfo(Integer blogId){
        if(blogId == null && blogId < 0){
            return null;
        }
        return userService.getAuthorInfo(blogId);
    }

UserService

 public UserInfo getAuthorInfo(Integer blogId) {
        //根据blogId获取userId
        //根据userId获取userInfo
        BlogInfo blogInfo = blogInfoMapper.queryById(blogId);
        if(blogInfo == null){
            return null;
        }
        return userInfoMapper.queryById(blogInfo.getUserId());
    }

实现发布博客

约定前后端交互接口

[请求]
/blog/add
title=标题&content=正⽂…
[响应]
{
“code”: 200,
“msg”: “”,
“data”: true
}
//true 成功
//false 失败

实现服务器代码

修改BlogController代码

@RequestMapping("/add")
    public Result publishBlog(String title, String content, HttpServletRequest request) {
        //从token中获取userId
        String token = request.getHeader("user_token");
        Integer userId = JwtUtils.getUserIdFromToken(token);
        if (userId == null || userId < 0) {
            return Result.fail("用户未登录", false);
        }
        //插入博客
        BlogInfo blogInfo = new BlogInfo();
        blogInfo.setUserId(userId);
        blogInfo.setTitle(title);
        blogInfo.setContent(content);
        blogService.insertBlog(blogInfo);
        return Result.success(true);
    }

实现删除/编辑博客

进⼊⽤⼾详情⻚时, 如果当前登陆⽤⼾正是⽂章作者, 则在导航栏中显⽰ [编辑] [删除] 按钮, ⽤⼾点击时则进⾏相应处理.

需要实现两件事:

• 判定当前博客详情⻚中是否要显⽰[编辑] [删除] 按钮
• 实现编辑/删除逻辑.

删除采⽤逻辑删除, 所以和编辑其实为同⼀个接⼝.

约定前后端交互接口
  1. 判定是否要显⽰[编辑] [删除] 按钮
    修改之前的 获取博客 信息的接⼝, 在响应中加上⼀个字段.
    获取博客详情

请求]
/blog/getBlogDetail?blogId=1
[响应]
{
“code”: 200,
“msg”: “”,
“data”: {
“id”: 1,
“title”: “第⼀篇博客”,
“content”: “111我是博客正⽂我是博客正⽂我是博客正⽂”,
“userId”: 1,
“loginUser”: 1
“deleteFlag”: 0,
“createTime”: “2023-10-21 16:56:57”,
“updateTime”: “2023-10-21T08:56:57.000+00:00”
}
}

修改博客

[请求]
/blog/update
[参数]
Content-Type: application/json
{
“title”: “测试修改⽂章”,
“content”: “在这⾥写下⼀篇博客”,
“blogId”: “4”
}
[响应]
{
“code”: 200,
“msg”: “”,
“data”: true
}

删除博客

[请求]
/blog/delete?blogId=1
[响应]
{
“code”: 200,
“msg”: “”,
“data”: true
}

实现服务器代码

给blog类新增一个字段

@Data
public class BlogInfo {
    private Integer id;
    private String title;
    private String content;
    private Integer userId;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
    private boolean isLoginUser;

    public String getCreateTime() {
        return DateUtils.formatDate(createTime);
    }
}

修改BlogControl

//获取博客详情
    @RequestMapping("/getBlogDetail")
    public BlogInfo getBlogDetail(Integer blogId, HttpServletRequest request) {
        BlogInfo blogInfo = blogService.getBlogDetail(blogId);
        //判断作者是否是登录用户
        String token = request.getHeader("user_token");
        Integer userId = JwtUtils.getUserIdFromToken(token);
        if (userId != null && userId == blogInfo.getUserId()) {
            blogInfo.setLoginUser(true);
        }
        return blogInfo;
    }

添加update/delete方法

/**
     * 编辑博客
     */
    @RequestMapping("/update")
    public boolean updateBlog(Integer id, String title, String content){
        BlogInfo blogInfo = new BlogInfo();
        blogInfo.setTitle(title);
        blogInfo.setContent(content);
        //根据博客ID去更新数据
        blogInfo.setId(id);
        blogService.updateBlog(blogInfo);
        return true;
    }

    /**
     * 删除博客
     */
    @RequestMapping("/delete")
    public boolean deleteBlog(Integer blogId){
        blogService.delete(blogId);
        return true;
    }

加密/加盐

加密介绍
在MySQL数据库中, 我们常常需要对密码, ⾝份证号, ⼿机号等敏感信息进⾏加密, 以保证数据的安全性.如果使⽤明⽂存储, 当⿊客⼊侵了数据库时, 就可以轻松获取到⽤⼾的相关信息, 从⽽对⽤⼾或者企业造成信息泄漏或者财产损失.⽬前我们⽤⼾的密码还是明⽂设置的, 为了保护⽤⼾的密码信息, 我们需要对密码进⾏加密
密码算法分类
密码算法主要分为三类: 对称密码算法, ⾮对称密码算法, 摘要算法

  1. 对称密码算法 是指加密秘钥和解密秘钥相同的密码算法. 常⻅的对称密码算法有: AES, DES, 3DES, RC4, RC5, RC6 等.
  2. ⾮对称密码算法 是指加密秘钥和解密秘钥不同的密码算法. 该算法使⽤⼀个秘钥进⾏加密, ⽤另外⼀ 个秘钥进⾏解密. ◦ 加密秘钥可以公开,⼜称为 公钥 ◦ 解密秘钥必须保密,⼜称为 私钥 常⻅的⾮对称密码算法有:RSA,DSA,ECDSA, ECC 等
  3. 摘要算法 是指把任意⻓度的输⼊消息数据转化为固定⻓度的输出数据的⼀种密码算法. 摘要算法是 不可逆的, 也就是⽆法解密. 通常⽤来检验数据的完整性的重要技术, 即对数据进⾏哈希计算然后⽐ 较摘要值, 判断是否⼀致. 常⻅的摘要算法有: MD5,SHA系列(SHA1, SHA2等), CRC(CRC8, CRC16, CRC32)

我们在博客系统中使用摘要算法中的MD5算法来加密

问题: 虽然经过MD5加密后的密⽂⽆法解密, 但由于相同的密码经过MD5哈希之后的密⽂是相同的, 当存储⽤⼾密码的数据库泄露后, 攻击者会很容易便能找到相同密码的⽤⼾, 从⽽降低了破解密码的难度. 因此, 在对⽤⼾密码进⾏加密时,需要考虑对密码进⾏包装, 即使是相同的密码, 也保存为不同的密⽂. 即使⽤⼾输⼊的是弱密码, 也考虑进⾏增强, 从⽽增加密码被攻破的难度.

解决⽅案: 采⽤为⼀个密码拼接⼀个随机字符来进⾏加密, 这个随机字符我们称之为"盐". 假如有⼀个加盐后的加密串,⿊客通过⼀定⼿段这个加密串, 他拿到的明⽂并不是我们加密前的字符串, ⽽是加密前的字符串和盐组合的字符串, 这样相对来说⼜增加了字符串的安全性.
在这里插入图片描述

解密流程: MD5是不可逆的, 通常采⽤"判断哈希值是否⼀致"来判断密码是否正确.如果⽤⼾输⼊的密码, 和盐值⼀起拼接后的字符串经过加密算法, 得到的密⽂相同, 我们就认为密码正确(密⽂相同, 盐值相同, 推测明⽂相同)
在这里插入图片描述

写加密/解密工具类
@Slf4j
public class SecurityUtils {
    /**
     * 加密
     * @param password 明文密码
     * @return 盐值+密文
     */
    public static String encry(String password){
        //生成随机盐值
        String salt = UUID.randomUUID().toString().replace("-","");
        System.out.println(salt);
        //加密 盐值+明文
        String securityPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes());
        //数据库中存储 盐值+明文
        return salt+securityPassword;
    }

    /**
     * 校验
     * @param inputPassword 输入的密码
     * @param sqlPassword 数据库中取到的密码
     * @return 密码是否正确
     */
    public static boolean verify(String inputPassword, String sqlPassword){
        //取出盐值
        if(sqlPassword == null || sqlPassword.length() != 64){
            return false;
        }
        String salt = sqlPassword.substring(0,32);
        //得到密文
        String securityPassword = DigestUtils.md5DigestAsHex((salt+inputPassword).getBytes());
        log.info(salt + securityPassword);
        return (salt + securityPassword).equals(sqlPassword);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1656104.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

详解如何把文件或应用开机自启动

相信很多人都想把某些开机都要用的软件或文件打开&#xff0c;但不知道如何操作&#xff0c;或不知道可以手动设置开机自启动这一操作&#xff0c;下面为大家讲解实现思路&#xff1a; 1.首先打开winR打开运行框&#xff0c;在里面弄输入&#xff1a;shell:startup 2.回车进入文…

Win10/11共享文件夹,访问提示需要输入用户名密码

Win10/11共享文件夹&#xff0c;访问提示需要输入用户名密码 问题 已经关闭了密码保护共享&#xff0c;但是局域网其他电脑访问该文件夹&#xff0c;提示需要输入用户名和密码 解决方法 操作步骤 1.按WINR键打开运行&#xff0c;输入gpedit.msc打开本地组策略编辑器 2.按如…

Hive SQL-DML-Load加载数据

Hive SQL-DML-Load加载数据 在 Hive 中&#xff0c;可以使用 SQL DML&#xff08;Data Manipulation Language&#xff09;语句中的 LOAD 命令来加载数据到表中。LOAD 命令用于将本地文件系统或 HDFS&#xff08;Hadoop 分布式文件系统&#xff09;中的数据加载到 Hive 表中。 …

CSAPP | Floating Point

CSAPP | Floating Point b i b_i bi​ b i − 1 b_{i-1} bi−1​ … b 2 b_2 b2​ b 1 b_1 b1​ b 0 b_0 b0​ b − 1 b_{-1} b−1​ b − 2 b_{-2} b−2​ b − 3 b_{-3} b−3​ … b − j b_{-j} b−j​ S ∑ k − j i b k 2 k S\sum_{k-j}^{i}b_k\times2^k S∑k…

目标检测算法YOLOv6简介

YOLOv6由Chuyi Li等人于2022年提出&#xff0c;论文名为&#xff1a;《YOLOv6: A Single-Stage Object Detection Framework for Industrial Applications》&#xff0c;论文见&#xff1a;https://arxiv.org/pdf/2209.02976 &#xff0c;项目网页&#xff1a;https://github.c…

67万英语单词学习词典ACCESS\EXCEL数据库

这似乎是最多记录的英语单词学习词典&#xff0c;包含复数、过去分词等形式的单词。是一个针对想考级的人员辅助背单词学英语必备的数据&#xff0c;具体请自行查阅以下的相关截图。 有了数据才能想方设法做好产品&#xff0c;结合权威的记忆理论&#xff0c;充分调动用户的眼…

GD32F103RCT6/GD32F303RCT6(9)高级定时器互补PWM波输出实验

本文章基于兆易创新GD32 MCU所提供的2.2.4版本库函数开发 后续项目主要在下面该专栏中发布&#xff1a; 手把手教你嵌入式国产化_不及你的温柔的博客-CSDN博客 感兴趣的点个关注收藏一下吧! 电机驱动开发可以跳转&#xff1a; 手把手教你嵌入式国产化-实战项目-无刷电机驱动&am…

Linux网络编程(四) 同时处理一个端口的UDP与TCP连接

从bind系统调用的参数来看&#xff0c;一个socket只能与一个socket地址绑定&#xff0c;即一个socket只能用来监听一个端口。因此&#xff0c;服务器如果要同时监听多个端口&#xff0c;就必须创建多个socket&#xff0c;并将它们分别绑定到各个端口上。这样一来&#xff0c;服…

Github下载的项目使用

根据该视频整理GitHub上的项目要怎么运行&#xff1f;一个视频教会你&#xff01;_哔哩哔哩_bilibili 方法一&#xff1a;从release中找。 方法二&#xff1a; 从官网中找&#xff08;位于右上角&#xff09; 方法三&#xff1a;看readme&#xff08;从readme中搜索以下词汇&a…

Milvus Cloud 的RAG 的广泛应用及其独特优势

一个典型的 RAG 框架可以分为检索器(Retriever)和生成器(Generator)两块,检索过程包括为数据(如 Documents)做切分、嵌入向量(Embedding)、并构建索引(Chunks Vectors),再通过向量检索以召回相关结果,而生成过程则是利用基于检索结果(Context)增强的 Prompt 来激…

【Qt 开发基础体系】字符串类应用和常用的数据类型

文章目录 1. Qt 字符串类应用1.1 操作字符串1.2 QString::append()函数1.3 QString::sprintf()函数1.4 QString::arg()函数 2. 查询字符串2.1 函数 QString::startsWith()2.2 函数 QString::contains()2.3 函数 QString::toInt()2.4 函数 QString::compare()2.5 将 QString 转换…

Github 50k star!吴恩达联合OpenAi共同编写<面向开发者的LLM入门教程> PDF推荐!

今天给大家推荐一本由吴恩达和OpenAI团队共同编写的关于大型语言模型&#xff08;LLM&#xff09;的权威教程<面向开发者的LLM入门教程>&#xff01;&#xff0c;在Github上已经高达50k star了&#xff0c;这含金量不用多说&#xff0c;在这里给大家强烈推荐一波&#xf…

电脑文件怎么加密?电脑涉密文件加密方法

文件加密是保护电脑涉密文件的重要方法&#xff0c;可以有效避免文件泄露风险。那么&#xff0c;电脑涉密文件该怎么加密呢&#xff1f;下面我们就来了解一下吧。 超级加密3000 在加密电脑涉密文件时&#xff0c;首先需要考虑的就是文件加密的安全性。因此&#xff0c;我们可以…

2024挂耳式耳机怎么选?5款高性价比开放式耳机推荐榜

近年来&#xff0c;开放式耳机受到了越来越多人的关注&#xff0c;特别是对于运动爱好者来说&#xff0c;在运动的过程中&#xff0c;传统的有线耳机不适合户外运动&#xff0c;不仅佩戴不稳&#xff0c;线还容易缠绕&#xff0c;而普通的蓝牙耳机长时间佩戴会感觉耳朵不适。在…

基于短时傅里叶变换域的一维信号邻域降噪方法(MATLAB)

基于傅里叶变换的信号频域表示及能量频域分布揭示了信号在频域的特征&#xff0c;但傅里叶变换是一种整体变换&#xff0c;只能了解信号的全局特性&#xff0c;不能有效检测信号频率随时间的变化情况。只有把时域和频域结合起来才能更好地反映非平稳信号的特征。时频分析的基本…

机器学习 - 梯度下降算法推导

要逐步推导多变量线性回归的梯度计算过程&#xff0c;我们首先需要明确模型和损失函数的形式&#xff0c;然后逐步求解每个参数的偏导数。这是梯度下降算法核心部分&#xff0c;因为这些偏导数将指导我们如何更新每个参数以最小化损失函数。 模型和损失函数 考虑一个多变量线…

Linux:进程通信(三)信号的捕捉

目录 一、信号捕捉函数 1、signal函数 2、sigaction函数 二、用户态与内核态 1、用户态 2、内核态 用户态与内核态转换 三、volatile关键字 四、SIGCHLD信号 一、信号捕捉函数 1、signal函数 signal函数是C语言标准库中的一个函数&#xff0c;用于处理Unix/Linux系…

web自动化系列-selenium执行js脚本|截图|识别验证码(十五)

1.执行脚本 如果你定位的元素通过各种方法都无法完成页面操作&#xff0c;最后的方法就是通过操作js脚本来完成 。 在selenium中提供了一个方法 &#xff0c;这个可以方法可以自行JS的脚本 。具体为&#xff1a; execute_script(js脚本) &#xff1a; js脚本代表要执行的脚本…

OC类与对象(下)

OC类与对象&#xff08;下&#xff09; 文章目录 OC类与对象&#xff08;下&#xff09;不是包装类的NSValue 和NSNumber处理对象打印对象和description方法 和 isEqual方法 类别类别语法部分利用类别进行模块化设计使用类别来调用私有方法 类的扩展协议与委托规范&#xff0c;…

BLIP和BLIP2 论文讲解

文章目录 BLIPIntroductionMethod模型架构预训练目标字幕和过滤&#xff08;Capfilt&#xff09; BLIP2IntroductionMethod模型结构Q-Former预训练第一阶段Q-Former预训练第二阶段 BLIP 论文&#xff1a; 《BLIP: Bootstrapping Language-Image Pre-training for Unified Visio…