diff --git a/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java b/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java index 5c7dda0..47259e2 100644 --- a/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java +++ b/src/main/java/asia/yulinling/workflow/config/SecurityConfig.java @@ -1,5 +1,6 @@ package asia.yulinling.workflow.config; +import asia.yulinling.workflow.scope.RequestScopeData; import asia.yulinling.workflow.security.*; import asia.yulinling.workflow.utils.JwtUtil; import jakarta.servlet.http.HttpServletRequest; @@ -34,6 +35,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic @Slf4j public class SecurityConfig { + private final RequestScopeData scopeData; private final JwtUserDetailsService jwtUserDetailsService; private final JwtRbacAuthenticationService jwtRbacAuthenticationService; @@ -56,19 +58,19 @@ public class SecurityConfig { ) // 认证请求 .authorizeHttpRequests(auth -> auth - .requestMatchers("/auth/**").permitAll() - .requestMatchers("/auth/register").permitAll() - .anyRequest().access((authenticationSupplier, requestAuthorizationContext) -> { - HttpServletRequest request = requestAuthorizationContext.getRequest(); - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - return new AuthorizationDecision( - jwtRbacAuthenticationService.hasPermission(request, authentication) - ); - }) + .requestMatchers("/auth/**").permitAll() + .requestMatchers("/auth/register").permitAll() + .anyRequest().access((authenticationSupplier, requestAuthorizationContext) -> { + HttpServletRequest request = requestAuthorizationContext.getRequest(); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return new AuthorizationDecision( + jwtRbacAuthenticationService.hasPermission(request, authentication) + ); + }) ) // JWT过滤器 .addFilterBefore( - new JwtAuthenticationFilter(jwtUtil, jwtUserDetailsService), + new JwtAuthenticationFilter(jwtUtil, scopeData, jwtUserDetailsService), UsernamePasswordAuthenticationFilter.class ); diff --git a/src/main/java/asia/yulinling/workflow/dto/request/UpdateUserRequest.java b/src/main/java/asia/yulinling/workflow/dto/request/UpdateUserRequest.java new file mode 100644 index 0000000..bfc0349 --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/dto/request/UpdateUserRequest.java @@ -0,0 +1,39 @@ +package asia.yulinling.workflow.dto.request; + +import lombok.Data; + +/** + *

+ * 更新用户信息类 + *

+ * + * @author YLL + * @since 2025/6/23 + */ +@Data +public class UpdateUserRequest { + /** + * 主键id + */ + private Long id; + + /** + * 昵称 + */ + private String nickname; + + /** + * 生日 + */ + private String birthday; + + /** + * 性别,男-1,女-2 + */ + private Integer sex; + + /** + * 手机号 + */ + private String phone; +} diff --git a/src/main/java/asia/yulinling/workflow/model/vo/user/UserPrincipal.java b/src/main/java/asia/yulinling/workflow/model/vo/user/UserPrincipal.java index b534816..ea730da 100644 --- a/src/main/java/asia/yulinling/workflow/model/vo/user/UserPrincipal.java +++ b/src/main/java/asia/yulinling/workflow/model/vo/user/UserPrincipal.java @@ -4,13 +4,11 @@ import asia.yulinling.workflow.model.entity.Permission; import asia.yulinling.workflow.model.entity.Role; import asia.yulinling.workflow.model.entity.User; import cn.hutool.core.util.StrUtil; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @@ -37,7 +35,6 @@ public class UserPrincipal implements UserDetails { /** * 主键id */ - @TableId(type = IdType.AUTO) private Long id; /** @@ -53,7 +50,6 @@ public class UserPrincipal implements UserDetails { /** * 加密后的密码 */ - @JsonIgnore private String password; /** @@ -156,8 +152,6 @@ public class UserPrincipal implements UserDetails { List roles, List permissions) { - log.info("roleNames{}\n{}authorities", roles, permissions); - List roleNames = roles .stream() .map(Role::getName) @@ -171,20 +165,12 @@ public class UserPrincipal implements UserDetails { new SimpleGrantedAuthority(permission.getPermission())) .collect(Collectors.toList()); - return new UserPrincipal( - user.getId(), - user.getUsername(), - user.getPassword(), - user.getNickname(), - user.getPhone(), - user.getEmail(), - user.getBirthday(), - user.getSex(), - user.getStatus(), - user.getCreateTime(), - user.getUpdateTime(), - roleNames, - authorities); + UserPrincipal userPrincipal = new UserPrincipal(); + BeanUtils.copyProperties(user, userPrincipal); + userPrincipal.setRoles(roleNames); + userPrincipal.setAuthorities(authorities); + + return userPrincipal; } diff --git a/src/main/java/asia/yulinling/workflow/model/vo/user/UserVO.java b/src/main/java/asia/yulinling/workflow/model/vo/user/UserVO.java index 1ea35ad..eff1115 100644 --- a/src/main/java/asia/yulinling/workflow/model/vo/user/UserVO.java +++ b/src/main/java/asia/yulinling/workflow/model/vo/user/UserVO.java @@ -4,7 +4,7 @@ import lombok.Data; /** *

- * 用户信息VO + * 用户信息VO *

* * @author YLL diff --git a/src/main/java/asia/yulinling/workflow/scope/RequestScopeData.java b/src/main/java/asia/yulinling/workflow/scope/RequestScopeData.java new file mode 100644 index 0000000..37f450b --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/scope/RequestScopeData.java @@ -0,0 +1,22 @@ +package asia.yulinling.workflow.scope; + +import lombok.Data; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +/** + *

+ * 请求生命周期的全局数据 + *

+ * + * @author YLL + * @since 2025/6/23 + */ +@Component +@RequestScope +@Data +public class RequestScopeData { + private String token; + private Long userId; + private boolean isLogin; +} diff --git a/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationFilter.java b/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationFilter.java index 6fbe284..5885ce9 100644 --- a/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationFilter.java +++ b/src/main/java/asia/yulinling/workflow/security/JwtAuthenticationFilter.java @@ -1,8 +1,8 @@ package asia.yulinling.workflow.security; import asia.yulinling.workflow.exception.SecurityException; -import asia.yulinling.workflow.constant.Status; import asia.yulinling.workflow.model.vo.user.UserPrincipal; +import asia.yulinling.workflow.scope.RequestScopeData; import asia.yulinling.workflow.utils.JwtUtil; import asia.yulinling.workflow.utils.ResponseUtil; import jakarta.servlet.FilterChain; @@ -32,6 +32,7 @@ import java.io.IOException; @Slf4j public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; + private final RequestScopeData scopeData; private final JwtUserDetailsService jwtUserDetailsService; @Override @@ -45,7 +46,12 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { if (StringUtils.hasText(token) && jwtUtil.validateToken(token)) { try { // 2. 解析token获取username - String username = jwtUtil.parseToken(token).getSubject(); + String username = jwtUtil.getUsernameByToken(token); + Long userId = jwtUtil.getUserIdByToken(token); + + scopeData.setUserId(userId); + scopeData.setToken(token); + scopeData.setLogin(true); // 3. 根据username验证password UserPrincipal userPrincipal = jwtUserDetailsService.loadUserByUsername(username); @@ -57,11 +63,12 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); - } catch (SecurityException e) { log.error("Security异常:{}", e.getMessage()); ResponseUtil.renderJson(response, e); } + } else { + scopeData.setLogin(false); } filterChain.doFilter(request, response); diff --git a/src/main/java/asia/yulinling/workflow/security/JwtRbacAuthenticationService.java b/src/main/java/asia/yulinling/workflow/security/JwtRbacAuthenticationService.java index f21a11b..64ac129 100644 --- a/src/main/java/asia/yulinling/workflow/security/JwtRbacAuthenticationService.java +++ b/src/main/java/asia/yulinling/workflow/security/JwtRbacAuthenticationService.java @@ -12,7 +12,6 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import org.springframework.util.AntPathMatcher; -import org.springframework.util.StringUtils; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition; import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition; @@ -57,8 +56,8 @@ public class JwtRbacAuthenticationService { List pagePerms = permissions.stream() .filter(permission -> Objects.equals(permission.getType(), 1)) - .filter(permission -> !StringUtils.isEmpty(permission.getUrl())) - .filter(permission -> !StringUtils.isEmpty(permission.getMethod())) + .filter(permission -> !permission.getUrl().isEmpty()) + .filter(permission -> !permission.getMethod().isEmpty()) .toList(); for (Permission permission : pagePerms) { diff --git a/src/main/java/asia/yulinling/workflow/security/JwtUserDetailsService.java b/src/main/java/asia/yulinling/workflow/security/JwtUserDetailsService.java index 3f86786..726ec77 100644 --- a/src/main/java/asia/yulinling/workflow/security/JwtUserDetailsService.java +++ b/src/main/java/asia/yulinling/workflow/security/JwtUserDetailsService.java @@ -2,7 +2,6 @@ package asia.yulinling.workflow.security; import asia.yulinling.workflow.mapper.PermissionMapper; import asia.yulinling.workflow.mapper.RoleMapper; -import asia.yulinling.workflow.mapper.RoleUserMapper; import asia.yulinling.workflow.mapper.UserMapper; import asia.yulinling.workflow.model.entity.Permission; import asia.yulinling.workflow.model.entity.Role; @@ -12,6 +11,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @@ -33,7 +33,6 @@ import java.util.stream.Collectors; public class JwtUserDetailsService implements UserDetailsService { private final UserMapper userMapper; private final RoleMapper roleMapper; - private final RoleUserMapper roleUserMapper; private final PermissionMapper permissionMapper; @Override @@ -53,21 +52,20 @@ public class JwtUserDetailsService implements UserDetailsService { log.error("未找到用户信息{}", username); throw new UsernameNotFoundException("未找到用户信息:" + username); } - log.info("username:{}", username); // 3. 查找用户对应的角色 List roles = roleMapper.selectRoleByUserId(user.getId()); if (roles.isEmpty()) { log.error("未找到角色信息{}", roles); - throw new UsernameNotFoundException("未找到角色信息" + roles); + throw new AccessDeniedException("未找到角色信息" + roles); } List permissions = permissionMapper.selectPermissionsByRoleId(roles.stream().map(Role::getId).collect(Collectors.toList())); if (permissions.isEmpty()) { log.error("未找到权限信息{}", permissions); - throw new UsernameNotFoundException("未找到权限信息" + permissions); + throw new AccessDeniedException("未找到权限信息" + permissions); } // 4. 返回User diff --git a/src/main/java/asia/yulinling/workflow/service/UserService.java b/src/main/java/asia/yulinling/workflow/service/UserService.java index 9cb09fd..f0b0d3e 100644 --- a/src/main/java/asia/yulinling/workflow/service/UserService.java +++ b/src/main/java/asia/yulinling/workflow/service/UserService.java @@ -1,8 +1,9 @@ package asia.yulinling.workflow.service; import asia.yulinling.workflow.dto.request.PageParam; -import asia.yulinling.workflow.model.ApiResponse; +import asia.yulinling.workflow.dto.request.UpdateUserRequest; import asia.yulinling.workflow.dto.response.PageResult; +import asia.yulinling.workflow.model.ApiResponse; import asia.yulinling.workflow.model.vo.user.UserVO; import org.springframework.transaction.annotation.Transactional; @@ -24,4 +25,12 @@ public interface UserService { * @return 用户列表 */ ApiResponse> getUserListByPage(PageParam pageParam); + + /** + * 更新用户信息 + * + * @param request UpdateUserRequest + * @return 更新状态 + */ + ApiResponse updateUserInfo(UpdateUserRequest request); } diff --git a/src/main/java/asia/yulinling/workflow/service/impl/UserServiceImpl.java b/src/main/java/asia/yulinling/workflow/service/impl/UserServiceImpl.java index 5ac05b4..0505dff 100644 --- a/src/main/java/asia/yulinling/workflow/service/impl/UserServiceImpl.java +++ b/src/main/java/asia/yulinling/workflow/service/impl/UserServiceImpl.java @@ -1,18 +1,23 @@ package asia.yulinling.workflow.service.impl; import asia.yulinling.workflow.dto.request.PageParam; +import asia.yulinling.workflow.dto.request.UpdateUserRequest; +import asia.yulinling.workflow.dto.response.PageResult; import asia.yulinling.workflow.mapper.UserMapper; import asia.yulinling.workflow.model.ApiResponse; -import asia.yulinling.workflow.dto.response.PageResult; import asia.yulinling.workflow.model.entity.User; import asia.yulinling.workflow.model.vo.user.UserVO; +import asia.yulinling.workflow.scope.RequestScopeData; import asia.yulinling.workflow.service.UserService; import asia.yulinling.workflow.utils.PageUtil; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.List; @@ -32,6 +37,7 @@ public class UserServiceImpl implements UserService { /** 注入用户Mapper */ private final UserMapper userMapper; + private final RequestScopeData scopeData; /** * 获取用户列表分页 @@ -65,4 +71,41 @@ public class UserServiceImpl implements UserService { throw new RuntimeException(e); } } + + /** + * 更新用户信息 + * + * @param request UpdateUserRequest + * @return 更新状态 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public ApiResponse updateUserInfo(UpdateUserRequest request) { + + Long userId = scopeData.getUserId(); + Long requestUserId = request.getId(); + + if (!requestUserId.equals(userId)) { + log.error("user id not match, scopeData: {}, requestUserId: {}", requestUserId, userId); + throw new RuntimeException("用户Id不匹配"); + } + + User user = new User(); + BeanUtils.copyProperties(request, user); + LambdaUpdateWrapper userLambdaUpdateWrapper = new LambdaUpdateWrapper<>(); + userLambdaUpdateWrapper.eq(User::getId, userId); + + userLambdaUpdateWrapper.set(StringUtils.hasText(request.getNickname()), User::getNickname, request.getNickname()); + userLambdaUpdateWrapper.set(StringUtils.hasText(request.getBirthday()), User::getBirthday, request.getBirthday()); + userLambdaUpdateWrapper.set(request.getSex() != null, User::getSex, request.getSex()); + userLambdaUpdateWrapper.set(StringUtils.hasText(request.getPhone()), User::getPhone, request.getPhone()); + + try { + userMapper.update(userLambdaUpdateWrapper); + return ApiResponse.ofSuccess("更新成功"); + } catch (Exception e) { + log.error("update user error:{}", e.getMessage()); + return ApiResponse.ofSuccess("更新失败"); + } + } } diff --git a/src/main/java/asia/yulinling/workflow/utils/JwtUtil.java b/src/main/java/asia/yulinling/workflow/utils/JwtUtil.java index bea6e7c..126fb06 100644 --- a/src/main/java/asia/yulinling/workflow/utils/JwtUtil.java +++ b/src/main/java/asia/yulinling/workflow/utils/JwtUtil.java @@ -3,7 +3,9 @@ package asia.yulinling.workflow.utils; import asia.yulinling.workflow.constant.Const; import asia.yulinling.workflow.constant.Status; import asia.yulinling.workflow.exception.SecurityException; +import asia.yulinling.workflow.model.vo.user.UserPrincipal; import cn.hutool.core.util.StrUtil; +import io.jsonwebtoken.*; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; import jakarta.servlet.http.HttpServletRequest; @@ -13,7 +15,6 @@ import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.security.core.Authentication; -import io.jsonwebtoken.*; import org.springframework.stereotype.Component; import java.security.Key; @@ -59,6 +60,27 @@ public class JwtUtil { * @return JWT */ public String generateToken(Authentication authentication, boolean isRememberMe) { + // 1. 解析principal + Object principal = authentication.getPrincipal(); + Long userId = null; + String username = null; + if (principal instanceof UserPrincipal) { + userId = ((UserPrincipal) principal).getId(); + username = ((UserPrincipal) principal).getUsername(); + } + + return generateToken(userId, username, isRememberMe); + } + + /** + * 创建JWT + * + * @param userId 用户Id + * @param username 用户名 + * @param isRememberMe 记住我 + * @return JWT + */ + public String generateToken(Long userId, String username, boolean isRememberMe) { // 1. 当前时间 Date now = new Date(); @@ -66,18 +88,20 @@ public class JwtUtil { long ttl = isRememberMe ? this.remember : this.ttl; Date expiration = new Date(now.getTime() + ttl); - // 3. 构建 JWT - String username = authentication.getName(); + // 3. 构建JwtBuilder JwtBuilder builder = Jwts.builder() .setSubject(username) + .claim("userId", userId) .setIssuedAt(now) .setExpiration(expiration) .signWith(this.key()); // 4. 生成token String token = builder.compact(); + // 5. 将token存入redis stringRedisTemplate.opsForValue().set(Const.REDIS_JWT_KEY_PREFIX + username, token, ttl, TimeUnit.SECONDS); + // 6. 返回生成的token return token; } @@ -122,6 +146,16 @@ public class JwtUtil { } } + public Long getUserIdByToken(String token) { + Claims claims = parseToken(token); + return (Long) claims.get("userId"); + } + + public String getUsernameByToken(String token) { + Claims claims = parseToken(token); + return claims.getSubject(); + } + /** * 校验Token * diff --git a/src/main/resources/db/data.sql b/src/main/resources/db/data.sql index 9ecdf7f..8c5a337 100644 --- a/src/main/resources/db/data.sql +++ b/src/main/resources/db/data.sql @@ -1,24 +1,22 @@ BEGIN; INSERT INTO `wk_permission` -VALUES (1072806379288399872, '测试页面', '/test', 1, 'page:test', 'GET', 1, 0); +VALUES (1072806379288399872, '测试页面', '/test', 1, 'page:test', 'GET', 1, 0, NULL, NULL); INSERT INTO `wk_permission` -VALUES (1072806379313565696, '测试页面-查询', '/**/test', 2, 'btn:test:query', 'GET', 1, 1072806379288399872); +VALUES (1072806379313565696, '测试页面-查询', '/**/test', 2, 'btn:test:query', 'GET', 1, 1072806379288399872, NULL, + NULL); INSERT INTO `wk_permission` -VALUES (1072806379330342912, '测试页面-添加', '/**/test', 2, 'btn:test:insert', 'POST', 2, 1072806379288399872); +VALUES (1072806379330342912, '测试页面-添加', '/**/test', 2, 'btn:test:insert', 'POST', 2, 1072806379288399872, NULL, + NULL); INSERT INTO `wk_permission` -VALUES (1072806379342925824, '监控在线用户页面', '/monitor', 1, 'page:monitor:online', NULL, 2, 0); +VALUES (1072806379342925824, '监控在线用户页面', '/monitor', 1, 'page:monitor:online', NULL, 2, 0, NULL, NULL); INSERT INTO `wk_permission` VALUES (1072806379363897344, '在线用户页面-查询', '/**/api/monitor/online/user', 2, 'btn:monitor:online:query', 'GET', - 1, - 1072806379342925824); + 1, 1072806379342925824, NULL, NULL); INSERT INTO `wk_permission` VALUES (1072806379384868864, '在线用户页面-踢出', '/**/api/monitor/online/user/kickout', 2, - 'btn:monitor:online:kickout', - 'DELETE', 2, 1072806379342925824); + 'btn:monitor:online:kickout', 'DELETE', 2, 1072806379342925824, NULL, NULL); INSERT INTO `wk_permission` -VALUES (1072806379384868865, '用户列表', '/users', 1, - 'page:test', - 'GET', 1, 0); +VALUES (1072806379384868865, '用户列表', '/users', 1, 'page:test', 'GET', 1, 0, NULL, NULL); COMMIT; BEGIN; diff --git a/src/test/java/asia/yulinling/workflow/service/UserServiceTest.java b/src/test/java/asia/yulinling/workflow/service/UserServiceTest.java index 5df81f9..b76ca17 100644 --- a/src/test/java/asia/yulinling/workflow/service/UserServiceTest.java +++ b/src/test/java/asia/yulinling/workflow/service/UserServiceTest.java @@ -21,11 +21,6 @@ public class UserServiceTest extends WorkFlowMainTests { @Autowired private UserService userService; - @Test - public void getUserList() { - ApiResponse> users = userService.getUserList(); - Assertions.assertEquals(200, users.getCode().intValue()); - } @Test public void getUserListByPage() {