MD5加密与Validation校验

目录

一、什么是MD5

特点

性质

springboot中使用

引入依赖

二、Spring Validation

使用步骤:

1、引入Spring Validation起步依赖

2、在需要被校验的参数前面添加@Pattern

3、在Controller类上添加@Validated

验证

分组校验

步骤

默认分组


一、什么是MD5

全称为 消息摘要算法版本5 (Message Digest Algorithm 5)

它是一种Hash算法,作用是为了信息安全

简单来说,你可以理解MD5值就是一串128bit的数据

特点

不可逆性根据 MD5 值计算不出原始数据
唯一性不同的原始数据计算出不同的MD5值

一个MD5理论上的确是可能对应无数多个原文的,因为MD5是有限多个的而原文可以是无数多个。

性质

压缩性任意长度的数据,算出的MD5值长度都是固定的(相当于超损压缩)
容易计算从原数据计算出MD5值很容易。
抗修改性对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别
抗碰撞已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
强抗碰撞想找到两个不同的数据,使它们具有相同的MD5值,是非常困难的

注:虽说MD5有不可逆的特点

但是由于某些MD5破解网站,专门用来查询MD5码,其通过把常用的密码先MD5处理,并将数据存储起来,然后跟需要查询的MD5结果匹配,这时就有可能通过匹配的MD5得到明文,所以有些简单的MD5码是反查到加密前原文的。

为了提高安全性,可以进行加盐处理,这样每次生成的值都不一样

可以直接添加使用源码,也可以引入依赖使用别人的方法

public class Md5Util {
    /**
     * 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校验下载的文件的正确性用的就是默认的这个组合
     */
    protected static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    protected static MessageDigest messagedigest = null;

    static {
        try {
            messagedigest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException nsaex) {
            System.err.println(Md5Util.class.getName() + "初始化失败,MessageDigest不支持MD5Util。");
            nsaex.printStackTrace();
        }
    }

    /**
     * 生成字符串的md5校验值
     *
     * @param s
     * @return
     */
    public static String getMD5String(String s) {
        return getMD5String(s.getBytes());
    }

    /**
     * 判断字符串的md5校验码是否与一个已知的md5码相匹配
     *
     * @param password  要校验的字符串
     * @param md5PwdStr 已知的md5校验码
     * @return
     */
    public static boolean checkPassword(String password, String md5PwdStr) {
        String s = getMD5String(password);
        return s.equals(md5PwdStr);
    }


    public static String getMD5String(byte[] bytes) {
        messagedigest.update(bytes);
        return bufferToHex(messagedigest.digest());
    }

    private static String bufferToHex(byte bytes[]) {
        return bufferToHex(bytes, 0, bytes.length);
    }

    private static String bufferToHex(byte bytes[], int m, int n) {
        StringBuffer stringbuffer = new StringBuffer(2 * n);
        int k = m + n;
        for (int l = m; l < k; l++) {
            appendHexPair(bytes[l], stringbuffer);
        }
        return stringbuffer.toString();
    }

    private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
        char c0 = hexDigits[(bt & 0xf0) >> 4];// 取字节中高 4 位的数字转换, >>>
        // 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同
        char c1 = hexDigits[bt & 0xf];// 取字节中低 4 位的数字转换
        stringbuffer.append(c0);
        stringbuffer.append(c1);
    }

}

springboot中使用

引入依赖

		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-core</artifactId>
		</dependency>

在启动类中配置一个bean

	@Bean
	public PasswordEncoder passwordEncoder(){
		return new BCryptPasswordEncoder();
	}

我们简单设计一个新增用户的接口,并对密码进行MD5加密处理

@RestController
@RequestMapping("/user")
public class UserController { 
   @Autowired
    private PasswordEncoder passwordEncoder;

        @PostMapping
    //前端传来的user对象是一个json格式
    public Result<?> addUser(@RequestBody User user){
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        //这个方法已经进行了加盐处理,相同的初始数据生成的值不一样
        userService.save(user);
        return Result.success("新增用户成功");
    }
}

现在我们在前端或是postman中创建两个密码相同的用户,然后到数据库中查看

密码同样是123456,但是生成的密码却不一样

 我们再来设计对应的登录接口

    @Override
    public Map<String, Object> login(User user) {
        //根据用户名与密码查询结果
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername,user.getUsername());
//        wrapper.eq(User::getPassword,user.getPassword()); 引入了MD5加密,就不能直接比对密码了
        User loginUser = this.baseMapper.selectOne(wrapper);

        //若结果不为空,并且密码和传入密码匹配则生成token,并将用户信息存入redis
        if (loginUser!=null && passwordEncoder.matches(user.getPassword(),loginUser.getPassword())){
            //暂时用UUID,终极方案应为jwt
            String key = "user:" + UUID.randomUUID();

            //存入redis
            loginUser.setPassword(null);    //密码不要存进去
            //默认这是永久有效的,要设置timeout时间
            redisTemplate.opsForValue().set(key,loginUser,30, TimeUnit.MINUTES);

            //返回数据
            Map<String,Object> data = new HashMap<>();
            data.put("token",key);
            return data;
        }
        return null;
    }

 

 成功登录


二、Spring Validation

