news 2026/9/14 13:44:38

SpringBoot+Spring Security实现竞赛系统认证与权限控制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Spring Security实现竞赛系统认证与权限控制

简介:一份面向高校毕业设计及课程设计场景的“大学生竞赛管理系统”完整项目源码,基于 SpringBoot + Spring Security + Jwt 构建后端接口,配合 Vue.js + Element UI + axios + MyBatis Plus 实现了清晰的前后端分离。项目聚焦大学生竞赛的报名、管理与信息展示,是一套可运行的完整系统,适合作为毕业设计选题或课程设计参考。资源包共 53 个文件,以 31 个 Vue 组件、11 个 JS 脚本、3 个 CSS 样式及若干 JSON 配置为主,另有 README 等说明文档,整体仅 230KB,结构精简,适合快速理解项目骨架。目前已有 85 人浏览学习。内容包含前端页面组件、路由与状态管理、接口调用封装,以及后端基于 Spring Security 与 JWT 的权限认证和业务逻辑示例,可帮助读者掌握登录鉴权、赛事报名、信息管理等典型模块;源码目录层次分明,便于在毕业设计答辩或课程作业中直接复用、扩展或二次开发。

1. 大学生竞赛管理系统:SpringBoot + Spring Security 的选型从哪里开始

高校里的竞赛报名往往在截止前半小时达到峰值,几十支队伍同时提交材料,管理员还要在几百条记录里逐个核对成员资格。比功能缺失更麻烦的是权限边界模糊:报过名的学生能改掉已经审核过的名单吗?评审老师能不能看到其他老师的打分记录?很多人会用if (user.getRole().equals("ADMIN"))这种散落的脏代码来应付。这个标题要解决两件事:用SpringBoot快速把竞赛管理系统的CRUD跑通,用Spring Security把登录、授权、接口保护做成完整链路。适合正在做课设、毕设或准备接手校内竞赛项目的开发者,你需要的不只是能跑的demo,还要知道版本怎么选、过滤器链怎么配、403为什么查不出原因。

2. 用SpringBoot搭竞赛管理系统的项目骨架与数据模型

2.1 创建项目时怎么选版本,避免SpringBoot版本太高的坑

很多人拿到一个“.zip”项目后,第一件事是解压后直接运行,结果遇到javax.servlet包不存在或者Security配置类找不到。常见原因就是SpringBoot版本和本地JDK不匹配。目前最常见的选择是:JDK8环境用SpringBoot 2.7.x,JDK17及以上用SpringBoot 3.x。SpringBoot 3.x把javax.都换成了jakarta.,从旧版复制过来的代码很多人就在这里卡住。Spring Security的配置方式也随之变化,5.7以前常用的WebSecurityConfigurerAdapter在5.7里标了deprecated,6.0以后直接移除。所以在创建项目阶段,先去 start.spring.io 或者IDEA的Spring Initializr里核对依赖版本,比后面排错省时间。

我一般在pom.xml里固定依赖如下:

<!-- SpringBoot父工程统一管理依赖版本 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <relativePath/> </parent> <dependencies> <!-- Web、Security、JPA、MySQL驱动 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> </dependencies>

这段配置的核心是spring-boot-starter-parent,它统一管理了SpringBoot、Spring Framework、Spring Security的版本号,不需要自己再去对齐各个jar的版本。spring-boot-starter-security会把Security自动装配进Web应用,也就是说只要引入依赖,所有接口默认都会被拦截,控制台会给你一串随机的登录密码。很多人第一次看到这个现象会以为项目坏了,其实是Security的默认保护机制生效了。JPA在这里用来管理用户、队伍、竞赛表的关联关系,如果你更熟悉MyBatis-Plus,可以换成mybatis-plus-boot-starter,只是后面基于SQL的权限控制要自己写一些XML。

为了减少后续返工,我建议把版本和ORM选型先确定下来。下面是我常对比的几项:

