RuoYi-Vue 框架学习

前言

        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("<", "&lt;").replaceAll(">", "&gt;");

        value = value.replaceAll("\\(", "&#40;").replaceAll("\\)", "&#41;");

        value = value.replaceAll("'", "&#39;");

        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') &gt;= 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') &lt;= 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);

    }

}

## 9. 安全监控和告警

### 9.1 安全事件监控

#### 9.1.1 异常登录监控
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值