前言
1、声明:本人学习过程中记录的内容或者引用的相关文章,如果侵犯了相关的产权,请及时联系我,我第一时间进行更改或者删除。
2、我学习的版本选择了前后端分离版本RuoYi-Vue 3.8.9。
3、RuoYi-Vue是一个基于Spring Boot + Vue2 的前后端分离的Java快速开发框架,这是一个成熟的企业级管理系统框架,提供了完整的权限管理和业务功能,方便我们快速进行企业应用开发。
4、关于后端代码的练习、前端代码的练习和css布局的测试,将分别建立新的项目进行学习。
5、因为新内容不断地增加,所以计划文章将不断地更新,望各位同仁不吝赐教!
一、技术栈分析
#### 后端
- JDK 1.8
- Spring Boot 2.5.15
- Spring Security 5.7.12
- MyBatis
- Redis
- MySQL
- JWT
#### 前端
- Vue 2.6.12
- Element UI 2.15.14
- Vue Router 3.4.9
- Vuex 3.6.0
- Axios 0.28.1
-node 22
### 核心特性
1. **前后端分离架构** - 清晰的职责分离
2. **JWT认证** - 无状态的身份认证
3. **动态权限控制** - 基于角色的权限管理
4. **代码生成器** - 提高开发效率
5. **多终端支持** - 支持多种客户端
6. **国际化支持** - 多语言支持
7. **主题切换** - 支持明暗主题
8. **响应式设计** - 适配不同屏幕
### 安全特性
- JWT Token认证
- BCrypt密码加密
- XSS攻击防护
- SQL注入防护
- 权限控制
- 审计日志
二、安全架构分析
## 1. 概述
RuoYi-Vue采用多层次、全方位的安全防护机制,确保系统的安全性。本文档详细分析系统的安全架构设计、防护机制和最佳实践。
## 2. 安全架构概述
### 2.1 安全层次
┌─────────────────────────────────────┐
│ 应用层安全 │
│ - 权限控制 - 数据验证 - 业务逻辑 │
├─────────────────────────────────────┤
│ 框架层安全 │
│ - Spring Security - 过滤器 - 拦截器 │
├─────────────────────────────────────┤
│ 传输层安全 │
│ - HTTPS - JWT - 加密传输 │
├─────────────────────────────────────┤
│ 数据层安全 │
│ - SQL注入防护 - 数据加密 - 访问控制 │
└─────────────────────────────────────┘
### 2.2 安全特性
- **身份认证** - JWT Token认证机制
- **权限控制** - 基于角色的访问控制(RBAC)
- **数据防护** - XSS、SQL注入防护
- **传输安全** - HTTPS加密传输
- **审计日志** - 操作日志记录
- **密码安全** - BCrypt加密存储
## 3. 认证与授权机制
### 3.1 JWT认证机制
#### 3.1.1 Token结构
// JWT Token组成
Header.Payload.Signature
// Header示例
{
"alg": "HS256",
"typ": "JWT"
}
// Payload示例
{
"sub": "1234567890",
"name": "admin",
"iat": 1516239022,
"exp": 1516242622
}
#### 3.1.2 Token服务实现
// TokenService.java
@Service
public class TokenService {
@Autowired
private RedisCache redisCache;
// 创建Token
public String createToken(LoginUser loginUser) {
String token = IdUtil.fastUUID();
loginUser.setToken(token);
setUserAgent(loginUser);
refreshToken(loginUser);
// 生成token
Map<String, Object> claims = new HashMap<>();
claims.put(Constants.LOGIN_USER_KEY, token);
return createToken(claims);
}
// 验证Token
public LoginUser getLoginUser(HttpServletRequest request) {
// 获取请求携带的令牌
String token = getToken(request);
if (StringUtils.isNotEmpty(token)) {
Claims claims = parseToken(token);
// 解析对应的权限以及用户信息
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid);
return redisCache.getCacheObject(userKey);
}
return null;
}
// 刷新Token
public void refreshToken(LoginUser loginUser) {
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * 1000);
// 根据uuid将loginUser缓存
String userKey = getTokenKey(loginUser.getToken());
redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.SECONDS);
}
}
### 3.2 Spring Security配置
#### 3.2.1 安全配置
// SecurityConfig.java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// CSRF禁用
.csrf(csrf -> csrf.disable())
// 认证失败处理类
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
// 基于token,不需要session
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 过滤请求
.authorizeHttpRequests(authz -> authz
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
.requestMatchers("/login", "/register", "/captchaImage").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
);
// 添加Logout filter
http.logout(logout -> logout.logoutSuccessHandler(logoutSuccessHandler));
// 添加JWT filter
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter
http.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
#### 3.2.2 JWT过滤器
// JwtAuthenticationTokenFilter.java
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private TokenService tokenService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
LoginUser loginUser = tokenService.getLoginUser(request);
if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) {
tokenService.verifyToken(loginUser);
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
chain.doFilter(request, response);
}
}
### 3.3 权限控制机制
#### 3.3.1 基于注解的权限控制
// 控制器中的权限注解
@RestController
@RequestMapping("/system/user")
public class SysUserController extends BaseController {
// 需要system:user:list权限
@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/list")
public TableDataInfo list(SysUser user) {
startPage();
List<SysUser> list = userService.selectUserList(user);
return getDataTable(list);
}
// 需要system:user:add权限
@PreAuthorize("@ss.hasPermi('system:user:add')")
@Log(title = "用户管理", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Valid @RequestBody SysUser user) {
if (!userService.checkUserNameUnique(user)) {
return error("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
}
user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
return toAjax(userService.insertUser(user));
}
}
#### 3.3.2 权限表达式
// SecurityUtils.java
@Component("ss")
public class SecurityUtils {
/**
* 验证用户是否具备某权限
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermi(String permission) {
if (StringUtils.isEmpty(permission)) {
return false;
}
LoginUser loginUser = getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
return false;
}
return hasPermissions(loginUser.getPermissions(), permission);
}
/**
* 验证用户是否不具备某权限,与 hasPermi 逻辑相反
*
* @param permission 权限字符串
* @return 用户是否不具备某权限
*/
public boolean lacksPermi(String permission) {
return !hasPermi(permission);
}
/**
* 验证用户是否具有以下任意一个权限
*
* @param permissions 以 LOGIN_AND_OR 为分隔符的权限列表
* @return 用户是否具有以下任意一个权限
*/
public boolean hasAnyPermi(String permissions) {
if (StringUtils.isEmpty(permissions)) {
return false;
}
for (String permission : permissions.split(LOGIN_AND_OR)) {
if (hasPermi(permission)) {
return true;
}
}
return false;
}
}
## 4. 攻击防护机制
### 4.1 XSS防护
#### 4.1.1 XSS过滤器
// XssFilter.java
@Component
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(
(HttpServletRequest) request);
chain.doFilter(xssRequest, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
#### 4.1.2 XSS请求包装器
// XssHttpServletRequestWrapper.java
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String[] getParameterValues(String parameter) {
String[] values = super.getParameterValues(parameter);
if (values == null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = cleanXSS(values[i]);
}
return encodedValues;
}
@Override
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
if (value == null) {
return null;
}
return cleanXSS(value);
}
@Override
public String getHeader(String name) {
String value = super.getHeader(name);
if (value == null) {
return null;
}
return cleanXSS(value);
}
private String cleanXSS(String value) {
// 过滤XSS攻击字符
value = value.replaceAll("<", "<").replaceAll(">", ">");
value = value.replaceAll("\\(", "(").replaceAll("\\)", ")");
value = value.replaceAll("'", "'");
value = value.replaceAll("eval\\((.*)\\)", "");
value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
value = value.replaceAll("script", "");
return value;
}
}
### 4.2 SQL注入防护
#### 4.2.1 SQL注入检测
// SqlUtil.java
public class SqlUtil {
/**
* SQL注入过滤
*
* @param str 待验证的字符串
*/
public static String escapeOrderBySql(String value) {
if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) {
throw new InvalidParameterException("参数不符合规范,不能进行查询");
}
return value;
}
/**
* 验证 order by 语法是否符合规范
*/
public static boolean isValidOrderBySql(String value) {
return value.matches(VALIDATION_PATTERN);
}
/**
* SQL关键字过滤
*/
public static String filterKeyword(String value) {
if (StringUtils.isNotEmpty(value)) {
value = value.toLowerCase();
String[] sqlKeywords = { "master", "truncate", "insert", "select", "delete", "update", "declare", "alert", "drop" };
for (String sqlKeyword : sqlKeywords) {
if (value.indexOf(sqlKeyword) != -1) {
throw new InvalidParameterException("参数存在SQL注入风险");
}
}
}
return value;
}
}
#### 4.2.2 MyBatis参数绑定
<!-- 使用参数绑定防止SQL注入 -->
<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader, r.role_name, r.role_key, ro.role_id, ro.role_name as role_name, ro.role_key as role_key
from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
left join sys_user_role ur on u.user_id = ur.user_id
left join sys_role r on r.role_id = ur.role_id
left join sys_role ro on ro.role_id = ur.role_id
where u.del_flag = '0'
<if test="userName != null and userName != ''">
AND u.user_name like concat('%', #{userName}, '%')
</if>
<if test="status != null and status != ''">
AND u.status = #{status}
</if>
<if test="phonenumber != null and phonenumber != ''">
AND u.phonenumber like concat('%', #{phonenumber}, '%')
</if>
<if test="params.beginTime != null and params.beginTime != ''">
AND date_format(u.create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d')
</if>
<if test="params.endTime != null and params.endTime != ''">
AND date_format(u.create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d')
</if>
<if test="deptId != null and deptId != 0">
AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set(#{deptId}, ancestors) ))
</if>
</select>
### 4.3 CSRF防护
#### 4.3.1 CSRF配置
// SecurityConfig.java
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// CSRF禁用(使用JWT时不需要CSRF)
.csrf(csrf -> csrf.disable())
// 其他配置...
return http.build();
}
}
## 5. 数据安全
### 5.1 密码加密
#### 5.1.1 BCrypt加密
// SecurityUtils.java
public class SecurityUtils {
/**
* 生成BCryptPasswordEncoder密码
*
* @param password 密码
* @return 加密字符串
*/
public static String encryptPassword(String password) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.encode(password);
}
/**
* 判断密码是否相同
*
* @param rawPassword 真实密码
* @param encodedPassword 加密后字符
* @return 结果
*/
public static boolean matchesPassword(String rawPassword, String encodedPassword) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.matches(rawPassword, encodedPassword);
}
}
#### 5.1.2 密码策略
// SysPasswordService.java
@Service
public class SysPasswordService {
@Autowired
private RedisCache redisCache;
private int maxRetryCount = 5;
private int lockTime = 10;
/**
* 登录账户密码错误次数缓存键名
*
* @param username 用户名
* @return 缓存键key
*/
private String getCacheKey(String username) {
return Constants.PWD_ERR_CNT_KEY + username;
}
public void validate(SysUser user, String password) {
AuthenticationManager authenticationManager = SpringUtils.getBean(AuthenticationManager.class);
try {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), password);
AuthenticationContextHolder.setContext(authenticationToken);
authenticationManager.authenticate(authenticationToken);
} catch (Exception e) {
throw new UserPasswordNotMatchException();
} finally {
AuthenticationContextHolder.clearContext();
}
}
/**
* 记录登录失败
*/
public void recordLoginFail(String username) {
String cacheKey = getCacheKey(username);
Integer retryCount = redisCache.getCacheObject(cacheKey);
if (retryCount == null) {
retryCount = 0;
}
retryCount++;
redisCache.setCacheObject(cacheKey, retryCount, lockTime, TimeUnit.MINUTES);
}
/**
* 清除登录失败记录
*/
public void clearLoginFailRecord(String username) {
redisCache.deleteObject(getCacheKey(username));
}
}
### 5.2 数据权限控制
#### 5.2.1 数据权限注解
// DataScope.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
/**
* 部门表的别名
*/
String deptAlias() default "";
/**
* 用户表的别名
*/
String userAlias() default "";
/**
* 权限字符 用于验证用户数据权限
*/
String permission() default "";
}
#### 5.2.2 数据权限切面
// DataScopeAspect.java
@Aspect
@Component
public class DataScopeAspect {
/**
* 全部数据权限
*/
public static final String DATA_SCOPE_ALL = "1";
/**
* 自定数据权限
*/
public static final String DATA_SCOPE_CUSTOM = "2";
/**
* 部门数据权限
*/
public static final String DATA_SCOPE_DEPT = "3";
/**
* 部门及以下数据权限
*/
public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
/**
* 仅本人数据权限
*/
public static final String DATA_SCOPE_SELF = "5";
/**
* 数据权限过滤关键字
*/
public static final String DATA_SCOPE = "data_scope";
@Before("@annotation(controllerDataScope)")
public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable {
clearDataScope(point);
handleDataScope(point, controllerDataScope);
}
protected void handleDataScope(final JoinPoint point, DataScope controllerDataScope) {
// 获取当前的用户
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNotNull(loginUser)) {
SysUser currentUser = loginUser.getUser();
// 如果是超级管理员,则不过滤数据
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) {
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
controllerDataScope.userAlias());
}
}
}
}
## 6. 审计日志系统
### 6.1 操作日志
#### 6.1.1 日志注解
// Log.java
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/**
* 模块
*/
String title() default "";
/**
* 功能
*/
BusinessType businessType() default BusinessType.OTHER;
/**
* 是否保存请求的参数
*/
boolean isSaveRequestData() default true;
/**
* 是否保存响应的参数
*/
boolean isSaveResponseData() default true;
}
#### 6.1.2 日志切面
// LogAspect.java
@Aspect
@Component
public class LogAspect {
@Around("@annotation(controllerLog)")
public Object doConcurrent(ProceedingJoinPoint joinPoint, Log controllerLog) throws Throwable {
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
String ip = IpUtils.getIpAddr();
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
controllerLog.setMethod(className + "." + methodName + "()");
// 设置请求方式
controllerLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog);
// 保存数据库
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
return result;
}
}
### 6.2 登录日志
#### 6.2.1 登录日志记录
// SysLoginService.java
@Service
public class SysLoginService {
@Autowired
private SysLogininforService logininforService;
/**
* 登录验证
*/
public String login(String username, String password, String code, String uuid) {
// 验证码校验
validateCaptcha(username, code, uuid);
// 登录前置校验
loginPreCheck(username, password);
// 用户验证
Authentication authentication = null;
try {
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (Exception e) {
if (e instanceof BadCredentialsException) {
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
} else {
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
throw new ServiceException(e.getMessage());
}
}
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
recordLoginInfo(loginUser.getUserId());
// 生成token
return tokenService.createToken(loginUser);
}
}
## 7. 安全配置参数
### 7.1 配置文件
#### 7.1.1 安全配置
# application.yml
# 安全配置
security:
# 用户配置信息
user:
# 用户名称
name: admin
# 用户密码
password: admin123
# 用户是否启用
enabled: true
# 用户是否锁定
locked: false
# 用户是否过期
expired: false
# 用户密码是否过期
passwordExpired: false
# 用户角色
roles: admin
# 验证码配置
captcha:
# 验证码开关
enabled: true
# 验证码类型 math 数组计算 char 字符验证
type: math
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
# 防止SQL注入攻击
sql:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
#### 7.1.2 JWT配置
# Token配置
token:
# 令牌自定义标识
header: Authorization
# 令牌密钥
secret: abcdefghijklmnopqrstuvwxyz
# 令牌有效期(默认30分钟)
expireTime: 30
# 令牌刷新时间(默认30分钟)
refreshTime: 30
# 令牌缓冲时间(默认5分钟)
bufferTime: 5
### 7.2 安全头配置
#### 7.2.1 安全响应头
// SecurityHeadersFilter.java
@Component
public class SecurityHeadersFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 防止XSS攻击
httpResponse.setHeader("X-XSS-Protection", "1; mode=block");
// 防止点击劫持
httpResponse.setHeader("X-Frame-Options", "DENY");
// 防止MIME类型嗅探
httpResponse.setHeader("X-Content-Type-Options", "nosniff");
// 严格传输安全
httpResponse.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
// 内容安全策略
httpResponse.setHeader("Content-Security-Policy", "default-src 'self'");
chain.doFilter(request, response);
}
}
## 8. 安全最佳实践
### 8.1 密码安全
#### 8.1.1 密码策略
// 密码验证规则
public class PasswordValidator {
public static boolean isValidPassword(String password) {
// 密码长度至少8位
if (password.length() < 8) {
return false;
}
// 必须包含大小写字母、数字和特殊字符
boolean hasUpper = password.matches(".*[A-Z].*");
boolean hasLower = password.matches(".*[a-z].*");
boolean hasNumber = password.matches(".*\\d.*");
boolean hasSpecial = password.matches(".*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?].*");
return hasUpper && hasLower && hasNumber && hasSpecial;
}
}
#### 8.1.2 密码历史
// 密码历史检查
@Service
public class PasswordHistoryService {
@Autowired
private SysUserMapper userMapper;
public boolean isPasswordUsedBefore(Long userId, String newPassword) {
// 检查最近5次密码是否重复
List<String> recentPasswords = userMapper.getRecentPasswords(userId, 5);
return recentPasswords.contains(encryptPassword(newPassword));
}
}
### 8.2 会话管理
#### 8.2.1 会话超时
// 会话超时配置
@Configuration
public class SessionConfig {
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
@EventListener
public void handleSessionDestroyed(HttpSessionDestroyedEvent event) {
// 清理用户缓存
String sessionId = event.getId();
// 清理相关缓存...
}
}
#### 8.2.2 并发会话控制
// 并发会话控制
@Service
public class SessionControlService {
@Autowired
private RedisCache redisCache;
public void checkConcurrentSessions(String username) {
String key = "user_sessions:" + username;
Set<String> sessions = redisCache.getCacheSet(key);
if (sessions.size() >= 3) { // 最多允许3个并发会话
// 踢出最早的会话
String oldestSession = sessions.iterator().next();
redisCache.deleteObject("token:" + oldestSession);
sessions.remove(oldestSession);
}
}
}
### 8.3 输入验证
#### 8.3.1 参数验证
// 参数验证注解
public class SysUser {
@NotBlank(message = "用户账号不能为空")
@Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")
private String userName;
@NotBlank(message = "用户昵称不能为空")
@Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符")
private String nickName;
@Email(message = "邮箱格式不正确")
@Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
private String email;
@Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符")
private String phonenumber;
}
#### 8.3.2 文件上传安全
// 文件上传安全检查
@Service
public class FileUploadService {
public void validateFile(MultipartFile file) {
// 检查文件大小
if (file.getSize() > 10 * 1024 * 1024) { // 10MB
throw new ServiceException("文件大小不能超过10MB");
}
// 检查文件类型
String contentType = file.getContentType();
if (!isAllowedFileType(contentType)) {
throw new ServiceException("不支持的文件类型");
}
// 检查文件扩展名
String originalFilename = file.getOriginalFilename();
if (!isAllowedExtension(originalFilename)) {
throw new ServiceException("不支持的文件扩展名");
}
}
private boolean isAllowedFileType(String contentType) {
String[] allowedTypes = {
"image/jpeg", "image/png", "image/gif",
"application/pdf", "application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
};
return Arrays.asList(allowedTypes).contains(contentType);
}
}
1799

被折叠的 条评论
为什么被折叠?