选型点SpringBoot 2.7.x + JPASpringBoot 3.x + MyBatis-Plus
JDK要求JDK8/11/17JDK17及以上
Security配置SecurityFilterChain Bean同样是Bean,但命名空间是jakarta
表关系处理@ManyToMany/@OneToMany直接映射自己写SQL关联
适合场景表结构清晰、关系多团队习惯SQL,复杂的动态查询多
常见坑mysql-connector-java的坐标带版本驱动类名变成了com.mysql.cj.jdbc.Driver

注意不要把版本锁得太死,比如Spring Security OAuth2.0服务搭建依赖的SDK版本,和普通Servlet应用的Security版本是不同分支,等做到单点登录时再单独引入。

2.2 竞赛管理系统的核心表设计与表关系

竞赛管理系统最典型的三张核心表是:用户表、竞赛表、队伍表。用户表存放学生、教师、管理员三类账号;竞赛表存放报名起止时间和当前状态;队伍表把多个学生组织起来,并记录审核状态。设计时不要为了图省事把这些字段全塞进一张表,因为Spring Security的授权判断会频繁读取角色和状态,拆开后可以用索引覆盖。

下面是一份可以直接执行的DDL,我写的时候刻意控制了字段数量:

-- 用户表:角色用大写英文,方便和Security的ROLE_前缀配合 CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, real_name VARCHAR(50), role VARCHAR(20) NOT NULL DEFAULT 'STUDENT', enabled TINYINT NOT NULL DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 竞赛表:报名起止时间由后端判定,不信任前端传参 CREATE TABLE competition ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(200) NOT NULL, register_start DATETIME NOT NULL, register_end DATETIME NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'DRAFT' ); -- 队伍表:status用于审核流程 CREATE TABLE team ( id BIGINT PRIMARY KEY AUTO_INCREMENT, competition_id BIGINT NOT NULL, leader_id BIGINT NOT NULL, team_name VARCHAR(100) NOT NULL, status VARCHAR(20) DEFAULT 'PENDING' ); -- 队伍成员关联表 CREATE TABLE team_member ( team_id BIGINT NOT NULL, user_id BIGINT NOT NULL, role_in_team VARCHAR(20), PRIMARY KEY (team_id, user_id) );

这份SQL里,sys_user.role的值建议直接存大写英文:ADMINTEACHERSTUDENT。这样和Spring Security默认的ROLE_前缀配合时,只需在代码里写hasRole('ADMIN')即可。team.status用于报名审核,推荐取值PENDINGAPPROVEDREJECTED,它是后面授权时的一个重要资源状态,底下会细讲。team_member这张关联表决定了谁属于哪个队伍,审查资料时通过队伍ID反查成员,就不用在用户表里存放冗长的JSON。

参数说明:如果希望一个学生同时加入多个队伍,主键用(team_id, user_id)就够了;如果要求一个学生只能参加一项竞赛,可以在team_member上加competition_id字段,并用联合唯一索引限制同一个人在同一场竞赛只能出现在一个队伍中。我这里没有加competition_id,因为队伍表已经通过competition_id确定了竞赛,遇到更严格的场景再补。

2.3 配置数据源,并验证Security初始行为

application.yml 在SpringBoot项目里承担自动装配的配置入口。数据源配置错误时,项目会启动失败或报无法打开连接,Security相关的自动装配也会一起失效。下面是我在本地开发会用的最小配置:

spring: datasource: url: jdbc:mysql://localhost:3306/contest?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: root jpa: hibernate: ddl-auto: update # 开发期自动建表,上线前改成validate show-sql: true # 联调时打印SQL,压测时关闭

这里的ddl-auto: update在开发阶段帮我在建表时自动补字段,但上线前要改成validate或直接关闭,否则生产环境一旦有人改了数据库,Hibernate可能自动改结构,像竞赛这种要留痕的场景,表结构变动是没有审计的。show-sql: true会打印JPA生成的SQL,报名高峰期日志量很大,本地联调时开着,压测时关掉。

配置好数据源后启动项目,观察控制台日志。你会看到类似Using generated security password: a8c6...的内容,说明Security已经接管了所有接口。这时不要急着写登录页面,先把第3章的UserDetailsService落地,Spring Security才能从数据库里取账号信息,而不是每次启动都随机生成密码。

