diff --git a/itbaima-monitor-server/src/main/java/com/example/config/SecurityConfiguration.java b/itbaima-monitor-server/src/main/java/com/example/config/SecurityConfiguration.java index ec0afe5..9ede5fc 100644 --- a/itbaima-monitor-server/src/main/java/com/example/config/SecurityConfiguration.java +++ b/itbaima-monitor-server/src/main/java/com/example/config/SecurityConfiguration.java @@ -56,7 +56,8 @@ public class SecurityConfiguration { .requestMatchers("/api/auth/**", "/error").permitAll() .requestMatchers("/monitor/**").permitAll() .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() - .anyRequest().hasAnyRole(Const.ROLE_DEFAULT) + .requestMatchers("/api/user/sub/**").hasRole(Const.ROLE_ADMIN) + .anyRequest().hasAnyRole(Const.ROLE_ADMIN, Const.ROLE_NORMAL) ) .formLogin(conf -> conf .loginProcessingUrl("/api/auth/login") diff --git a/itbaima-monitor-server/src/main/java/com/example/controller/UserController.java b/itbaima-monitor-server/src/main/java/com/example/controller/UserController.java new file mode 100644 index 0000000..94af939 --- /dev/null +++ b/itbaima-monitor-server/src/main/java/com/example/controller/UserController.java @@ -0,0 +1,47 @@ +package com.example.controller; + +import com.example.entity.RestBean; +import com.example.entity.vo.request.ChangePasswordVO; +import com.example.entity.vo.request.CreateSubAccountVO; +import com.example.entity.vo.response.SubAccountVO; +import com.example.service.AccountService; +import com.example.utils.Const; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/user") +public class UserController { + + @Resource + AccountService service; + + @PostMapping("/change-password") + public RestBean changePassword(@RequestBody @Valid ChangePasswordVO vo, + @RequestAttribute(Const.ATTR_USER_ID) int userId) { + return service.changePassword(userId, vo.getPassword(), vo.getNew_password()) ? + RestBean.success() : RestBean.failure(401, "原密码输入错误"); + } + + @PostMapping("/sub/create") + public RestBean createSubAccount(@RequestBody @Valid CreateSubAccountVO vo) { + service.createSubAccount(vo); + return RestBean.success(); + } + + @GetMapping("/sub/delete") + public RestBean deleteSubAccount(int uid, @RequestAttribute(Const.ATTR_USER_ID) int userId) { + if(uid == userId) + return RestBean.failure(401, "非法参数"); + service.deleteSubAccount(uid); + return RestBean.success(); + } + + @GetMapping("/sub/list") + public RestBean> subAccountList() { + return RestBean.success(service.listSubAccount()); + } +} diff --git a/itbaima-monitor-server/src/main/java/com/example/controller/exception/UserController.java b/itbaima-monitor-server/src/main/java/com/example/controller/exception/UserController.java deleted file mode 100644 index bc4628d..0000000 --- a/itbaima-monitor-server/src/main/java/com/example/controller/exception/UserController.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.controller.exception; - -import com.example.entity.RestBean; -import com.example.entity.vo.request.ChangePasswordVO; -import com.example.service.AccountService; -import com.example.utils.Const; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping("/api/user") -public class UserController { - - @Resource - AccountService service; - - @PostMapping("/change-password") - public RestBean changePassword(@RequestBody @Valid ChangePasswordVO vo, - @RequestAttribute(Const.ATTR_USER_ID) int userId) { - return service.changePassword(userId, vo.getPassword(), vo.getNew_password()) ? - RestBean.success() : RestBean.failure(401, "原密码输入错误"); - } -} diff --git a/itbaima-monitor-server/src/main/java/com/example/entity/dto/Account.java b/itbaima-monitor-server/src/main/java/com/example/entity/dto/Account.java index 3fbfa1f..818588a 100644 --- a/itbaima-monitor-server/src/main/java/com/example/entity/dto/Account.java +++ b/itbaima-monitor-server/src/main/java/com/example/entity/dto/Account.java @@ -23,4 +23,5 @@ public class Account implements BaseData { String email; String role; Date registerTime; + String clients; } diff --git a/itbaima-monitor-server/src/main/java/com/example/entity/vo/request/CreateSubAccountVO.java b/itbaima-monitor-server/src/main/java/com/example/entity/vo/request/CreateSubAccountVO.java new file mode 100644 index 0000000..627f2a9 --- /dev/null +++ b/itbaima-monitor-server/src/main/java/com/example/entity/vo/request/CreateSubAccountVO.java @@ -0,0 +1,20 @@ +package com.example.entity.vo.request; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.util.List; + +@Data +public class CreateSubAccountVO { + @Length(min = 1, max = 10) + String username; + @Email + String email; + @Length(min = 6, max = 20) + String password; + @Size(min = 1) + List clients; +} diff --git a/itbaima-monitor-server/src/main/java/com/example/entity/vo/response/SubAccountVO.java b/itbaima-monitor-server/src/main/java/com/example/entity/vo/response/SubAccountVO.java new file mode 100644 index 0000000..b22fe69 --- /dev/null +++ b/itbaima-monitor-server/src/main/java/com/example/entity/vo/response/SubAccountVO.java @@ -0,0 +1,12 @@ +package com.example.entity.vo.response; + +import com.alibaba.fastjson2.JSONArray; +import lombok.Data; + +@Data +public class SubAccountVO { + int id; + String name; + String email; + JSONArray clients; +} diff --git a/itbaima-monitor-server/src/main/java/com/example/filter/JwtAuthenticationFilter.java b/itbaima-monitor-server/src/main/java/com/example/filter/JwtAuthenticationFilter.java index a719751..06f39cf 100644 --- a/itbaima-monitor-server/src/main/java/com/example/filter/JwtAuthenticationFilter.java +++ b/itbaima-monitor-server/src/main/java/com/example/filter/JwtAuthenticationFilter.java @@ -19,6 +19,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import java.util.ArrayList; /** * 用于对请求头中Jwt令牌进行校验的工具,为当前请求添加用户验证信息 @@ -61,6 +62,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); request.setAttribute(Const.ATTR_USER_ID, utils.toId(jwt)); + request.setAttribute(Const.ATTR_USER_ROLE, new ArrayList<>(user.getAuthorities()).get(0).getAuthority()); } filterChain.doFilter(request, response); } diff --git a/itbaima-monitor-server/src/main/java/com/example/service/AccountService.java b/itbaima-monitor-server/src/main/java/com/example/service/AccountService.java index eeb6299..95d29d4 100644 --- a/itbaima-monitor-server/src/main/java/com/example/service/AccountService.java +++ b/itbaima-monitor-server/src/main/java/com/example/service/AccountService.java @@ -3,13 +3,20 @@ package com.example.service; import com.baomidou.mybatisplus.extension.service.IService; import com.example.entity.dto.Account; import com.example.entity.vo.request.ConfirmResetVO; +import com.example.entity.vo.request.CreateSubAccountVO; import com.example.entity.vo.request.EmailResetVO; +import com.example.entity.vo.response.SubAccountVO; import org.springframework.security.core.userdetails.UserDetailsService; +import java.util.List; + public interface AccountService extends IService, UserDetailsService { Account findAccountByNameOrEmail(String text); String registerEmailVerifyCode(String type, String email, String address); String resetEmailAccountPassword(EmailResetVO info); String resetConfirm(ConfirmResetVO info); boolean changePassword(int id, String oldPass, String newPass); + void createSubAccount(CreateSubAccountVO vo); + void deleteSubAccount(int uid); + List listSubAccount(); } diff --git a/itbaima-monitor-server/src/main/java/com/example/service/impl/AccountServiceImpl.java b/itbaima-monitor-server/src/main/java/com/example/service/impl/AccountServiceImpl.java index 33d33f0..d5a628e 100644 --- a/itbaima-monitor-server/src/main/java/com/example/service/impl/AccountServiceImpl.java +++ b/itbaima-monitor-server/src/main/java/com/example/service/impl/AccountServiceImpl.java @@ -1,14 +1,18 @@ package com.example.service.impl; +import com.alibaba.fastjson2.JSONArray; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.entity.dto.Account; import com.example.entity.vo.request.ConfirmResetVO; +import com.example.entity.vo.request.CreateSubAccountVO; import com.example.entity.vo.request.EmailResetVO; +import com.example.entity.vo.response.SubAccountVO; import com.example.mapper.AccountMapper; import com.example.service.AccountService; import com.example.utils.Const; import com.example.utils.FlowUtils; +import com.example.utils.JwtUtils; import jakarta.annotation.Resource; import org.springframework.amqp.core.AmqpTemplate; import org.springframework.beans.factory.annotation.Value; @@ -19,6 +23,8 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.util.Date; +import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.TimeUnit; @@ -45,6 +51,9 @@ public class AccountServiceImpl extends ServiceImpl impl @Resource FlowUtils flow; + @Resource + JwtUtils jwt; + /** * 从数据库中通过用户名或邮箱查找用户详细信息 * @param username 用户名 @@ -127,6 +136,31 @@ public class AccountServiceImpl extends ServiceImpl impl return true; } + @Override + public void createSubAccount(CreateSubAccountVO vo) { + Account account = new Account(null, vo.getUsername(), + passwordEncoder.encode(vo.getPassword()), + vo.getEmail(), Const.ROLE_NORMAL, new Date(), + JSONArray.copyOf(vo.getClients()).toJSONString()); + this.save(account); + } + + @Override + public void deleteSubAccount(int uid) { + this.removeById(uid); + jwt.deleteUser(uid); + } + + @Override + public List listSubAccount() { + return this.list(Wrappers.query().eq("role", "user")) + .stream().map(account -> { + SubAccountVO vo = account.asViewObject(SubAccountVO.class); + vo.setClients(JSONArray.parse(account.getClients())); + return vo; + }).toList(); + } + /** * 移除Redis中存储的邮件验证码 * @param email 电邮 diff --git a/itbaima-monitor-server/src/main/java/com/example/service/impl/ClientServiceImpl.java b/itbaima-monitor-server/src/main/java/com/example/service/impl/ClientServiceImpl.java index 34f0058..c863502 100644 --- a/itbaima-monitor-server/src/main/java/com/example/service/impl/ClientServiceImpl.java +++ b/itbaima-monitor-server/src/main/java/com/example/service/impl/ClientServiceImpl.java @@ -172,7 +172,6 @@ public class ClientServiceImpl extends ServiceImpl impleme StringBuilder sb = new StringBuilder(24); for (int i = 0; i < 24; i++) sb.append(CHARACTERS.charAt(random.nextInt(CHARACTERS.length()))); - System.out.println(sb); return sb.toString(); } } diff --git a/itbaima-monitor-server/src/main/java/com/example/utils/Const.java b/itbaima-monitor-server/src/main/java/com/example/utils/Const.java index 3ff9502..e8f1158 100644 --- a/itbaima-monitor-server/src/main/java/com/example/utils/Const.java +++ b/itbaima-monitor-server/src/main/java/com/example/utils/Const.java @@ -7,6 +7,8 @@ public final class Const { //JWT令牌 public final static String JWT_BLACK_LIST = "jwt:blacklist:"; public final static String JWT_FREQUENCY = "jwt:frequency:"; + + public final static String USER_BLACK_LIST = "user:blacklist"; //请求频率限制 public final static String FLOW_LIMIT_COUNTER = "flow:counter:"; public final static String FLOW_LIMIT_BLOCK = "flow:block:"; @@ -18,9 +20,11 @@ public final class Const { public final static int ORDER_CORS = -102; //请求自定义属性 public final static String ATTR_USER_ID = "userId"; + public final static String ATTR_USER_ROLE = "userRole"; public final static String ATTR_CLIENT = "client"; //消息队列 public final static String MQ_MAIL = "mail"; //用户角色 - public final static String ROLE_DEFAULT = "admin"; + public final static String ROLE_ADMIN = "admin"; + public final static String ROLE_NORMAL = "user"; } diff --git a/itbaima-monitor-server/src/main/java/com/example/utils/JwtUtils.java b/itbaima-monitor-server/src/main/java/com/example/utils/JwtUtils.java index 269f7a8..96ada21 100644 --- a/itbaima-monitor-server/src/main/java/com/example/utils/JwtUtils.java +++ b/itbaima-monitor-server/src/main/java/com/example/utils/JwtUtils.java @@ -109,6 +109,7 @@ public class JwtUtils { try { DecodedJWT verify = jwtVerifier.verify(token); if(this.isInvalidToken(verify.getId())) return null; + if(this.isInvalidUser(verify.getClaim("id").asInt())) return null; Map claims = verify.getClaims(); return new Date().after(claims.get("exp").asDate()) ? null : verify; } catch (JWTVerificationException e) { @@ -140,6 +141,14 @@ public class JwtUtils { return claims.get("id").asInt(); } + public void deleteUser(int uid) { + template.opsForValue().set(Const.USER_BLACK_LIST + uid, "", expire, TimeUnit.HOURS); + } + + private boolean isInvalidUser(int uid){ + return Boolean.TRUE.equals(template.hasKey(Const.USER_BLACK_LIST + uid)); + } + /** * 频率检测,防止用户高频申请Jwt令牌,并且采用阶段封禁机制 * 如果已经提示无法登录的情况下用户还在刷,那么就封禁更长时间 diff --git a/itbaima-monitor-web/src/views/IndexView.vue b/itbaima-monitor-web/src/views/IndexView.vue index a17d790..15f79be 100755 --- a/itbaima-monitor-web/src/views/IndexView.vue +++ b/itbaima-monitor-web/src/views/IndexView.vue @@ -44,17 +44,27 @@ import {Back, Moon, Sunny} from "@element-plus/icons-vue"; import TabItem from "@/component/TabItem.vue"; import {ref} from "vue"; import {useDark} from "@vueuse/core"; +import {useRoute} from "vue-router"; function userLogout() { logout(() => router.push("/")) } +const route = useRoute() +const defaultIndex = () => { + for (let tab of tabs) { + if(route.name === tab.route) + return tab.id + } + return 1 +} + const dark = ref(useDark()) -const tab = ref(1) const tabs = [ {id: 1, name: '管理', route: 'manage'}, {id: 2, name: '安全', route: 'security'} ] +const tab = ref(defaultIndex()) function changePage(item) { tab.value = item.id router.push({name: item.route}) diff --git a/itbaima-monitor-web/src/views/main/Security.vue b/itbaima-monitor-web/src/views/main/Security.vue index c7ca35e..0524802 100644 --- a/itbaima-monitor-web/src/views/main/Security.vue +++ b/itbaima-monitor-web/src/views/main/Security.vue @@ -2,7 +2,7 @@ import {reactive, ref} from "vue"; import {logout, post} from "@/net"; import {ElMessage} from "element-plus"; -import {Lock, Switch} from "@element-plus/icons-vue"; +import {Lock, Plus, Switch} from "@element-plus/icons-vue"; import router from "@/router"; const formRef = ref() @@ -54,6 +54,8 @@ function resetPassword() { @@ -84,7 +90,14 @@ function resetPassword() { .info-card { border-radius: 7px; width: 100%; + height: fit-content; padding: 15px 20px; background-color: var(--el-bg-color); + + .title { + font-size: 18px; + font-weight: bold; + color: dodgerblue; + } }