Spring提供了一个参数校验框架,使用预定义的注解完成参数校验,叫做Spring Validation。

使用步骤:

1、引入Spring Validation起步依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

2、在需要被校验的参数前面添加@Pattern

@Patten(regexp = "^正则表达式$")

@Validated
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/register")   //校验
    public Result register(@Pattern(regexp = "^\\S{5,16}$") String username,
                           @Pattern(regexp = "^\\S{5,16}$") String password) {

        //查询用户
        User user = userService.findByUserName(username);
        if (user == null){
            //注册
            userService.register(username,password);
            return Result.success();
        }else {
            //占用
            return Result.error("用户名以及被占用");
        }

    }
}
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e){
        e.printStackTrace();
        //有的异常可能没有message,所以要先用三元运算符判断一下
        return Result.error(StringUtils
                .hasLength(e.getMessage()) ? e.getMessage() : "操作失败");
    }

}

3、在Controller类上添加@Validated

验证

分组校验

        把校验项进行归类分组,在完成不同的功能时,校验指定组中的验项

比如这个实体类需要被分组校验

@Data
public class Category {
    private Integer id;//主键ID

    @NotEmpty
    private String categoryName;//分类名称

    @NotEmpty
    private String categoryAlias;//分类别名

    private Integer createUser;//创建人ID

    @JsonFormat(pattern = "yyyy-MM-DD HH:mm:ss")
    private LocalDateTime createTime;//创建时间

    @JsonFormat(pattern = "yyyy-MM-DD HH:mm:ss")
    private LocalDateTime updateTime;//更新时间


}

步骤

        1、定义分组

    //在实体类底下添加分组
    public interface Add{

    }

    public interface Update{

    }

        2、定义校验项时指定归属的分组

@Data
public class Category {
    @NotNull(groups = Update.class)
    private Integer id;//主键ID

    @NotNull(groups ={ Add.class,Update.class })
    @NotEmpty
    private String categoryName;//分类名称

    @NotNull(groups ={ Add.class,Update.class })
    @NotEmpty
    private String categoryAlias;//分类别名

    public interface Add{

    }

    public interface Update{

    }

}

id被分到update组,name和alias被分到add组和update组,这样只有在update的时候才会校验id,而在add的时候就不校验id

        3、校验时要指定的分组

在@Validated(分组.class)

@PostMapping
    public Result category(@RequestBody @Validated(Category.Add.class) Category category){

        String categoryName = category.getCategoryName();
        if (StringUtils.hasLength(categoryService.selectByName(categoryName))){
            return Result.error("分类重复");
        }

        categoryService.category(category);

        return Result.success();
    }


@PutMapping()
    public Result updateCategory(@RequestBody @Validated(Category.Update.class) Category category){

        categoryService.updateCategory(category);

        return Result.success();
    }

默认分组

注:

        如果某个校验项中没有指定分组,默认属于Default分组

        分组之间可以继承,A extent B,那么A组就有B组的所有校验项

Add extend 默认,那么add就是default分组了;可以有多个分组继承default,那么它们都是default了

因此刚才实体类的代码也可以这么写

@Data
public class Category {
    @NotNull(groups = Update.class)
    private Integer id;//主键ID

    @NotNull()
    @NotEmpty
    private String categoryName;//分类名称

    @NotNull()
    @NotEmpty
    private String categoryAlias;//分类别名


    public interface Add extends Default{

    }

    public interface Update extends Default{

    }

}

@Notnull中什么也不写那就默认是default,但是此时add与update都继承了default,所以其实也相当于上面的一样

自定义校验

参数校验的注解很强大,但是已经自带的注解有时候不能满足我们的需求

比如:要求某个属性的值必须是 1 或者 0

这种需求就没有现成的注解可以满足我们的需求,因此我们就需要自己手动创建一个校验注解

打开@NotNull注解查看源码,我们直接模仿它来完成我们的注解

注解也直接拿过来,其中

@Documented:元注解,标识被作用到注解是可以被抽取到帮助文档中

@Target:元注解,标识被作用的注解可以被用在哪里,方法、属性等

@Retention:元注解,标识被作用的注解在那个阶段会被保留,如编译阶段、运行阶段等

@Constraint:标识谁来给这个注解来提供校验的规则

其中属性已经定好了,我们也直接拿过来,其中

String message default:提供校验失败后的提示信息

Class<?>[ ] groups() default { }:指定分组

Class<? extends Payload>[] payload() default { }:负载,获取到state注解的附加信息

步骤1、创建注解类

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
        validatedBy = { StateValidation.class }
)
public @interface State {

    String message() default "{jakarta.validation.constraints.NotNull.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

步骤二、创建实现类

创建一个实现类,要继承ConstraintValidator(<哪一个注解,作用的字段的类型>)

public class StateValidation implements ConstraintValidator<State,String> {

    /**
     *
     * @param s 将来要校验的数据
     * @param constraintValidatorContext
     * @return false则不通过,true则通过
     */
    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        //提供校验规则
        if (s == null){
            return false;
        }

        if (s.equals("已发布") || s.equals("草稿")){
            return true;
        }
        return false;
    }
}

3、使用

这样我们自定义的注解就可以生效了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

An1ong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值