这里补充一句SpringBoot自动装配原理的实际应用:Spring Boot会读取spring.factoriesAutoConfiguration.imports里的配置类,Security自动配置类在没有自定义SecurityFilterChainBean时生效。一旦你注册了自己的过滤器链,这些默认行为就会被覆盖,这也是我们下一步要做的。

3. Spring Security 认证:从表单登录到JWT无状态接口

3.1 SecurityFilterChain:Spring Security 6之后唯一的配置入口

Spring Security的配置核心不再是继承WebSecurityConfigurerAdapter,而是一个SecurityFilterChainBean。这种方法把「哪些路径放行、哪些路径需要认证、用不用Session、是否开启CSRF」集中在一个方法里,比之前分散的配置更容易审查。

一个适合前后端分离竞赛系统的配置如下:

@Configuration @EnableWebSecurity public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) { this.jwtAuthenticationFilter = jwtAuthenticationFilter; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // 前后端分离使用JWT,关闭CSRF;保留Session时不要关 .csrf(csrf -> csrf.disable()) // 不创建会话,每个请求都通过JWT认证 .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth // 登录和注册接口放行 .requestMatchers("/api/auth/login", "/api/register").permitAll() // 管理端接口只允许ADMIN角色 .requestMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) // JWT过滤器放在用户名密码认证过滤器之前 .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }

逻辑说明:csrf(csrf -> csrf.disable())表示关闭跨站请求伪造防护,因为纯API场景使用了JWT,服务端不依赖Cookie会话;如果你保留了Session,这里一定不能关,否则登录后的接口会成为CSRF攻击目标。sessionCreationPolicy(STATELESS)告诉Spring Security不要创建HttpSession,每次请求都从Authorization头解析用户信息。requestMatchers("/api/auth/login", "/api/register").permitAll()放行登录和注册接口,/api/admin/**要求管理员角色,其余所有请求都必须登录。

参数说明:UsernamePasswordAuthenticationFilter.class是一个位置标记,Spring Security的过滤器链按顺序执行,JWT过滤器放在它前面,可以让JWT解析结果先于用户名密码认证生效。注意addFilterBefore的第二个参数是类对象,不是实例,写错会直接在启动时报ClassCastException。

很多老牌教程里会看到configure(AuthenticationManagerBuilder)这种写法,那是Spring Security 5.6以前的风格。现在的框架版本里,WebSecurityConfigurerAdapter类已经不存在了,复制过来的代码需要改成上述Bean形式。这个区别几乎占了SpringBoot/Spring Security面试题中“旧项目怎么升级Security”的一半内容。

3.2 从数据库加载用户:UserDetailsService与BCrypt密码

在学生、教师、管理员共存的系统里,UserDetailsService是认证的数据来源。它的返回值是UserDetails,而我们的sys_user表里还包含了姓名、创建时间等字段,不建议把实体直接返回给Security,而是把Security需要的用户名、密码、角色组装进去。下面是一个实现:

@Service public class UserDetailsServiceImpl implements UserDetailsService { private final UserRepository userRepository; public UserDetailsServiceImpl(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserDO user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("用户不存在")); return org.springframework.security.core.userdetails.User .withUsername(user.getUsername()) // 密码保持数据库里的BCrypt密文,由PasswordEncoder负责比对 .password(user.getPassword()) // 自动加ROLE_前缀 .roles(user.getRole()) // enabled字段控制是否禁用 .disabled(!user.getEnabled()) .build(); } }

逻辑说明:查询数据库后,先判断用户是否存在,这是第一道检查;随后把密码原样交给Security去比对,比对规则由PasswordEncoder决定。roles(user.getRole())会为角色自动加上ROLE_前缀,比如数据库中存的是ADMIN,最终生成的权限就是ROLE_ADMIN。这样在@PreAuthorize里写hasRole('ADMIN')时才能对上。

这里有个必须配套完成的Bean:

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

这件事不做的话,注册用户时即使密码是明文,认证时Security也会用BCrypt去比,导致永远登录失败。注册接口里每次保存密码前,都要调用passwordEncoder.encode(rawPassword)

参数说明:BCryptPasswordEncoder的强度字段默认是10,可以传入更高的值比如12,但每次哈希计算耗时也会增加。竞赛系统的登录频率不高,用默认值就够了。同一个明文密码每次加密结果不同,这是因为BCrypt会自动生成随机盐,所以不要用等值查询来比密码。

3.3 JWT认证过滤器:把token解析成Authentication

如果采用前后端分离,前端登录后拿到一个JWT,之后每次请求在Authorization头带上Bearer前缀。服务端不需要Session,只需要在过滤器中解析并校验这个token是否由我们的密钥签发。核心过滤器代码如下:

@Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; private final UserDetailsServiceImpl userDetailsService; public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider, UserDetailsServiceImpl userDetailsService) { this.jwtTokenProvider = jwtTokenProvider; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String header = request.getHeader("Authorization"); if (header != null && header.startsWith("Bearer ")) { String token = header.substring(7); if (jwtTokenProvider.validateToken(token)) { String username = jwtTokenProvider.getUsernameFromToken(token); UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails( new WebAuthenticationDetailsSource().buildDetails(request)); // 把认证结果放进SecurityContext,后续授权判断才能拿到用户信息 SecurityContextHolder.getContext().setAuthentication(authentication); } } chain.doFilter(request, response); } }

逻辑说明:这个过滤器只处理带了Authorization头的请求。token被解析出用户名后,我们会重新调用loadUserByUsername从数据库拿最新的用户状态。这样做的代价是多一次查询,但好处是用户被封禁或删除时,JWT还没过期也能立刻失效;如果要追求极致性能,可以把角色直接写进token,省去这次数据库访问。UsernamePasswordAuthenticationToken构造函数的三个参数分别是principalcredentialsauthorities,业务代码里通常通过principal拿到当前登录用户。

参数说明:header.substring(7)是因为Bearer正好7个字符;如果前端没按约定加这个前缀,解析出来的就是空token,会被validateToken拒绝。OncePerRequestFilter保证一个请求只执行一次过滤器,避免在转发场景下重复认证。

JwtTokenProvider被反复调用,它的内部一般包含生成token和验证token两个方法。生成时可以用io.jsonwebtoken.Jwts,示例钥匙长度在教程中经常被忽略:用HS256算法时,密钥长度不能少于256位,否则一部分JDK会抛WeakKeyException。这就是为什么很多照着教程敲的学生在看到“密码学弱密钥”报错时一脸茫然。

JWT相关参数推荐值说明
过期时间7200秒竞赛报名接口操作较多,但不要设24小时,泄风险会变大
密钥至少32字节随机字符串写入application.yml,不要硬编码在代码里
RefreshToken需要如果支持移动端,建议用OAuth2.0的refresh_token方案
claim中的角色可选为了实时权限,建议不放入角色,每次从UserDetails中获取

4. Spring Security权限模型落地:守住竞赛项目中的报名、审核与评分

4.1 RBAC在Spring Security中的两种授权写法

竞赛管理系统通常不只有“登录才能访问”,还要细化到“学生只能报名和自己有关的队伍,评委只能看被分配给自己的作品”。Spring Security提供了两层控制:URL级和方法级。URL级在SecurityFilterChain里配,适合粗粒度管理,比如所有/api/admin/**接口都只有管理员能进;方法级通过注解写在业务接口上,适合处理“同一资源不同角色不同行为”的场景。

在讲解注解之前,先明确两个概念的差别。hasRole('ADMIN')会在判断时自动补全ROLE_前缀,要求当前用户的权限集合里存在ROLE_ADMINhasAuthority('competition:register')则不做任何前缀处理,需要权限记录里直接存储competition:register这样的权限代码。两者没有绝对优劣,角色适合稳定不常变的功能模块,权限码适合需要给不同角色灵活分配操作的复杂系统。

表达式写法适合场景
hasRolehasRole('ADMIN')管理员后台、教师评审端等固定角色
hasAnyRolehasAnyRole('TEACHER','ADMIN')评分接口,允许教师和管理员共同访问
hasAuthorityhasAuthority('team:register')需要给普通用户单独开放权限码的RBAC实现
hasPermissionhasPermission('#teamId','Team')数据级权限,需要实现PermissionEvaluator

如果只是做课设,用hasRolehasAnyRole就够了,能把接口权限说清楚。如果要做到细粒度数据隔离,那就要实现PermissionEvaluator,工作量会明显上升。

4.2 用@EnableMethodSecurity和@PreAuthorize守护报名接口

我建议在Controller方法上写@PreAuthorize,而不是在Service里用if手工判断角色。因为注解的声明式写法能让权限规则和业务逻辑一起被审查,别人接手时看到注解就知道这个接口是给谁用的。SpringBoot 2.7需要在配置类上加@EnableGlobalMethodSecurity(prePostEnabled = true),而SpringBoot 3.x引入了@EnableMethodSecurity,它默认开启@PreAuthorize@PostAuthorize

下面是一个竞赛报名接口的写法:

@RestController @RequestMapping("/api/competition") public class CompetitionController { private final CompetitionService competitionService; public CompetitionController(CompetitionService competitionService) { this.competitionService = competitionService; } // 只有学生角色才能报名 @PreAuthorize("hasRole('STUDENT')") @PostMapping("/{competitionId}/register") public Result register(@PathVariable Long competitionId) { LoginUser current = (LoginUser) SecurityContextHolder.getContext() .getAuthentication().getPrincipal(); return competitionService.register(current.getId(), competitionId); } }

逻辑说明:@PreAuthorize("hasRole('STUDENT')")在方法执行前校验当前用户是否拥有学生角色,如果不满足,Spring Security会直接抛出AccessDeniedException,整个方法体不会进入。SecurityContextHolder.getContext().getAuthentication().getPrincipal()是取出当前登录用户的标准写法,但要注意这里拿到的对象必须是你之前放进UsernamePasswordAuthenticationTokenUserDetails实例,所以强转前要确认类型。

参数说明:LoginUser建议是自定义的UserDetails实现类,里面包含用户ID。不要试图从username去反查用户ID,那样会多一次无谓的查询。曾经有同事把Principal理解成前端传进来的用户名,结果在@RequestParam里定义了一个同名的principal参数,导致Spring Security解析混乱,这是命名冲突造成的一个很隐性错误。

4.3 审核/评分接口的权限边界与403排错

竞赛系统的审核流程一般是:学生提交队伍材料,管理员或教师审核通过,评审阶段由教师打分。这三类操作需要的权限完全不同。URL级已经在SecurityConfig里把/api/admin/**限定为管理员,那么评委打分的接口可以放在/api/review/**下,通过方法级注解控制为教师或管理员。

@RestController @RequestMapping("/api/review") public class ReviewController { private final ReviewService reviewService; public ReviewController(ReviewService reviewService) { this.reviewService = reviewService; } // 教师和管理员都可评分 @PreAuthorize("hasAnyRole('TEACHER','ADMIN')") @PostMapping("/{teamId}/score") public Result score(@PathVariable Long teamId, @RequestBody ScoreRequest request) { return reviewService.score(teamId, request.getScore()); } }

这里的hasAnyRole('TEACHER','ADMIN')表示两种角色都能调用,评委本人是不是被分配到这个队伍,还需在reviewService.score里再做一次数据级校验。没有数据级权限控制的系统,容易出现一个教师看到所有队伍打分结果的越权问题。如果你在排错时遇到403,先按下表排查,大多数情况不是代码逻辑问题,而是配置细节问题:

症状最常见原因处理方式
所有登录请求都403SecurityConfig没放行登录接口检查permitAll是否覆盖了正确路径
只有学生403角色前缀对不上数据库role里存了ROLE_吗?如果存了,用hasAuthority('ROLE_ADMIN')
带token也403JWT过滤器没被加入链路检查addFilterBefore的过滤器顺序
普通请求偶尔403CSRF未关闭且前端没带tokenAPI环境csrf.disable()
方法上@PreAuthorize失效没开@EnableGlobalMethodSecurity@EnableMethodSecurity在配置类或启动类上补注解

排错时不要只盯着接口的返回值,先把过滤器的日志级别调到DEBUG,在application.yml里设logging.level.org.springframework.security=DEBUG。这样能看到每次请求命中了哪条授权规则。竞赛系统上线前,建议把所有角色都跑一遍,用不同账号访问同一个接口,确认预期中的401/403都是一致的。

5. 大学生竞赛管理系统验收技巧:用测试和参数校验守住截止时间

5.1 用Spring Security Test模拟三种角色

接口自测时,手动登录再复制token很慢。Spring Security Test提供了@WithMockUser,可以跳过完整登录流程,直接让请求带上指定角色。这在测试@PreAuthorize规则时非常高效。在pom.xml中加入spring-boot-starter-testspring-security-test依赖,然后写一个MockMvc测试:

@SpringBootTest @AutoConfigureMockMvc public class RegisterApiTest { @Autowired private MockMvc mockMvc; @Test @WithMockUser(username = "stu01", roles = "STUDENT") void student_can_register_own_team() throws Exception { mockMvc.perform(post("/api/competition/1/register")) .andExpect(status().isOk()); } @Test @WithMockUser(username = "teacher01", roles = "TEACHER") void teacher_cannot_register_team() throws Exception { mockMvc.perform(post("/api/competition/1/register")) .andExpect(status().isForbidden()); } }

@WithMockUser里的roles值不需要加ROLE_前缀,测试框架会自动补全。这个测试验证的是授权规则,不是认证流程;如果要测试JWT解析,需要自己构造带Authorization头的请求。实测时我还会加一条:用管理员账号访问学生报名接口,预期403,防止未来有人改SecurityConfig时把角色放开。

5.2 报名接口的高危参数校验清单

竞赛报名接口涉及上传材料与截止时间,最大的风险是前端把截止时间改掉后直接提交。常见的做法是在Service里用LocalDateTime.now()对比competition.getRegisterEnd(),而不是让页面传入注册时间。团队人数用@Min@Max限制,上传文件名用UUID重命名后再存盘,可以同时避开路径穿越和重名覆盖。

还有一个容易被忽略的检查项:SpringBoot Actuator的heapdump端点。如果项目引入了spring-boot-starter-actuator,生产环境一定要用management.endpoints.web.exposure.include=health,info限制暴露,否则/actuator/heapdump会把堆转储文件泄露出去,里面有敏感信息。把这几条写进验收用例:截止时间不信任前端、队伍人数边界、文件名校验、Actuator端口关闭。跑完这些用例后,再回到浏览器里用学生、教师、管理员三个账号分别走一遍报名和审核流程,检查日志里是否出现非预期的匿名访问记录。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 13:44:32

信创环境下SNMP协议栈选型:开源、免费SDK与国产自研怎么选?

先聊个挺典型的交付场景。单位要上信创改造&#xff0c;网络运维平台要从原来的底层环境整套往国产化迁移&#xff0c;操作系统换成麒麟V10&#xff0c;数据库换达梦&#xff0c;芯片平台是飞腾或者鲲鹏。平台要纳管上千台交换机、路由器、服务器&#xff0c;采集CPU、内存、接…

作者头像 李华
网站建设 2026/9/14 13:43:12

LTE CA认证测试实战:CE与FCC路径解析及整改指南

上个月在实验室做一台多模终端的FCC预测试&#xff0c;单载波模式下B2、B4、B7各项指标都干干净净&#xff0c;结果一打开上行CA&#xff08;载波聚合&#xff09;配置&#xff0c;带外杂散直接飚高了快10dB&#xff0c;几乎把整个验证计划打乱。这个场景在LTE CA认证测试里太常…

作者头像 李华
网站建设 2026/9/14 13:42:18

Prism框架实战:模块化WPF应用开发指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 13:38:44

DeepSeek Harness配置实战:通用设置与Agent预设拆解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华