From 0a126eb52068506321ffa4f9c363469c5b0c69ab Mon Sep 17 00:00:00 2001 From: Hanserwei <2628273921@qq.com> Date: Sat, 29 Nov 2025 15:19:35 +0800 Subject: [PATCH] feat(security): implement JWT-based authentication and authorization - Configured JWT token validation filter in security chain - Added user role mapping with new t_user_role table and UserRole entity - Implemented custom authentication entry point and access denied handler - Updated UserDetailService to load user roles from database - Added @PreAuthorize annotation support for method-level security - Refactored build scripts to use java-library plugin and proper dependency scope - Enhanced SQL schema with user role table and improved table comments - Added global exception handler for AccessDeniedException - Introduced ResponseCodeEnum constants for unauthorized and forbidden access - Integrated TokenAuthenticationFilter into Spring Security filter chain --- .idea/data_source_mapping.xml | 6 ++ .idea/modules.xml | 2 + sql/createTable.sql | 25 ++++- weblog-module-admin/build.gradle.kts | 13 ++- .../admin/config/WebSecurityConfig.java | 17 ++- weblog-module-common/build.gradle.kts | 19 ++-- .../common/domain/dataobject/UserRole.java | 64 +++++++++++ .../domain/repository/UserRoleRepository.java | 11 ++ .../common/enums/ResponseCodeEnum.java | 2 + .../exception/GlobalExceptionHandler.java | 8 ++ weblog-module-jwt/build.gradle.kts | 6 +- .../JwtAuthenticationSecurityConfig.java | 7 ++ .../jwt/filter/TokenAuthenticationFilter.java | 101 ++++++++++++++++++ .../jwt/handler/RestAccessDeniedHandler.java | 26 +++++ .../handler/RestAuthenticationEntryPoint.java | 32 ++++++ .../jwt/service/UserDetailServiceImpl.java | 18 +++- .../web/controller/TestController.java | 10 ++ 17 files changed, 339 insertions(+), 28 deletions(-) create mode 100644 .idea/data_source_mapping.xml create mode 100644 weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/UserRole.java create mode 100644 weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/UserRoleRepository.java create mode 100644 weblog-module-jwt/src/main/java/com/hanserwei/jwt/filter/TokenAuthenticationFilter.java create mode 100644 weblog-module-jwt/src/main/java/com/hanserwei/jwt/handler/RestAccessDeniedHandler.java create mode 100644 weblog-module-jwt/src/main/java/com/hanserwei/jwt/handler/RestAuthenticationEntryPoint.java diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml new file mode 100644 index 0000000..d636af0 --- /dev/null +++ b/.idea/data_source_mapping.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index a93abb3..f6c1c36 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -5,6 +5,8 @@ + + \ No newline at end of file diff --git a/sql/createTable.sql b/sql/createTable.sql index e5c8e1c..30ef788 100644 --- a/sql/createTable.sql +++ b/sql/createTable.sql @@ -1,3 +1,5 @@ +-- ==================================================================================================================== +-- ==================================================================================================================== -- 1. 创建一个函数,用于在数据更新时自动修改 update_time 字段 CREATE OR REPLACE FUNCTION set_update_time() RETURNS TRIGGER AS @@ -7,7 +9,6 @@ BEGIN RETURN NEW; END; $$ LANGUAGE plpgsql; - -- 2. 创建表(使用 BOOLEAN 替代 SMALLINT for is_deleted) CREATE TABLE t_user ( @@ -20,14 +21,30 @@ CREATE TABLE t_user -- 使用 BOOLEAN 逻辑删除,DEFAULT FALSE 对应 '0:未删除' is_deleted BOOLEAN NOT NULL DEFAULT FALSE ); - -- 3. 创建触发器,在每次 UPDATE 操作前调用函数 CREATE TRIGGER set_t_user_update_time BEFORE UPDATE ON t_user FOR EACH ROW EXECUTE FUNCTION set_update_time(); - -- 添加注释 COMMENT ON TABLE t_user IS '用户表(优化版)'; -COMMENT ON COLUMN t_user.is_deleted IS '逻辑删除:FALSE:未删除 TRUE:已删除'; \ No newline at end of file +COMMENT ON COLUMN t_user.is_deleted IS '逻辑删除:FALSE:未删除 TRUE:已删除'; +-- ==================================================================================================================== +-- ==================================================================================================================== + +-- ==================================================================================================================== +-- ==================================================================================================================== +CREATE TABLE t_user_role +( + id BIGSERIAL PRIMARY KEY, + username VARCHAR(60) NOT NULL, + role_name VARCHAR(60) NOT NULL, -- 重命名为 role_name 避免关键字冲突 + create_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_username ON t_user_role (username); + +COMMENT ON COLUMN t_user_role.role_name IS '角色名称'; +-- ==================================================================================================================== +-- ==================================================================================================================== \ No newline at end of file diff --git a/weblog-module-admin/build.gradle.kts b/weblog-module-admin/build.gradle.kts index 9c9b99c..9f6ec8b 100644 --- a/weblog-module-admin/build.gradle.kts +++ b/weblog-module-admin/build.gradle.kts @@ -1,15 +1,14 @@ plugins { - java + `java-library` } dependencies { - // Spring Security - implementation("org.springframework.boot:spring-boot-starter-security") - // Test - testImplementation("org.springframework.boot:spring-boot-starter-test") + api("org.springframework.boot:spring-boot-starter-security") implementation(project(":weblog-module-common")) - implementation(project(":weblog-module-jwt")) + api(project(":weblog-module-jwt")) + + testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.security:spring-security-test") -} \ No newline at end of file +} diff --git a/weblog-module-admin/src/main/java/com/hanserwei/admin/config/WebSecurityConfig.java b/weblog-module-admin/src/main/java/com/hanserwei/admin/config/WebSecurityConfig.java index eca740a..2811e04 100644 --- a/weblog-module-admin/src/main/java/com/hanserwei/admin/config/WebSecurityConfig.java +++ b/weblog-module-admin/src/main/java/com/hanserwei/admin/config/WebSecurityConfig.java @@ -1,12 +1,15 @@ package com.hanserwei.admin.config; import com.hanserwei.jwt.config.JwtAuthenticationSecurityConfig; +import com.hanserwei.jwt.handler.RestAccessDeniedHandler; +import com.hanserwei.jwt.handler.RestAuthenticationEntryPoint; import jakarta.annotation.Resource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; @@ -17,11 +20,18 @@ import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity +@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true) public class WebSecurityConfig { @Resource private JwtAuthenticationSecurityConfig jwtAuthenticationSecurityConfig; + @Resource + private RestAccessDeniedHandler restAccessDeniedHandler; + + @Resource + private RestAuthenticationEntryPoint restAuthenticationEntryPoint; + @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http @@ -38,7 +48,12 @@ public class WebSecurityConfig { .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) - // 5. 应用自定义配置 (核心变化) + // 5. 自定义未登录/权限不足的响应 + .exceptionHandling(exception -> exception + .authenticationEntryPoint(restAuthenticationEntryPoint) + .accessDeniedHandler(restAccessDeniedHandler) + ) + // 6. 应用自定义配置 (核心变化) .with(jwtAuthenticationSecurityConfig, Customizer.withDefaults()); return http.build(); } diff --git a/weblog-module-common/build.gradle.kts b/weblog-module-common/build.gradle.kts index 03d0289..c3bcf76 100644 --- a/weblog-module-common/build.gradle.kts +++ b/weblog-module-common/build.gradle.kts @@ -4,21 +4,18 @@ plugins { dependencies { - // guava implementation("com.google.guava:guava:33.5.0-jre") - // commons-lang3 implementation("org.apache.commons:commons-lang3:3.20.0") - // jpa - api("org.springframework.boot:spring-boot-starter-data-jpa") - // test - testImplementation("org.springframework.boot:spring-boot-starter-test") - // jackson implementation("com.fasterxml.jackson.core:jackson-databind") implementation("com.fasterxml.jackson.core:jackson-core") - // aop - implementation("org.springframework.boot:spring-boot-starter-aop") - // web + + api("org.springframework.boot:spring-boot-starter-data-jpa") api("org.springframework.boot:spring-boot-starter-web") - // postgresql + api("org.springframework.boot:spring-boot-starter-security") + api("org.springframework.boot:spring-boot-starter-aop") + runtimeOnly("org.postgresql:postgresql") + + testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.springframework.security:spring-security-test") } diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/UserRole.java b/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/UserRole.java new file mode 100644 index 0000000..fc2e87a --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/domain/dataobject/UserRole.java @@ -0,0 +1,64 @@ +package com.hanserwei.common.domain.dataobject; + +import jakarta.persistence.*; // 使用 Jakarta Persistence API (JPA 3.0+) +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; + +import java.io.Serial; +import java.io.Serializable; +import java.time.Instant; + +/** + * 用户角色表(t_user_role 对应实体) + */ +@Setter +@Getter +@Entity +@Table(name = "t_user_role", + indexes = { + @Index(name = "idx_username", columnList = "username") // 映射数据库中的 idx_username 索引 + }) +public class UserRole implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID (BIG SERIAL PRIMARY KEY) + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + /** + * 用户名 (VARCHAR(60) NOT NULL) + */ + @Column(name = "username", length = 60, nullable = false) + private String username; + + /** + * 角色名称 (VARCHAR(60) NOT NULL) + */ + @Column(name = "role_name", length = 60, nullable = false) + private String roleName; + + /** + * 创建时间 (TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP) + */ + @CreationTimestamp + @Column(name = "create_time", nullable = false, updatable = false) + private Instant createTime; + + // --- 构造函数 (Constructor) --- + + public UserRole() { + } + + public UserRole(String username, String roleName) { + this.username = username; + this.roleName = roleName; + } + +} \ No newline at end of file diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/UserRoleRepository.java b/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/UserRoleRepository.java new file mode 100644 index 0000000..1611660 --- /dev/null +++ b/weblog-module-common/src/main/java/com/hanserwei/common/domain/repository/UserRoleRepository.java @@ -0,0 +1,11 @@ +package com.hanserwei.common.domain.repository; + +import com.hanserwei.common.domain.dataobject.UserRole; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface UserRoleRepository extends JpaRepository { + + List queryAllByUsername(String username); +} diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/enums/ResponseCodeEnum.java b/weblog-module-common/src/main/java/com/hanserwei/common/enums/ResponseCodeEnum.java index 02e7128..d770a39 100644 --- a/weblog-module-common/src/main/java/com/hanserwei/common/enums/ResponseCodeEnum.java +++ b/weblog-module-common/src/main/java/com/hanserwei/common/enums/ResponseCodeEnum.java @@ -15,6 +15,8 @@ public enum ResponseCodeEnum implements BaseExceptionInterface { // ----------- 业务异常状态码 ----------- LOGIN_FAIL("20000", "登录失败"), USERNAME_OR_PWD_ERROR("20001", "用户名或密码错误"), + UNAUTHORIZED("20002", "无访问权限,请先登录!"), + FORBIDDEN("20004", "演示账号仅支持查询操作!") ; // 异常码 diff --git a/weblog-module-common/src/main/java/com/hanserwei/common/exception/GlobalExceptionHandler.java b/weblog-module-common/src/main/java/com/hanserwei/common/exception/GlobalExceptionHandler.java index 0b8f107..c9fe5be 100644 --- a/weblog-module-common/src/main/java/com/hanserwei/common/exception/GlobalExceptionHandler.java +++ b/weblog-module-common/src/main/java/com/hanserwei/common/exception/GlobalExceptionHandler.java @@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.security.access.AccessDeniedException; import java.util.Optional; @ControllerAdvice @@ -76,4 +77,11 @@ public class GlobalExceptionHandler { return Response.fail(errorCode, errorMessage); } + @ExceptionHandler({ AccessDeniedException.class }) + public void throwAccessDeniedException(AccessDeniedException e) throws AccessDeniedException { + // 捕获到鉴权失败异常,主动抛出,交给 RestAccessDeniedHandler 去处理 + log.info("============= 捕获到 AccessDeniedException"); + throw e; + } + } \ No newline at end of file diff --git a/weblog-module-jwt/build.gradle.kts b/weblog-module-jwt/build.gradle.kts index bcb19b2..47a5255 100644 --- a/weblog-module-jwt/build.gradle.kts +++ b/weblog-module-jwt/build.gradle.kts @@ -2,12 +2,10 @@ plugins { `java-library` } -group = "com.hanserwei.jwt" -version = project.parent?.version ?: "0.0.1-SNAPSHOT" - dependencies { - implementation("org.springframework.boot:spring-boot-starter-security") + api("org.springframework.boot:spring-boot-starter-security") implementation(project(":weblog-module-common")) + implementation("org.apache.commons:commons-lang3:3.20.0") // jwt api(libs.jjwt.api) diff --git a/weblog-module-jwt/src/main/java/com/hanserwei/jwt/config/JwtAuthenticationSecurityConfig.java b/weblog-module-jwt/src/main/java/com/hanserwei/jwt/config/JwtAuthenticationSecurityConfig.java index 89e4c97..96cb19d 100644 --- a/weblog-module-jwt/src/main/java/com/hanserwei/jwt/config/JwtAuthenticationSecurityConfig.java +++ b/weblog-module-jwt/src/main/java/com/hanserwei/jwt/config/JwtAuthenticationSecurityConfig.java @@ -1,6 +1,7 @@ package com.hanserwei.jwt.config; import com.hanserwei.jwt.filter.JwtAuthenticationFilter; +import com.hanserwei.jwt.filter.TokenAuthenticationFilter; import com.hanserwei.jwt.handler.RestAuthenticationFailureHandler; import com.hanserwei.jwt.handler.RestAuthenticationSuccessHandler; import jakarta.annotation.Resource; @@ -20,6 +21,9 @@ public class JwtAuthenticationSecurityConfig extends SecurityConfigurerAdapter claimsJws = jwtTokenHelper.parseToken(token); + + // 4. 获取用户名 + // JJWT 0.12+ 建议使用 getPayload() 替代 getBody() + // 在 Helper 中生成 Token 时使用的是 .subject(username),所以这里取 Subject + String username = claimsJws.getPayload().getSubject(); + + // 5. 组装认证信息 (如果当前上下文没有认证信息) + if (StringUtils.isNotBlank(username) && Objects.isNull(SecurityContextHolder.getContext().getAuthentication())) { + + // 查询数据库获取用户完整信息 (包含权限) + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + + // 构建 Spring Security 的认证 Token + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); + + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + // 6. 将认证信息存入 SecurityContext + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } catch (AuthenticationException e) { + // 7. 异常处理 + // 捕获 JwtTokenHelper 抛出的 BadCredentialsException 或 CredentialsExpiredException + // 如果 Token 存在但是无效/过期,直接交给 EntryPoint 处理响应 (通常返回 401) + // 并 return 结束当前过滤器,不再往下执行 + authenticationEntryPoint.commence(request, response, e); + return; + } catch (Exception e) { + // 处理其他未预料到的异常 + log.error("JWT处理过程中发生未知错误", e); + authenticationEntryPoint.commence(request, response, new AuthenticationException("系统内部认证错误") { + }); + return; + } + } + } + + // 8. 放行请求 (如果没有 Token 或者 Token 校验通过,继续执行下一个过滤器) + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/weblog-module-jwt/src/main/java/com/hanserwei/jwt/handler/RestAccessDeniedHandler.java b/weblog-module-jwt/src/main/java/com/hanserwei/jwt/handler/RestAccessDeniedHandler.java new file mode 100644 index 0000000..e0e88cb --- /dev/null +++ b/weblog-module-jwt/src/main/java/com/hanserwei/jwt/handler/RestAccessDeniedHandler.java @@ -0,0 +1,26 @@ +package com.hanserwei.jwt.handler; + +import com.hanserwei.common.enums.ResponseCodeEnum; +import com.hanserwei.common.utils.Response; +import com.hanserwei.jwt.utils.ResultUtil; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Slf4j +@Component +public class RestAccessDeniedHandler implements AccessDeniedHandler { + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { + log.warn("登录成功访问收保护的资源,但是权限不够: ", accessDeniedException); + // 预留,后面引入多角色时会用到 + ResultUtil.fail(response, Response.fail(ResponseCodeEnum.FORBIDDEN)); + } +} diff --git a/weblog-module-jwt/src/main/java/com/hanserwei/jwt/handler/RestAuthenticationEntryPoint.java b/weblog-module-jwt/src/main/java/com/hanserwei/jwt/handler/RestAuthenticationEntryPoint.java new file mode 100644 index 0000000..f851e3f --- /dev/null +++ b/weblog-module-jwt/src/main/java/com/hanserwei/jwt/handler/RestAuthenticationEntryPoint.java @@ -0,0 +1,32 @@ +package com.hanserwei.jwt.handler; + +import com.hanserwei.common.enums.ResponseCodeEnum; +import com.hanserwei.common.utils.Response; +import com.hanserwei.jwt.utils.ResultUtil; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.InsufficientAuthenticationException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Slf4j +@Component +public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + log.warn("用户未登录访问受保护的资源: ", authException); + if (authException instanceof InsufficientAuthenticationException) { + ResultUtil.fail(response, HttpStatus.UNAUTHORIZED.value(), Response.fail(ResponseCodeEnum.UNAUTHORIZED)); + return; + } + + ResultUtil.fail(response, HttpStatus.UNAUTHORIZED.value(), Response.fail(authException.getMessage())); + } +} diff --git a/weblog-module-jwt/src/main/java/com/hanserwei/jwt/service/UserDetailServiceImpl.java b/weblog-module-jwt/src/main/java/com/hanserwei/jwt/service/UserDetailServiceImpl.java index 4f521c2..ab887f3 100644 --- a/weblog-module-jwt/src/main/java/com/hanserwei/jwt/service/UserDetailServiceImpl.java +++ b/weblog-module-jwt/src/main/java/com/hanserwei/jwt/service/UserDetailServiceImpl.java @@ -1,14 +1,18 @@ package com.hanserwei.jwt.service; import com.hanserwei.common.domain.dataobject.User; +import com.hanserwei.common.domain.dataobject.UserRole; import com.hanserwei.common.domain.repository.UserRepository; +import com.hanserwei.common.domain.repository.UserRoleRepository; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import java.util.List; import java.util.Objects; @Service @@ -18,6 +22,9 @@ public class UserDetailServiceImpl implements UserDetailsService { @Resource private UserRepository userRepository; + @Resource + private UserRoleRepository userRoleRepository; + @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 从数据库查询用户信息 @@ -27,10 +34,19 @@ public class UserDetailServiceImpl implements UserDetailsService { throw new UsernameNotFoundException("用户不存在"); } + List userRoles = userRoleRepository.queryAllByUsername(username); + // 转换为数组 + String[] roleArr = new String[0]; + if (!CollectionUtils.isEmpty(userRoles)) { + roleArr = userRoles.stream() + .map(UserRole::getRoleName) + .toArray(String[]::new); + } + // authorities 用于指定角色,这里写死为 ADMIN 管理员 return org.springframework.security.core.userdetails.User.withUsername(user.getUsername()) .password(user.getPassword()) - .authorities("ADMIN") + .authorities(roleArr) .build(); } } \ No newline at end of file diff --git a/weblog-web/src/main/java/com/hanserwei/web/controller/TestController.java b/weblog-web/src/main/java/com/hanserwei/web/controller/TestController.java index 4735912..f3783c8 100644 --- a/weblog-web/src/main/java/com/hanserwei/web/controller/TestController.java +++ b/weblog-web/src/main/java/com/hanserwei/web/controller/TestController.java @@ -1,9 +1,11 @@ package com.hanserwei.web.controller; import com.hanserwei.common.aspect.ApiOperationLog; +import com.hanserwei.common.utils.Response; import com.hanserwei.web.model.User; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.validation.annotation.Validated; @@ -35,4 +37,12 @@ public class TestController { return ResponseEntity.ok("参数没有任何问题"); } + @PostMapping("/admin/update") + @ApiOperationLog(description = "测试更新接口") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public Response testUpdate() { + log.info("更新成功..."); + return Response.success(); + } + } \ No newline at end of file