安全模块每个月自动更新密钥ø

This commit is contained in:
筱锋xiao_lfeng 2024-01-17 12:07:35 +08:00
parent d7afcc1e18
commit 2e4b16b188
Signed by: XiaoLFeng
GPG Key ID: F693AA12AABBFA87
11 changed files with 305 additions and 39 deletions

View File

@ -2,8 +2,10 @@ package com.jsl.oa;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class JslOrganizeInternalOaApplication {
public static void main(String[] args) {

View File

@ -0,0 +1,52 @@
package com.jsl.oa;
import com.google.gson.Gson;
import com.jsl.oa.common.constant.SafeConstants;
import com.jsl.oa.mapper.InfoMapper;
import com.jsl.oa.model.doData.ConfigDO;
import com.jsl.oa.model.voData.business.InfoAboutSecurityKey;
import com.jsl.oa.utils.Processing;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.stereotype.Component;
import java.sql.Timestamp;
/**
* <h1>启动类</h1>
* <hr/>
* 用于启动项目
*
* @version v1.1.0
* @see org.springframework.boot.SpringApplication
* @see org.springframework.boot.autoconfigure.SpringBootApplication
* @since v1.1.0
*/
@Component
@RequiredArgsConstructor
public class JslOrganizeInternalOaRunnerApplication implements SmartInitializingSingleton {
private final Gson gson = new Gson();
private final InfoMapper infoMapper;
@Override
public void afterSingletonsInstantiated() {
// 获取数据库中的 SecurityKey
try {
SafeConstants.SECRET_KEY = infoMapper.getSecurityKey().getData();
} catch (NullPointerException exception) {
// 生成密钥
String key = Processing.generateKey(System.currentTimeMillis());
InfoAboutSecurityKey infoAboutSecurityKey = new InfoAboutSecurityKey();
infoAboutSecurityKey.setKey(key)
.setUpdateTime(System.currentTimeMillis());
String json = gson.toJson(infoAboutSecurityKey, InfoAboutSecurityKey.class);
// 更新密钥
ConfigDO configDO = new ConfigDO();
configDO.setValue("security_key")
.setData(json)
.setCreatedAt(new Timestamp(System.currentTimeMillis()));
infoMapper.insertSecurityKey(configDO);
SafeConstants.SECRET_KEY = key;
}
}
}

View File

@ -0,0 +1,5 @@
package com.jsl.oa.common.constant;
public class SafeConstants {
public static String SECRET_KEY;
}

View File

@ -27,7 +27,7 @@ public class ShiroConfiguration {
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/auth/**", "anon"); // 登录接口允许匿名访问
filterChainDefinitionMap.put("/unauthorized", "anon"); // 未授权接口允许匿名访问
filterChainDefinitionMap.put("/", "anon"); // 首页允许匿名访问
filterChainDefinitionMap.put("/", "jwt"); // 首页允许匿名访问
filterChainDefinitionMap.put("/**/**", "jwt"); // 其他接口一律拦截(需要Token)
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

View File

@ -0,0 +1,48 @@
package com.jsl.oa.mapper;
import com.jsl.oa.model.doData.ConfigDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
/**
* <h1>信息映射</h1>
* <hr/>
* 用于信息的映射映射数据库 oa_config
*
* @since v1.1.0
* @version v1.1.0
* @see com.jsl.oa.model.doData.ConfigDO
*/
@Mapper
public interface InfoMapper {
/**
* <h2>获取密钥</h2>
* <hr/>
* 用于获取密钥
*
* @return {@link ConfigDO}
*/
@Select("SELECT * FROM organize_oa.oa_config WHERE value= 'security_key'")
ConfigDO getSecurityKey();
/**
* <h2>更新密钥</h2>
* <hr/>
* 用于更新密钥
*
* @param configDO {@link ConfigDO}
*/
@Update("UPDATE organize_oa.oa_config SET data = #{data} WHERE value = 'security_key'")
void updateSecurityKey(ConfigDO configDO);
/**
* <h2>插入密钥</h2>
* <hr/>
* 用于插入密钥
*
* @param configDO {@link ConfigDO}
*/
@Update("INSERT INTO organize_oa.oa_config (value, data, created_at) VALUES (#{value}, #{data}, #{createdAt})")
void insertSecurityKey(ConfigDO configDO);
}

View File

@ -0,0 +1,19 @@
package com.jsl.oa.model.voData.business;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* <h1>密钥信息</h1>
* <hr/>
* 用于密钥信息的存储
*
* @since v1.1.0
* @version v1.1.0
*/
@Data
@Accessors(chain = true)
public class InfoAboutSecurityKey {
private String key;
private Long updateTime;
}

View File

@ -0,0 +1,57 @@
package com.jsl.oa.schedule;
import com.google.gson.Gson;
import com.jsl.oa.mapper.InfoMapper;
import com.jsl.oa.model.doData.ConfigDO;
import com.jsl.oa.model.voData.business.InfoAboutSecurityKey;
import com.jsl.oa.utils.Processing;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
/**
* <h1>安全相关日程</h1>
* <hr/>
* 用于安全相关的日程
*
* @since v1.1.0
* @version v1.1.0
* @author 筱锋xiao_lfeng
*/
@Component
@RequiredArgsConstructor
public class SafeSchedule {
private final InfoMapper infoMapper;
/**
* <h2>更新密钥</h2>
* <hr/>
* 用于更新密钥密钥每个月进行更新需要检查每月是否到1号<br/>
* 每天凌晨0点执行一次从数据库获取数据检查是否需要更新密钥
*/
@Scheduled(cron = "0 0 0 * * ?")
public void updateKey() {
// 获取当前日
SimpleDateFormat getDateFormatAtDay = new SimpleDateFormat("dd");
String day = getDateFormatAtDay.format(System.currentTimeMillis());
if (day.equals("01")) {
// 获取密钥及更新时间
ConfigDO configDO = infoMapper.getSecurityKey();
Gson gson = new Gson();
InfoAboutSecurityKey securityKey = gson.fromJson(configDO.getData(), InfoAboutSecurityKey.class);
// 检查时间戳是否超过了20天有效期
if (System.currentTimeMillis() - securityKey.getUpdateTime() > 1728000000) {
// 生成新密钥
String newKey = Processing.generateKey(System.currentTimeMillis());
// 更新密钥
securityKey.setKey(newKey)
.setUpdateTime(System.currentTimeMillis());
// 更新数据库
configDO.setData(gson.toJson(securityKey));
infoMapper.updateSecurityKey(configDO);
}
}
}
}

View File

@ -89,7 +89,7 @@ public class AuthServiceImpl implements AuthService {
if (BCrypt.checkpw(userLoginVO.getPassword(), userDO.getPassword())) {
UserReturnBackVO userReturnBackVO = new UserReturnBackVO();
// 授权 Token
String token = JwtUtil.generateToken(userDO.getUsername());
String token = JwtUtil.generateToken(userDO.getId());
userReturnBackVO.setAddress(userDO.getAddress())
.setAge(userDO.getAge())
.setEmail(userDO.getEmail())
@ -118,7 +118,7 @@ public class AuthServiceImpl implements AuthService {
// 邮箱获取用户
UserDO userDO = userMapper.getUserInfoByEmail(email);
// 授权 Token
String token = JwtUtil.generateToken(userDO.getUsername());
String token = JwtUtil.generateToken(userDO.getId());
UserReturnBackVO userReturnBackVO = new UserReturnBackVO();
userReturnBackVO.setAddress(userDO.getAddress())
.setAge(userDO.getAge())
@ -143,7 +143,7 @@ public class AuthServiceImpl implements AuthService {
UserDO userDO = userMapper.getUserInfoByEmail(email);
if (userDO != null) {
// 生成验证码
Integer code = Processing.createCode();
Integer code = Processing.createCode(null);
// 存储验证码
if (emailRedisUtil.setData(BusinessConstants.BUSINESS_LOGIN, email, code, 5)) {
// 发送邮件

View File

@ -1,49 +1,86 @@
package com.jsl.oa.utils;
import com.jsl.oa.common.constant.SafeConstants;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.jetbrains.annotations.NotNull;
import java.security.Key;
import java.util.regex.Pattern;
/**
* <h1>JWT工具类</h1>
* <hr/>
* 用于生成Token和验证Token
*
* @author 筱锋xiao_lfeng
* @version v1.1.0
* @see com.jsl.oa.config.JwtFilter
* @since v1.1.0
*/
public class JwtUtil {
// 替换为实际的密钥建议使用足够长的随机字符串
private static final String SECRET_KEY = "238542310128901753637022851772455105464283332917211091531086967815273100806759714250034263888525489008903447113697698540563820710887668094087054975808574632265678643370464260078072153369247242449569221118098938297741582538222826493707667477115117609126233";
// Token 有效期这里设置为一天可以根据实际需求调整
private static final long EXPIRATION_TIME = 86400000;
// 生成Token
public static String generateToken(String username) {
Key key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes());
/**
* <h2>生成Token</h2>
* <hr/>
* 用于生成Token
* @param userId 用户ID
* @return 返回生成的Token
*/
public static String generateToken(@NotNull Long userId) {
Key key = Keys.hmacShaKeyFor(SafeConstants.SECRET_KEY.getBytes());
return Jwts.builder()
.setSubject(username)
.setSubject(userId.toString())
.setExpiration(new java.util.Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
// 验证Token
/**
* <h2>验证Token</h2>
* <hr/>
* 用于验证Token是否有效
*
* @param token Token
* @return {@link Boolean}
*/
public static boolean verify(String token) {
try {
Key key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes());
Jws<Claims> claimsJws = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token);
// 从JWT中获取用户名进行匹配
String tokenUsername = claimsJws.getBody().getSubject();
Long getTokenInUserId = getUserId(token);
// 验证用户名是否匹配
return Pattern.matches("^[0-9A-Za-z_]{3,40}$", tokenUsername);
return Pattern.matches("^[0-9]+$", getTokenInUserId.toString());
} catch (Exception e) {
// 验证失败
e.printStackTrace();
return false;
}
}
/**
* <h2>获取用户名</h2>
* <hr/>
* 用于获取Token中的用户名
*
* @param token Token
* @return 返回获取到的用户名
*/
public static Long getUserId(String token) {
Key key = Keys.hmacShaKeyFor(SafeConstants.SECRET_KEY.getBytes());
Jws<Claims> claimsJws = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token);
// 从JWT中获取用户名进行匹配
long userId;
try {
userId = Long.parseLong(claimsJws.getBody().getSubject());
} catch (NumberFormatException exception) {
exception.printStackTrace();
throw new NumberFormatException("用户ID格式错误");
}
return userId;
}
}

View File

@ -12,8 +12,8 @@ import java.util.Random;
* <hr/>
*
* @author 筱锋xiao_lfeng
* @since v1.0.0
* @version v1.0.0
* @since v1.0.0
*/
public class Processing {
@ -22,9 +22,9 @@ public class Processing {
* <hr/>
* 用于获取参数校验错误信息
*
* @since v1.0.0
* @param bindingResult 参数校验结果
* @return {@link ArrayList<String>}
* @since v1.0.0
*/
public static @NotNull ArrayList<String> getValidatedErrorList(BindingResult bindingResult) {
ArrayList<String> arrayList = new ArrayList<>();
@ -39,9 +39,9 @@ public class Processing {
* <hr/>
* 用于生成工号默认长度为10
*
* @since v1.0.0
* @param type 0:学生 1:教师 2:其他
* @return {@link String}
* @since v1.0.0
*/
public static @NotNull String createJobNumber(Short type) {
return createJobNumber(type, (short) 10);
@ -52,10 +52,10 @@ public class Processing {
* <hr/>
* 用于生成工号
*
* @since v1.0.0
* @param type 0:学生 1:教师 2:其他
* @param size 工号长度
* @return {@link String}
* @since v1.0.0
*/
public static @NotNull String createJobNumber(Short type, Short size) {
StringBuilder stringBuilder = new StringBuilder();
@ -68,22 +68,67 @@ public class Processing {
}
// 生成工号
Random random = new Random();
for (int i = 0; i < size-3; i++) {
for (int i = 0; i < size - 3; i++) {
stringBuilder.append(random.nextInt(10));
}
return stringBuilder.toString();
}
/**
* <h1>生成验证码</h1>
* <hr/>
* 用于生成验证码默认长度为6
*
* @return {@link Integer}
*/
public static @NotNull Integer createCode() {
public static @NotNull Integer createCode(Integer size) {
if (size == null) {
size = 6;
}
StringBuilder stringBuilder = new StringBuilder();
// 生成验证码
Random random = new Random();
for (int i = 0; i < 6; i++) {
for (int i = 0; i < size; i++) {
stringBuilder.append(random.nextInt(10));
}
return Integer.valueOf(stringBuilder.toString());
}
/**
* <h1>生成256位字符串</h1>
* <hr/>
* 用于生成256位字符串用于加密
*
* @param input 输入
* @return 返回加密后的字符串
*/
public static String generateKey(Long input) {
String inputString = String.valueOf(input);
// 使用简单的伪随机算法生成256位字符串
StringBuilder result = new StringBuilder();
for (int i = 0; i < 256; i++) {
int charIndex;
int math;
if (i == 0) {
math = Math.abs(inputString.hashCode());
} else {
math = Math.abs(inputString.hashCode() % i);
}
if (i < 62) {
charIndex = (math % 62);
} else {
charIndex = (math / 6 % 62);
}
char nextChar = getCharFromIndex(charIndex);
result.append(nextChar);
}
return result.toString();
}
private static char getCharFromIndex(int index) {
// 生成字符集合可以根据需要自定义
String charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return charset.charAt(index);
}
}

View File

@ -1,6 +1,6 @@
package com.jsl.oa;
import com.jsl.oa.utils.JwtUtil;
import com.jsl.oa.utils.Processing;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@ -9,12 +9,13 @@ class JslOrganizeInternalOaApplicationTests {
@Test
void contextLoads() {
String token = JwtUtil.generateToken("admin");
if (JwtUtil.verify(token)) {
System.out.println("验证通过");
} else {
System.out.println("验证失败");
}
System.out.println(Processing.generateKey(1L));
System.out.println(Processing.generateKey(1L));
System.out.println(Processing.generateKey(2L));
System.out.println(Processing.generateKey(2L));
System.out.println(Processing.generateKey(3L));
System.out.println(Processing.generateKey(3L));
System.out.println(Processing.generateKey(3L));
}
}