1.使用手工加盐算法代替明文,提高用户隐私安全性。
2.登录功能的验证使用了拦截器。
3.支持分布式 Session存储和缓存都放到了Redis里面。
先删除项目中无用的文件和目录
引入前端页面(resources-static)
添加项目常用配置(在resources下创建一个application.yml,并删除application.properties)
在我的gitee的代码片段中复制SSM常用配置
# 配置数据库的连接字符串
spring:
datasource:
url: jdbc:mysql://127.0.0.1/mycnblog?characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 设置 Mybatis 的 xml 保存路径
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml
configuration: # 配置打印 MyBatis 执行的 SQL
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 配置打印 MyBatis 执行的 SQL
logging:
level:
com:
example:
demo: debug
复制后注意!看我们所使用的数据库名字是否为mycnblog 密码是否正确
-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;
-- 使用数据数据
use mycnblog;
-- 创建表[用户表]
drop table if exists userinfo;
create table userinfo(
id int primary key auto_increment,
username varchar(100) not null,
password varchar(32) not null,
photo varchar(500) default '',
createtime datetime default current_timestamp,
updatetime datetime default current_timestamp,
`state` int default 1
) default charset 'utf8mb4';
-- 创建文章表
drop table if exists articleinfo;
create table articleinfo(
id int primary key auto_increment,
title varchar(100) not null,
content text not null,
createtime datetime default current_timestamp,
updatetime datetime default current_timestamp,
uid int not null,
rcount int not null default 1,
`state` int default 1
)default charset 'utf8mb4';
-- 添加一个用户信息
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);
-- 文章添加测试数据
insert into articleinfo(title,content,uid)
values('Java','Java正文',1);
先建这几个 后面再有需要再建
先在demo下建一个公共包(common)公共类(AjaxResult)
前端和后端使用ajax交互,返回统一的结果 :AjaxResult
//AjaxResult.java
package com.example.demo.common;
import lombok.Data;
import java.io.Serializable;
/**
* 统一数据格式返回
* implements Serializable 实现序列化
*/
@Data
public class AjaxResult implements Serializable {
//状态码
private Integer code;
//状态码的描述信息
private String msg;
//返回的数据 不知道返回的数据类型是啥 所以使用Object
private Object data;
/**
* 操作成功返回的结果
*/
public static AjaxResult success(Object data){
AjaxResult result = new AjaxResult();
result.setCode(200);
result.setMsg("");
result.setData(data);
return result;
}
//进行方法的重载 自定义code
public static AjaxResult success(int code, Object data){
AjaxResult result = new AjaxResult();
result.setCode(code);
result.setMsg("");
result.setData(data);
return result;
}
public static AjaxResult success(int code, String msg, Object data){
AjaxResult result = new AjaxResult();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
/**
* 返回失败的结果
*/
public static AjaxResult fail(int code, String msg){
AjaxResult result = new AjaxResult();
result.setCode(code);
result.setMsg(msg);
result.setData(null);
return result;
}
public static AjaxResult fail(int code, String msg, Object data){
AjaxResult result = new AjaxResult();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
}
//ResponseAdvice.java
package com.example.demo.config;
import com.example.demo.common.AjaxResult;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* 实现统一数据返回的保底类
* 说明:在返回数据之前,检测数据的类型是否为统一的对象;如果不是,封装成统一的对象
*/
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
private ObjectMapper objectMapper;
/**
* 开关,如果是true的时候,才能调用beforeBodyWrite
*/
@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 AjaxResult) return body;
if(body instanceof String){
//先把body封装成统一的对象,因其为String类型,再把它转为json类型
return objectMapper.writeValueAsString(AjaxResult.success(body));
}
return AjaxResult.success(body);
}
}
1.非空校验
2.判断两次密码是否一致
3.ajax提交请求
#在head中增加jquery
#在提交按钮中添加onclick
前端页面修改完代码不生效,此时极大的概率是缓存
1.先清空idea的缓存,删除项目目录下的target文件夹,然后重启项目
2.强制刷新浏览器
3.如果前两步还没有解决缓存问题,那么尝试给url添加上一个没有意义的参数 比如?v=1
后端代码写的时候注意顺序!一个调用一个 因此我们先在UserMapper.java/ArticleMapper.java中写,再在对应的*Mapper.xml中写SQL语句,再在*Service.java中写这个方法,最后再在*Controller.java中写具体的实现!后面的后端代码基本都是这个顺序写
//demo-entity-UserInfo.java
package com.example.demo.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class UserInfo {
//Integer 比int的兼容性更好
private Integer id;
private String username;
private String password;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private Integer state;
}
//mapper-UserMapper.java
package com.example.demo.mapper;
import com.example.demo.entity.UserInfo;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
//注册
int reg(UserInfo userInfo);
}
//resources-mapper-UserMapper.xml
insert into userinfo(username,password) values(#{username},#{password})
//service-UserService.java
package com.example.demo.service;
import com.example.demo.entity.UserInfo;
import com.example.demo.mapper.UserMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public int reg(UserInfo userInfo) {
return userMapper.reg(userInfo);
}
}
//controller-UserController.java
package com.example.demo.controller;
import com.example.demo.common.AjaxResult;
import com.example.demo.entity.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/reg")
public AjaxResult reg(UserInfo userInfo) {
//非空校验和参数有效性校验
if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())||
!StringUtils.hasLength(userInfo.getPassword())) {
return AjaxResult.fail(-1,"非法参数");
}
return AjaxResult.success(userService.reg(userInfo));
}
}
#在head中增加jquery
#在提交按钮中添加onclick
//在UserMapper.java中加入getUserByName方法
package com.example.demo.mapper;
import com.example.demo.entity.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface UserMapper {
//注册
int reg(UserInfo userInfo);
/**
* 登陆 根据用户查询userinfo对象
* 说明:只传username 在UserController里进行password的比对
*/
UserInfo getUserByName(@Param("username") String username);
}
#在UserMapper.xml中添加select操作
insert into userinfo(username,password) values(#{username},#{password})
//在UserService中加入getUserByName()方法,并返回username
package com.example.demo.service;
import com.example.demo.entity.UserInfo;
import com.example.demo.mapper.UserMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public int reg(UserInfo userInfo) {
return userMapper.reg(userInfo);
}
public UserInfo getUserByName(String username){
return userMapper.getUserByName(username);
}
}
//在UserController中添加login方法,并传入用户名和密码
在UserController中的login方法里 将用户存储到session中,不然之后的删除博客等操作无法进行
@RequestMapping("/login")
public AjaxResult login(HttpServletRequest request, String username, String password) {
//1.非空校验
if(!StringUtils.hasLength(username)|| !StringUtils.hasLength(password)) {
return AjaxResult.fail(-1,"非法请求");
}
//2.查询数据库
UserInfo userInfo = userService.getUserByName(username);
if(userInfo != null && userInfo.getId() > 0){
//有效的用户名 两个密码是否相同
if(password.equals(userInfo.getPassword())){
//登陆成功
//将用户存储到session中
HttpSession session = request.getSession();
session.setAttribute(AppVariable.USER_SESSION_KEY,userInfo);
userInfo.setPassword("");//返回前端之前,隐藏敏感(密码)信息
return AjaxResult.success(userInfo);
}
}
return AjaxResult.success(0,null);
}
//在common下建立一个全局变量类 AppVariable.java
public class AppVariable {
public static final String USER_SESSION_KEY = "USER_SESSION_KEY";
}
先写一个拦截器LoginInterceptor,这个拦截器要实现HandlerInterceptor(拦截管理器)的接口,里面去实现preHandle的方法
然后要把prehandle加到全项目的全局配置文件中
//config-LoginInterceptor.java
package com.example.demo.config;
import com.example.demo.common.AppVariable;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* 登录拦截器
*/
public class LoginInterceptor implements HandlerInterceptor {
/**
* true -> 用户已登录
* false ->用户未登录
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//如果有与当前的request相关联的HttpSession,则返回该HttpSession;如果没有则返回null;
HttpSession session =request.getSession(false);
if(session!=null &&session.getAttribute(AppVariable.USER_SESSION_KEY) != null){
//用户已登录
return true;
}
//调整到登陆页面
response.sendRedirect("/login.html");
return false;
}
}
//在系统的配置文件中配置拦截规则
//在config下建AppConfig.java 实现一个WebMvcConfigurer的配置文件
package com.example.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/css/**")
.excludePathPatterns("/editor.md/**")
.excludePathPatterns("/img/**")
.excludePathPatterns("/js/**")
.excludePathPatterns("/login.html")
.excludePathPatterns("/reg.html")
.excludePathPatterns("/blog_list.html")
.excludePathPatterns("/blog_content.html")
.excludePathPatterns("/user/login")
.excludePathPatterns("/user/reg");
}
}
注意:此时的session是借助cookie储存到cookie中的 后期会部署到Redis上
#在head中增加jquery
更改这两处 实现动态赋值
//此处对应博客列表articleinfo 因此在demo-mapper下建ArticleMapper.java
package com.example.demo.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface ArticleMapper {
int getArtCountByUid(@Param("uid") Integer uid);
}
#因此在resources-mapper下建ArticleMapper.xml
此时生成一个单元测试 进行测试
package com.example.demo.mapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class ArticleMapperTest {
@Resource
private ArticleMapper articleMapper;
@Test
void getArtCountByUid() {
int result = articleMapper.getArtCountByUid(1);
System.out.println("文章总数: "+result);
}
}
可以看到这里文章总数为1 因此ArticleMapper没有问题,接着写
//demo-controller-UserController.java
@Autowired
private ArticleService articleService;
此时返回的数据不再是基础数据类型,而是articlecount,因此新建vo-UserinfoVO.java 展示的一个对象
package com.example.demo.vo;
import com.example.demo.entity.UserInfo;
import lombok.Data;
@Data
public class UserinfoVO extends UserInfo {
private Integer artCount;//此人发表的文章总数
}
接着在demo-controller-UserController里写
1.得到当前登录用户(从session中获取 )
2.得到用户发表的文章总数
但是此处的session不是只在我的博客列表页进行获取
//在demo-common下建UserSessionUtils.java
package com.example.demo.common;
import com.example.demo.entity.UserInfo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* 当前登陆用户相关的操作
*/
public class UserSessionUtils {
/**
* 得到当前的登录用户
* @param request
* @return
*/
public static UserInfo getUser(HttpServletRequest request){
HttpSession session = request.getSession(false);
if(session != null &&
session.getAttribute(AppVariable.USER_SESSION_KEY) != null){
//说明用户已正常登录
return (UserInfo) session.getAttribute(AppVariable.USER_SESSION_KEY);
}
return null;
}
}
//demo-controller-UserController.java
@RequestMapping("/showinfo")
public AjaxResult showInfo(HttpServletRequest request){
UserinfoVO userinfoVO = new UserinfoVO();
//1.得到当前登录用户(从session中获取 )
UserInfo userInfo = UserSessionUtils.getUser(request);
if(userInfo ==null){
return AjaxResult.fail(-1,"非法请求");
}
//Spring提供的深克隆方法
BeanUtils.copyProperties(userInfo,userinfoVO);
//2.得到用户发表的文章总数
userinfoVO.setArtCount(articleService.getArtCountByUid(userInfo.getId()));
return AjaxResult.success(userinfoVO);
}
获取我的文章列表要不要传uid给后端?
不需要传递任何参数,并且是一定不能传参的
//建一个实体类 Articleinfo.java
package com.example.demo.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class Articleinfo {
private Integer id;
private String title;
private String content;
private Data createtime;
private Data updatetime;
private Integer uid;
private Integer rcount;
private Integer state;
}
//在AriticleMapper.java里添加getMyList方法
package com.example.demo.mapper;
import com.example.demo.entity.Articleinfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface ArticleMapper {
int getArtCountByUid(@Param("uid") Integer uid);
List getMyList(@Param("uid") Integer uid);
}
//在AriticleMapper.xml里添加
//ArticleService.java
public class ArticleService {
@Resource
private ArticleMapper articleMapper;
public int getArtCountByUid(Integer uid){
return articleMapper.getArtCountByUid(uid);
}
public List getMyList(Integer uid) {
return articleMapper.getMyList(uid);
}
}
//controller-ArticleController.java
package com.example.demo.controller;
import com.example.demo.common.AjaxResult;
import com.example.demo.common.UserSessionUtils;
import com.example.demo.entity.Articleinfo;
import com.example.demo.entity.UserInfo;
import com.example.demo.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@RestController
@RequestMapping("/art")
public class ArticleController {
@Autowired
private ArticleService articleService;
@RequestMapping("/mylist")
public AjaxResult getMyList(HttpServletRequest request){
UserInfo userInfo = UserSessionUtils.getUser(request);
if(userInfo == null){
return AjaxResult.fail(-1,"非法请求");
}
List list = articleService.getMyList(userInfo.getId());
return AjaxResult.success(list);
}
}
1.通过配置文件,设置全局的时间格式化
#在application.yml里的spring中设置jaskson
# 配置数据库的连接字符串
spring:
jackson:
jackson:
date-format: 'yyyy-MM-dd HH:mm:ss'
time-zone: 'GMT+8'
datasource:
url: jdbc:mysql://127.0.0.1/mycnblog?characterEncoding=utf8
username: root
password: "zy19991227"
driver-class-name: com.mysql.cj.jdbc.Driver
注意事项:此配置对LocalDateTime/LocalDate,需要使用Date数据类型
2.使用@JsonFormat注解
@Data
public class Articleinfo {
private Integer id;
private String title;
private String content;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createtime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime updatetime;
private Integer uid;
private Integer rcount;
private Integer state;
}
//ArticleMapper.java中加入del方法
@Mapper
public interface ArticleMapper {
int getArtCountByUid(@Param("uid") Integer uid);
List getMyList(@Param("uid") Integer uid);
int del(@Param("id") Integer id,@Param("uid") Integer uid);
}
//ArticleMapper.xml加入sql语句
delete from articleinfo where id=#{id} and uid=#{uid}
//ArticleService.java中加入del方法
public int del(Integer id,Integer uid){
return articleMapper.del(id,uid);
}
//UserController.java
@RequestMapping("/logout")
public AjaxResult logout(HttpSession session){
session.removeAttribute(AppVariable.USER_SESSION_KEY);
return AjaxResult.success(1);
}
同时登陆zhangsan和admin用户,zhangsan用户点击注销
确认是否注销成功? 看注销后的张三能否删除admin用户的文章 不能
实现思路:
1.从url中得到文章id
2.从后端查询当前文章的详情信息(以及uid)
3.根据上一步查询的uid查询用户的信息
4.请求后端接口实现阅读量+1
//在blog_content.html中修改
//ArticleMapper.java中加入getDetail方法
Articleinfo getDetail(@Param("id") Integer id);
#ArticleMapper.xml中加入SQL语句
//ArticleService.java中加入getDetail方法
public Articleinfo getDetail(Integer id) {
return articleMapper.getDetail(id);
}
!!!注意此处需要在AppConfig.java拦截器中开放一个接口
//因为我们还有个所有人的博客列表页,从那里进入博客详情页也是ok的
.excludePathPatterns("/art/detail")
//根据id获取用户 UserMapper.java
UserInfo getUserById(@Param("id") Integer id);
#UserMapper.xml中加入SQL语句
//UserService.java中加入getUserById方法
public UserInfo getUserById(Integer id){
return userMapper.getUserById(id);
}
//UserController.java中加入getUserById方法
@RequestMapping("/getuserbyid")
public AjaxResult getUserById(Integer id){
if(id==null || id <= 0){
//无效参数
return AjaxResult.fail(-1,"非法参数");
}
UserInfo userinfo = userService.getUserById(id);
if(userinfo == null || userinfo.getId() <=0){
//无效参数
return AjaxResult.fail(-1,"非法参数");
}
//取出userinfo中的敏感信息 密码
userinfo.setPassword("");
UserinfoVO userinfoVO = new UserinfoVO();
BeanUtils.copyProperties(userinfo,userinfoVO);
//查询当前用户发表的文章数
userinfoVO.setArtCount(articleService.getArtCountByUid(id));
return AjaxResult.success(userinfoVO);
}
!!!注意此处需要在AppConfig.java拦截器中开放一个接口
因为文章列表页针对所有由用户开放
.excludePathPatterns("/user/getuserbyid")
实现思路1:
先查询文章的阅读量,然后再+1设置到数据库中
实现思路2:
将两步合二为一,update article set rcount=rcount+1 where id =xxx
//ArticleMapper.java中定义incrRCount
int incrRCount(@Param("id") Integer id);
#ArticleMapper.xml中更新SQL语句
update articleinfo set rcount=rcount+1 where id=#{id}
//ArticleService.java中定义incrRCount方法
public int incrRCount(Integer id){
return articleMapper.incrRCount(id);
}
//ArticleController.java中定义incrRCount方法
@RequestMapping("/incr-rcount")
public AjaxResult incrRCount(Integer id){
if(id != null && id > 0){
return AjaxResult.success(articleService.incrRCount(id));
}
return AjaxResult.success(-1,"未知错误");
}
!!!注意此处需要在AppConfig.java拦截器中开放一个接口 否则出现302错误
.excludePathPatterns("/user/incr-rcount")
//blog_add.html
//ArticleMapper.java
int add(Articleinfo articleinfo);
#ArticleMapper.xml中添加SQL语句
insert into articleinfo(title,content,uid) values(#{title},#{content},#{uid})
//ArticleService.java中定义 add方法
public int add(Articleinfo articleinfo){
return articleMapper.add(articleinfo);
}
ArticleController.java中定义 add方法
@RequestMapping("/add")
public AjaxResult add(HttpServletRequest request, Articleinfo articleinfo){
//1.非空校验
if(articleinfo == null || !StringUtils.hasLength(articleinfo.getTitle()) ||
!StringUtils.hasLength(articleinfo.getContent())){
//非法参数
return AjaxResult.fail(-1,"非法参数");
}
//2.数据库添加操作
//a.得到当前登陆用户的id
UserInfo userInfo = UserSessionUtils.getUser(request);
if(userInfo == null || userInfo.getId() <= 0){
//无效的登录用户
return AjaxResult.fail(-2,"无效的登录用户");
}
articleinfo.setUid(userInfo.getId());
//b.添加数据库并返回结果
return AjaxResult.success(articleService.add(articleinfo));
}
1.得到文章id
2.去后端查询文章的详情信息并设置到页面上
3.进行文章修改操作(调用后台)
//ArticleMapper.java
int update(Articleinfo articleinfo);
#ArticleMapper.xml中添加SQL语句
update articleinfo set title=#{title},content=#{content},updatetime=#{updatetime}
where id=#{id} and uid=#{uid}
//ArticleService.java中定义 update方法
public int update(Articleinfo articleinfo){
return articleMapper.update(articleinfo);
}
//ArticleController.java中定义 update方法
@RequestMapping("/update")
public AjaxResult update(HttpServletRequest request, Articleinfo articleinfo){
//1.非空校验
if(articleinfo == null || !StringUtils.hasLength(articleinfo.getTitle()) ||
!StringUtils.hasLength(articleinfo.getContent())||
articleinfo.getId() ==null){
//非法参数
return AjaxResult.fail(-1,"非法参数");
}
//2.得到当前用户id
UserInfo userInfo = UserSessionUtils.getUser(request);
if(userInfo == null && userInfo.getId() == null){
//无效用户
return AjaxResult.fail(-2,"无效用户");
}
//核心代码!!(解决了修改文章归属人判定的问题)
articleinfo.setUid(userInfo.getId());
articleinfo.setUpdatetime(LocalDateTime.now());
return AjaxResult.success(articleService.update(articleinfo));
}
1.铭文不行,会泄露隐私
2.传统的MD5有规律可循,虽然不可逆,但是有规律可循,可以被暴力破解
【彩虹表:记录了几乎所有字符串的MD5】
3.加盐加密
随机,没有规律可言
每次调用方法的时候产生盐值(唯一)+密码 = 最终密码
最终密码还使用MD5加密,但是没关系,此时的MD5没有规律
使用UUID实现唯一密码
需要两个密码:
1.需要验证的密码(用户输入的密码)
2.最终加密的密码(存在数据库中的密码)
核心思想:得到盐值 这个盐值会放到最终密码的某个位置
最终密码格式(65位):盐值(32位)$加密后的密码(32位)
难以解密的原因:没有绝对安全的密码,解密者不知道加密的规律,就算你知道我的加盐的密码,但是构造彩虹表是需要时间的,一个密码就需要构建一个彩虹表,但是数据库中有成千上万的彩虹表,破解成本极高
验证密码的伪代码:
已知:用户输入的明文密码、此用户在数据库存储的最终密码=盐值$加密后的密码
1.从最终密码中得到盐值
2.将用户输入的明文密码+盐值进行加密操作=加密后的密码
3.使用 盐值$加密后的密码 生成数据库存储的密码
4.对比生成的最终密码和数据库最终的密码是否相等
如果相等,那么用户名和密码就是对的,反之密码输入错误
package com.example.demo.common;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.util.UUID;
public class PasswordUtils {
/**
* 1.加盐并生成密码
* @param password 明文密码
* @return 保存到数据库中的密码
*/
public static String encrypt(String password){
//a.产生盐值(UUID生成的36位,有4位-,因此删去后为32位)
String salt = UUID.randomUUID().toString().replace("-","");
//b.生成加盐之后的密码
String saltPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());
//c.生成最终密码(保存到数据库中的密码)【约定:盐值(32位)+$+加密后的密码(32位)】
String finalPassword = salt + "$" + saltPassword;
return finalPassword;
}
/**
* 2.生成加盐密码(步骤1的重载)
* @param password 明文
* @param salt 固定的盐值
* @return 最终密码
*/
public static String encrypt(String password,String salt){
//a.生成一个加盐后的密码
String saltPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());
//b.生成最终的密码【约定:盐值(32位)+$+加密后的密码(32位)】
String finalPassword = salt + "$" + saltPassword;
return finalPassword;
}
/**
* 3.验证密码
* @param inputPassword 用户输入的明文密码
* @param finalPassword 数据库保存的最终密码
* @return
*/
public static boolean check(String inputPassword,String finalPassword){
if(StringUtils.hasLength(inputPassword) && StringUtils.hasLength(finalPassword) &&
finalPassword.length() == 65){
//a.得到盐值
String salt = finalPassword.split("\\$")[0];
//b.使用之前加密的步骤,将明文密码和已经得到的盐值进行加密,生成最终的密码
String confirmPassword = PasswordUtils.encrypt(inputPassword,salt);
//c.对比两个最终密码是否相同
return confirmPassword.equals(finalPassword);
}
return false;
}
/* public static void main(String[] args) {
String password = "admin";
String finalPassword = PasswordUtils.encrypt(password);
System.out.println("加密" + finalPassword);
//对比
*//* String inputPassword = "12345";
System.out.println("对比 "+ inputPassword
+"是否等于" + password + "->" +PasswordUtils.check(inputPassword,finalPassword));*//*
String inputPassword2 = "admin";
System.out.println("对比 "+ inputPassword2
+"是否等于" + password + "->" +PasswordUtils.check(inputPassword2,finalPassword));
}*/
}
public static void main(String[] args) {
String password = "123456";
String finalPassword = PasswordUtils.encrypt(password);
System.out.println("加密" + finalPassword);
//对比
String inputPassword = "12345";
System.out.println("对比 "+ inputPassword
+"是否等于" + password + "->" +PasswordUtils.check(inputPassword,finalPassword));
String inputPassword2 = "123456";
System.out.println("对比 "+ inputPassword2
+"是否等于" + password + "->" +PasswordUtils.check(inputPassword2,finalPassword));
}
将数据库明文密码加密
update userinfo set password='12ad512af18f426e8e2248a17d8f2f92$40ffec117f74b4bf2a3fc0a849b7e0db';
在UserController.java的login方法中更改
if(password.equals(userInfo.getPassword()))
if(PasswordUtils.check(password,userInfo.getPassword())
在UserController.java的reg方法中更改
//密码加盐处理
userInfo.setPassword(PasswordUtils.encrypt(userInfo.getPassword()));
1.引入Spring Security 框架
org.springframework.boot
spring-boot-starter-security
只用其类库,不用自动注入等功能
2.排除Spring Security自动加载
启动类中加这样一句话
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
3.调用Spring Security加盐和验证
测试
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.util.DigestUtils;
@SpringBootTest
class DemoApplicationTests {
@Test
void contextLoads() {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String password = "123456";
String finalPassword = passwordEncoder.encode(password);
System.out.println("第1次加密:" + finalPassword);
System.out.println("第2次加密:" + passwordEncoder.encode(password));
System.out.println("第3次加密:" + passwordEncoder.encode(password));
// 验证
String inputPassword = "12345";
System.out.println("错误密码比对结果:" +
(passwordEncoder.matches(inputPassword, finalPassword)));
String inputPassword2 = "123456";
System.out.println("正确密码比对结果:" +
(passwordEncoder.matches(inputPassword2, finalPassword)));
}
/* public static void main(String[] args) {
String password = "123456";
String mdString = DigestUtils.md5DigestAsHex(password.getBytes());
System.out.println(mdString);
}*/
}
分页关键实现分析:
前端:当前页面【每页显示条数固定显示最大2条】
后端:当前页码、每页显示最大条数
公式:(当前页码n-1)*每页显示最大条数psize =offset
//ArticleMapper.java
List getListByPage(@Param("psize") Integer psize,
@Param("offsize") Integer offsize);
package com.example.demo.mapper;
import com.example.demo.entity.Articleinfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class ArticleMapperTest {
@Resource
private ArticleMapper articleMapper;
@Test
void getArtCountByUid() {
int result = articleMapper.getArtCountByUid(1);
System.out.println("文章总数: "+result);
}
@Test
void getListByPage() {
List list = articleMapper.getListByPage(3,0);
System.out.println(list);
System.out.println("-------------- ");
List list2 = articleMapper.getListByPage(3,3);
System.out.println(list);
}
}
//ArticleService.java中定义 getListByPage方法
public List getListByPage(Integer psize,Integer offsize){
return articleMapper.getListByPage(psize,offsize);
}
//ArticleController.java中定义 getListByPage方法
/**
*查询列表根据分页
* @param pindex 当前页码(从1开始)
* @param psize 每页显示条数
* @return
*/
@RequestMapping("/listbypage")
public AjaxResult getListByPage(Integer pindex, Integer psize){
//1.参数校正
if(pindex == null || pindex <= 1){
pindex = 1;
}
if(psize == null || psize <= 1){
psize = 2;
}
//分页公式的值 = (当前页码n-1)*每页显示最大条数psize
int offsize = (pindex - 1) * psize;
List list = articleService.getListByPage(psize,offsize);
return AjaxResult.success(list);
}
博客列表
//ArticleMapper.java
int getCount();
#ArticleMapper.xml
//ArticleService.java
public int getCount() {
return articleMapper.getCount();
}
//ArticleController.java
/**
*查询列表根据分页
* @param pindex 当前页码(从1开始)
* @param psize 每页显示条数
* @return
*/
@RequestMapping("/listbypage")
public AjaxResult getListByPage(Integer pindex, Integer psize){
//1.参数校正
if(pindex == null || pindex <= 1){
pindex = 1;
}
if(psize == null || psize <= 1){
psize = 2;
}
//分页公式的值 = (当前页码n-1)*每页显示最大条数psize
int offsize = (pindex - 1) * psize;
//文章列表数据
List list = articleService.getListByPage(psize,offsize);
//当前列表总共多少页
//a.总共多少条数据
int totalCount = articleService.getCount();
//b.总条数/psize(每页显示条数)
double pcountdb = totalCount / (psize * 1.0);
//c.使用进一法得到总页数
int pcount = (int)Math.ceil(pcountdb);
HashMap result = new HashMap<>();
result.put("list",list);
result.put("pcount",pcount);
return AjaxResult.success(result);
}