一、密码加密
1.1Hash算法(MD5/SHA-512等)
哈希算法,又称摘要算法(Digest),是一种将任意长度的输入通过散列函数变换成固定长度的输出的单向密码体制。这种映射的规则就是哈希算法,而通过原始数据映射之后得到的二进制值串就是哈希值。
哈希算法最重要的特点就是:相同的输入一定得到相同的输出,不同的输入可能得到相同的输出,但不可能通过输出反推出原始输入。这意味着哈希算法可以用于快速比较两个数据是否相同,常用于密码存储、数字签名、数据完整性校验等领域。
Hash算法特性
- 唯一性。数据通过hash算法计算的hash值是唯一的
- 压缩性。例如,任意长度的数据,算出的MD5值的长度是固定的(128位二进制数,32位十六进制数)
- 不可逆。无法从结果复原源数据信息
- 抗修改。对原数据的任何改动,hash值完全不同
- 强抗碰撞。伪造数据非常困难
- 容易计算。从原数据计算出值很容易
Hash算法无法转换回源数据,因此是签名算法,不是加密/解密算法(无解密)
即,仅判断是不是源数据,不知道源数据是什么。因此适合,验证敏感源数据的正确性。例如,验证密码(如何判断密码正确?)
Hash算法缺点
1.2加Salt算法
Salt(盐)是在密码学中常用的一种安全措施,其本质是一段随机的字符串。在密码加密过程中,Salt会被添加到原始密码中,再通过散列函数进行散列,最终生成一个唯一的散列值。
Salt的主要作用是增加破解密码的难度,因为即使两个用户使用了相同的密码,由于Salt的存在,他们的密码散列值也会不同。这就意味着,攻击者即使获取了数据库中的密码散列值,也无法通过简单的对比找到匹配的原始密码。
1.3Spring-Security
Spring提供了一套安全框架,处理加密/解密数据信息 提供了包括对称/非对称加密,不同Hash算法等一系列实现
相关配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
相关接口
PasswordEncoder(org.springframework.security.crypto.password)接口
PasswordEncoder接口是Spring Security中用于对密码进行加密的接口。它提供了一种通用的方法来将明文密码转换为加密后的密码,以便在存储和验证过程中使用。
- String encode(CharSequence rawPassword),编码密码
- boolean matches(CharSequence rawPassword, String encodedPassword),验证原始密码与编码密码
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class PasswordEncoderExample {
public static void main(String[] args) {
// 创建PasswordEncoder实例
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// 原始密码
String plainPassword = "myPassword";
// 加密密码
String encodedPassword = passwordEncoder.encode(plainPassword);
System.out.println("原始密码: " + plainPassword);
System.out.println("加密后的密码: " + encodedPassword);
}
}
Pbkdf2PasswordEncoder类,Pbkdf2算法
Pbkdf2PasswordEncoder类是Spring Security中的一个密码编码器,它使用PBKDF2算法对密码进行加密。PBKDF2是一种密钥导出函数,它可以从用户输入的密码生成一个足够复杂的密钥,以保护存储在数据库中的密码。
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class PasswordEncoderExample {
public static void main(String[] args) {
// 创建Pbkdf2PasswordEncoder对象
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// 原始密码
String plainPassword = "myPassword";
// 使用Pbkdf2算法加密密码
String encodedPassword = passwordEncoder.encode(plainPassword);
System.out.println("原始密码: " + plainPassword);
System.out.println("加密后的密码: " + encodedPassword);
}
}
BCryptPasswordEncoder类,Bcrypt算法
BCryptPasswordEncoder类是Spring Security中的一个密码编码器,它使用Bcrypt算法对密码进行加密。Bcrypt是一种加密算法,它可以生成一个足够复杂的哈希值来保护存储在数据库中的密码。自动生成随机盐值,并附在结果,避免盐值的单独保存
- 128bits随机二进制数,16bytes,base64,24chars,特殊算法转为22chars
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class PasswordEncoderExample {
public static void main(String[] args) {
// 创建BCryptPasswordEncoder对象
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// 原始密码
String plainPassword = "myPassword";
// 使用Bcrypt算法加密密码
String encodedPassword = passwordEncoder.encode(plainPassword);
System.out.println("原始密码: " + plainPassword);
System.out.println("加密后的密码: " + encodedPassword);
}
}
1.4实现
配置类
package com.passwordencoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfiguration {
// 定义一个名为getPasswordEncoder的方法,返回类型为PasswordEncoder
@Bean
public PasswordEncoder getPasswordEncoder() {
// 创建一个新的BCryptPasswordEncoder对象并返回
return new BCryptPasswordEncoder();
}
}
状态类
package com.passwordencoder.vo;
import lombok.Builder;
import lombok.Data;
import java.util.Map;
@Data
@Builder
public class ResultVO {
private int code;
private String message;
private Map<String, Object> data;
public static ResultVO success(Map<String, Object> data) {
return ResultVO.builder().code(200).data(data).build();
}
public static ResultVO error(int code, String msg) {
return ResultVO.builder().code(code).message(msg).build();
}
}
实体类
package com.passwordencoder.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User04 {
private String userName;
private String password;
}
服务类
package com.example.springmvcexamples.example04.passwordencoder.service;
import com.example.springmvcexamples.example04.passwordencoder.entity.User04;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j // 使用lombok的注解,简化代码并自动生成getter、setter等方法
@Service // 标记这是一个Spring服务类
public class UserService04 {
// 根据用户名获取用户信息的方法
public User04 getUser(String userName) {
// 如果用户名为"BO",则返回一个包含用户名和加密密码的用户对象
return "BO".equals(userName)
? User04.builder() // 使用User04的构建器模式创建一个新的用户对象
.userName("BO") // 设置用户名为"BO"
.password("$2a$10$A7OcKw5xxRMh9c4ghWySr.Rjh22gpWyiWExZO5i2B32eJLQrFXcr6") // 设置加密后的密码
.build() // 构建并返回用户对象
: null; // 如果用户名不为"BO",则返回null
}
}
处理类
package com.passwordencoder.controller;
import com.passwordencoder.entity.User04;
import com.passwordencoder.service.UserService04;
import com.passwordencoder.vo.ResultVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/example04/")
@RequiredArgsConstructor
public class ExampleController04 {
private final UserService04 userService;
private final PasswordEncoder passwordEncoder;
@PostMapping("login")
public ResultVO login(@RequestBody User04 user) {
// 先查询用户是否存在
User04 u = userService.getUser(user.getUserName());
if (u == null || !passwordEncoder.matches(user.getPassword(), u.getPassword())) {
log.debug("登录失败");
return ResultVO.error(401, "用户名密码错误");
}
// 登录成功,添加token等操作
log.debug("登录成功");
return ResultVO.success(Map.of("user", u));
}
}
测试
POST http://localhost:8081/api/example04/login
Content-Type: application/json
{
"userName": "BO",
"password": "12345"
}
二、序列化与反序列化
- SpringMVC默认基于Jackson实现序列化/反序列化
- SpringMVC自动注入Jackson ObjectMapper映射对象到容器
- String writeValueAsString(T payload),将对象序列化为json字符串
- T readValue(String content, Class c),将json字符串反序列化为指定类型的Java对象
TypeReference<T>抽象类。创建子类,具体化泛型。可通过创建类似接口的匿名内部类实现
三、token令牌
3.1概述
Token令牌是一种用于身份验证和授权的凭证,通常由服务器生成并发送给用户。它包含有关用户的信息,例如用户名、角色等,以及一些会话信息,例如过期时间等。当用户尝试访问受保护的资源时,他们需要提供有效的Token令牌以证明其身份和权限。服务器将验证Token令牌的有效性,并根据其中包含的信息授予或拒绝用户的请求。
Restful设计思想,服务器端不再保存用户状态(无HttpSession)
- 用户登录后,将用户身份/权限信息封装在Token(令牌)
- ·将token信息加密(Authorization)通过http header返给客户端
- ·客户端每次需要身份/权限的请求,均需在http header携带Authorization
- ·服务器端拦截权限请求,从Authorization中解密出Token权鉴
实现
- ·JWT。流行的认证标准,信息由header/payload/,signature组成,多种实现
- ·自定义Token。更灵活,数据量小
3.2适合敏感数据的加密传输
加密/解密算法,适合敏感数据的加密传输
- 对称加密算法(AES等),通过相同密钥加密/解密
- 非对称加密算法(RSA等),公钥加密的数据,必须通过私钥才能解密
密钥生成器
my:
secretkey: R28K42ZEJ8LWRHU5
salt: 636eac2534bcfcb0
实体类
package com.example.springmvcexamples.example05;
import lombok.Data;
@Data
public class MyToken {
public enum Role{
USER, ADMIN
}
private Integer uid;
private Role role;
}
组件类
package com.example.springmvcexamples.example05.textencryptor;
import com.example.springmvcexamples.example02.handlingexception.exception.MyException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Map;
@Component
public class EncryptComponent05 {
private final ObjectMapper objectMapper;
@Value("${my.secretkey}")
private String secretKey;
@Value("${my.salt}")
private String salt;
private TextEncryptor encryptor;
public EncryptComponent05(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
/**
* 直接基于密钥/盐值创建单例TextEncryptor对象。避免反复创建
*/
@PostConstruct
public void getTextEncryptor() {
encryptor = Encryptors.text(secretKey, salt);
}
public String encrypt(Map<String, Object> payload) {
try {
String json = objectMapper.writeValueAsString(payload);
return encryptor.encrypt(json);
} catch (JsonProcessingException e) {
throw new MyException(500, "服务器端错误");
}
}
/**
* 无法验证/解密/反序列化,说明数据被篡改,判定无权限
* @param auth
* @return
*/
public Map<String, Object> decrypt(String auth) {
try {
String json = encryptor.decrypt(auth);
return objectMapper.readValue(json, Map.class);
} catch (Exception e) {
throw new MyException(403, "无权限");
}
}
}
@Component注解
`@Component`注解是Spring框架中的一个注解,用于标记一个类作为Spring容器中的组件。当Spring容器启动时,会自动扫描带有`@Component`注解的类,并将这些类实例化为对象,然后将这些对象存储在Spring容器中,以便在其他组件中通过依赖注入的方式使用。
测试一
package com.example.springmvcexamples.example05;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import java.util.HashMap;
import java.util.Map;
@SpringBootTest
@Slf4j
public class EncryptorTest {
// 自动注入ObjectMapper对象,用于将对象转换为JSON字符串和将JSON字符串转换为对象
@Autowired
private ObjectMapper objectMapper;
// 从配置文件中获取加密密钥
@Value("${my.secretkey}")
private String secretKey;
// 从配置文件中获取盐值
@Value("${my.salt}")
private String salt;
// 测试方法
@Test
public void test_encrypt() {
// 创建加密器,使用密钥和盐值进行加密
TextEncryptor encryptor = Encryptors.text(secretKey, salt);
try {
// 创建一个包含uid和role的Map对象
Map<String, Object> map = Map.of("uid", 1384896304762638307L, "role", 9);
// 将Map对象转换为JSON字符串
String json = objectMapper.writeValueAsString(map);
// 使用加密器对JSON字符串进行加密
String r = encryptor.encrypt(json);
// 输出加密后的字符串
log.debug(r);
// 输出加密后的字符串长度
log.debug("{}", r.length());
// 再次使用加密器对JSON字符串进行加密,验证加密结果是否一致
log.debug(encryptor.encrypt(json));
// 使用加密器对加密后的字符串进行解密
String reJson = encryptor.decrypt(r);
// 将解密后的JSON字符串转换为Map对象
Map<String, Object> reToken = objectMapper.readValue(reJson, Map.class);
// 输出解密后的Map对象中的role值
log.debug(reToken.get("role").toString());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
测试二
package com.example.springmvcexamples.example05;
import com.example.springmvcexamples.example05.textencryptor.EncryptComponent05;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Map;
@SpringBootTest
@Slf4j
public class TextEncryptorTest {
@Autowired
private EncryptComponent05 encrypt;
@Test
public void test_encrypt() {
// MyToken Token = new MyToken();
// Token.setUid(1);
// Token.setRole(MyToken.Role.ADMIN);
Map<String, Object> map = Map.of("uid", 1384896304762638307L, "role", 9);
String r = encrypt.encrypt(map);
log.debug(r);
log.debug("{}", r.length());
log.debug(encrypt.encrypt(map));
}
@Test
public void test_decrypt() {
String auth = "b3a60e67dfcd220874e36569f623829ea97d556d646b4eb208c2f43" +
"b452bbf61a3e5982e0a52810517bcc734a5561e2dc53a9e3854d5fd4afebf0b15b7c1ad5c";
Map<String, Object> token = encrypt.decrypt(auth);
log.debug("{}", token.get("uid"));
log.debug("{}", token.get("role"));
}
}
四、拦截器
HandlerInterceptor (org.springframework.web.servlet.HandlerInterceptor)接口
- Boolean preHandle()方法:controller方法执行前回调,返回false,则不会继续执行。登录验证等
- Void postHandle()方法:perHandle()返回true后,controller方法执行后
- afterCompletion方法:postHandle()执行后,回调
- Object handle,封装被拦截方法对象
拦截器类
InterceptorRegistry
- addInterceptor(),添加拦截器组件
- addPathPatterns(),添加拦截路径
- excludePathPatterns(),添加排除路径
- 可声明多个拦截器,按顺序拦截
package com.example.springmvcexamples.example06.interceptor.interceptor;
import com.example.springmvcexamples.example02.handlingexception.exception.MyException;
import com.example.springmvcexamples.example05.textencryptor.EncryptComponent05;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
@Component // 将LoginInterceptor06类标记为Spring容器中的组件
@Slf4j // 使用log4j进行日志记录
@RequiredArgsConstructor // 通过构造函数注入依赖,避免在实例化时需要手动注入依赖
public class LoginInterceptor06 implements HandlerInterceptor {
private final EncryptComponent05 encryptComponent; // 注入加密组件
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token"); // 从请求头中获取token
if (token == null) {
throw new MyException(401, "未登录"); // 如果token为空,抛出自定义异常
}
Map<String, Object> result = encryptComponent.decrypt(token); // 解密token,获取用户信息
request.setAttribute("role", result.get("role")); // 将用户角色设置到请求属性中,以便后续处理中使用
return true; // 返回true表示继续执行后续的拦截器和处理器
}
}
配置类
package com.example.springmvcexamples;
import com.example.springmvcexamples.example06.interceptor.interceptor.LoginInterceptor06;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@RequiredArgsConstructor
public class WebMvcConfiguration implements WebMvcConfigurer {
private final LoginInterceptor06 adminInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminInterceptor)
.addPathPatterns("/api/example06/admin/**");
}
}
五、定时服务(Timer Service)
定时服务(Timer Service):用以调度安排所有基于定时通知的工作流程
支持指定时间、某一时间后、指定时间间隔内的定时通知
Spring Task Execution and Scheduling
- 基于TaskExecutor,TaskScheduler接口
- 基于注解
- 基于Quartz Scheduler第三方库
@Scheduled
@Scheduled
注解是Spring框架中的一个注解,用于标记一个方法为定时任务。它可以与@Scheduled
注解一起使用,或者与TaskScheduler
接口一起使用。使用
@Scheduled
注解的方法会在指定的时间间隔内自动执行定时任务方法声明在组件内,方法必须无返回值
- fixedRate,每次执行间隔时间,即使上一任务未执行完依然执行任务(毫秒)
- fixedDelay,每次执行完成到下一次开始执行间隔时间,即上一任务执行结束后,过指定时间执行任务(毫秒)
- initialDelay,第一次执行前的延迟时间(毫秒)
- Cron,指定执行时间表达式
Cron表达式
顺序:秒、分、时、日、月、星期(数字或单词缩写)、年(可省略,即每年)。值为数字或符号
默认不支持从后计算(不支持L)
启动类
package com.example.springmvcexamples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
@SpringBootApplication
@EnableScheduling
public class SpringmvcExamplesApplication {
public static void main(String[] args) {
SpringApplication.run(SpringmvcExamplesApplication.class, args);
}
}
@EnableScheduling
注解
@EnableScheduling
注解是Spring框架中的一个注解,用于开启定时任务功能。在Spring Boot应用中,可以通过在主类上添加该注解来启用定时任务。
组件类
package com.example.springmvcexamples.example07.timer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class MyTimer {
@Scheduled(cron = "0 0 8 10 * ?")
public void paySalary() {
log.debug("Your salary has been paid!");
}
}