Просмотр исходного кода

feat: "完善UserDetail的权限和全局异常处理"

Leonezhurui 2 лет назад
Родитель
Сommit
26b0e620d0

+ 4 - 1
src/main/java/com/njuzr/eaibackend/config/JWTAuthenticationFilter.java

@@ -1,5 +1,6 @@
 package com.njuzr.eaibackend.config;
 
+import com.njuzr.eaibackend.exception.MyException;
 import com.njuzr.eaibackend.po.MyUserDetails;
 import com.njuzr.eaibackend.service.MyUserDetailService;
 import com.njuzr.eaibackend.utils.JWTTokenUtil;
@@ -38,11 +39,13 @@ public class JWTAuthenticationFilter extends OncePerRequestFilter {
         String token = request.getHeader("Authorization");
         log.info("从Header中解析出Token:" + token);
 
-        if (token != null && jwtTokenUtil.isTokenNotExpired(token)) {
+        if (token != null) {
+            if(!jwtTokenUtil.isTokenNotExpired(token))  throw new MyException(401, "用户token过期");
             String username = jwtTokenUtil.parseToken(token);
             log.info("解析出username:" + username);
             UserDetails userDetails = userDetailService.loadUserByUsername(username);
             log.info("解析出UserDetails:" + userDetails);
+            log.info("解析出UserDetails权限:" + userDetails.getAuthorities());
 
             UsernamePasswordAuthenticationToken authentication =
                     new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

+ 28 - 3
src/main/java/com/njuzr/eaibackend/controller/UserController.java

@@ -5,10 +5,13 @@ import com.njuzr.eaibackend.dto.UserRegisterDTO;
 import com.njuzr.eaibackend.exception.MyException;
 import com.njuzr.eaibackend.service.UserService;
 import com.njuzr.eaibackend.utils.ModelMapperUtil;
+import com.njuzr.eaibackend.utils.ObjectFieldCheckerUtil;
 import com.njuzr.eaibackend.vo.UserVO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.web.bind.annotation.*;
 
 /**
@@ -29,10 +32,32 @@ public class UserController {
         this.userService = userService;
     }
 
+
+    /**
+     * 用户更新自己的用户信息(固定信息不可更改,例如真实姓名、邮箱、学号、角色等)
+     * @param id
+     * @param userDTO
+     * @return
+     */
+    @PreAuthorize("hasRole('ROLE_STUDENT') or hasRole('ROLE_TEACHER')")
+    @PutMapping
+    public MyResponse updateUser(@AuthenticationPrincipal(expression = "id") Long id, @RequestBody UserDTO userDTO) {
+        log.info("UserDTO解析成功,对象如下:"+userDTO);
+        UserVO userVO;
+        if (ObjectFieldCheckerUtil.areAllFieldsNullOrAbsent(userDTO, "id", "name", "officialEmail", "officialNumber", "role", "createTime")) {
+            userVO = userService.updateUser(id, userDTO);
+            return MyResponse.success(userVO);
+        }
+        return MyResponse.error(400, "请求体中包含了不能更新的字段");
+    }
+
     @GetMapping("/{id}")
-    @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_TEACHER')")
-    public MyResponse findUserById(@PathVariable Long id) {
-        return MyResponse.success(id);
+//    @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_TEACHER')")
+    public MyResponse findUserById(@PathVariable Long id, @AuthenticationPrincipal UserDetails userDetails) {
+        log.info("用户认证成功,解析出的UserDetails信息如下:"+userDetails.getAuthorities());
+        UserVO userVO = userService.findById(id);
+        log.info("生成的userVO信息如下:"+userVO);
+        return MyResponse.success(userVO);
     }
 
 }

+ 36 - 0
src/main/java/com/njuzr/eaibackend/exception/GlobalExceptionHandler.java

@@ -0,0 +1,36 @@
+package com.njuzr.eaibackend.exception;
+
+import com.njuzr.eaibackend.controller.MyResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import java.nio.file.AccessDeniedException;
+
+/**
+ * @author: Leonezhurui
+ * @Date: 2024/2/21 - 08:35
+ * @Package: EAI-Backend
+ * @Descrpition: 定义Controller层异常处理机制
+ */
+
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    // @PreAuthorize注解,使得抛出的Spring Security的AccessDeniedException不会被自定义的AccessDeniedHandler捕获,转而由Spring MVC的异常处理机制(如@ControllerAdvice)来处理的
+    // 务必注意这里的异常类型:org.springframework.security.access.AccessDeniedException.class
+    @ExceptionHandler(org.springframework.security.access.AccessDeniedException.class)
+    public MyResponse handleAccessDeniedException(org.springframework.security.access.AccessDeniedException e) {
+        log.error("全局异常处理器捕获,访问被拒绝,错误如下:"+e.getMessage());
+        return MyResponse.error(HttpStatus.FORBIDDEN.value(), "访问被拒绝:" + e.getMessage());
+    }
+
+    @ExceptionHandler(Exception.class)
+    public MyResponse handleGlobalException(Exception e) {
+        log.error("全局异常处理器捕获,错误如下:"+e.getMessage());
+        return MyResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务器错误: " + e.getMessage());
+    }
+
+}

+ 2 - 0
src/main/java/com/njuzr/eaibackend/mapper/UserMapper.java

@@ -15,4 +15,6 @@ import org.apache.ibatis.annotations.Select;
 public interface UserMapper extends BaseMapper<User> {
 
     User selectByOfficialNumber(String officialNumber);
+
+    int updateUser(User user);
 }

+ 9 - 15
src/main/java/com/njuzr/eaibackend/po/MyUserDetails.java

@@ -1,13 +1,17 @@
 package com.njuzr.eaibackend.po;
 
+import com.njuzr.eaibackend.enums.Role;
 import com.njuzr.eaibackend.po.base.BaseUser;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.ToString;
 import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.userdetails.UserDetails;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 
 /**
  * @author: Leonezhurui
@@ -19,26 +23,16 @@ import java.util.Collection;
 @ToString(callSuper = true)
 @Data
 public class MyUserDetails extends BaseUser implements UserDetails {
+    private Long id; // 将User所有信息都包含进去(方便在Controller中的操作)
     private String username;
     private String password;
-//    private boolean accountNonLocked;
-//    private boolean enabled;
-//    private List<GrantedAuthority> authorities;
-//
-//
-//    public MyUserDetails(User user) {
-//        this.username = user.getOfficialNumber();
-//        this.password = user.getPassword();
-//        this.accountNonLocked = user.isAccountNonLocked();
-//        this.enabled = user.isEnabled();
-//        this.authorities = user.getRole().stream()
-//                .map(SimpleGrantedAuthority::new)
-//                .collect(Collectors.toList());
-//    }
 
     @Override
     public Collection<? extends GrantedAuthority> getAuthorities() {
-        return null;
+        List<GrantedAuthority> authorities = new ArrayList<>();
+        Role role = getRole();
+        authorities.add(new SimpleGrantedAuthority("ROLE_"+role.getRole())); // 需要authorities列表之前加上ROLE_前缀
+        return authorities;
     }
 
     @Override

+ 6 - 0
src/main/java/com/njuzr/eaibackend/service/MyUserDetailService.java

@@ -1,15 +1,21 @@
 package com.njuzr.eaibackend.service;
 
+import com.njuzr.eaibackend.enums.Role;
 import com.njuzr.eaibackend.mapper.UserMapper;
 import com.njuzr.eaibackend.po.MyUserDetails;
 import com.njuzr.eaibackend.po.User;
 import com.njuzr.eaibackend.utils.ModelMapperUtil;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
 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 java.util.ArrayList;
+import java.util.List;
+
 /**
  * @author: Leonezhurui
  * @Date: 2024/2/20 - 00:02

+ 4 - 0
src/main/java/com/njuzr/eaibackend/service/UserService.java

@@ -3,6 +3,7 @@ package com.njuzr.eaibackend.service;
 import com.njuzr.eaibackend.dto.UserDTO;
 import com.njuzr.eaibackend.dto.UserRegisterDTO;
 import com.njuzr.eaibackend.vo.UserVO;
+import org.springframework.security.core.userdetails.User;
 
 /**
  * @author: Leonezhurui
@@ -14,5 +15,8 @@ public interface UserService {
 
     UserVO createUser(UserRegisterDTO userDTO);
 
+
     UserVO findById(Long id);
+
+    UserVO updateUser(Long id, UserDTO userDTO);
 }

+ 22 - 0
src/main/java/com/njuzr/eaibackend/service/impl/UserServiceImpl.java

@@ -1,5 +1,6 @@
 package com.njuzr.eaibackend.service.impl;
 
+import com.baomidou.mybatisplus.extension.activerecord.Model;
 import com.njuzr.eaibackend.dto.UserDTO;
 import com.njuzr.eaibackend.dto.UserRegisterDTO;
 import com.njuzr.eaibackend.exception.MyException;
@@ -69,4 +70,25 @@ public class UserServiceImpl implements UserService {
 
         return ModelMapperUtil.map(targetUser, UserVO.class);
     }
+
+    /**
+     *
+     * @param userDTO
+     * @return
+     */
+    @Override
+    public UserVO updateUser(Long id, UserDTO userDTO) {
+        User opeUser = ModelMapperUtil.map(userDTO, User.class);
+        opeUser.setId(id);
+        try {
+            int status = userMapper.updateUser(opeUser);
+            if (status > 0) {
+                return ModelMapperUtil.map(userMapper.selectById(opeUser.getId()), UserVO.class);
+            }
+        }catch (Exception e) {
+            log.error("数据库更新错误,错误如下:"+e.getMessage());
+            throw new MyException(500, "数据库更新失败");
+        }
+        return null;
+    }
 }

+ 69 - 0
src/main/java/com/njuzr/eaibackend/utils/ObjectFieldCheckerUtil.java

@@ -0,0 +1,69 @@
+package com.njuzr.eaibackend.utils;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+
+/**
+ * @author: Leonezhurui
+ * @Date: 2024/2/21 - 11:06
+ * @Package: EAI-Backend
+ */
+
+@Slf4j
+public class ObjectFieldCheckerUtil {
+
+    /**
+     * 检查指定的字段是否全部为 null 或不存在。
+     *
+     * @param object 对象实例
+     * @param fieldNames 字段名数组
+     * @return 如果所有指定字段都为 null 或不存在,则返回 true;如果任一字段存在且不为 null,则返回 false。
+     */
+    public static boolean areAllFieldsNullOrAbsent(Object object, String... fieldNames) {
+        if (object == null) {
+            return true;
+        }
+
+        for (String fieldName : fieldNames) {
+            try {
+                Field field = getFieldFromClassOrSuperclass(object.getClass(), fieldName);
+                if (field != null) {
+                    field.setAccessible(true); // 确保可以访问私有和受保护字段
+                    if (field.get(object) != null) {
+                        return false;
+                    }
+                }
+            } catch (IllegalAccessException e) {
+                // IllegalAccessException: 如果此 Field 对象正在执行 Java 语言访问控制,并且底层字段不可访问
+                log.error("对象字段出错,错误如下:"+e.getMessage());
+                throw new RuntimeException("无法访问字段: " + e.getMessage());
+            }
+        }
+
+        // 所有字段都为 null 或不存在
+        return true;
+    }
+
+    /**
+     * 遍历当前类及其所有父类,直到找到所需的字段或到达类层次结构的顶端
+     * @param clazz
+     * @param fieldName
+     * @return
+     */
+    public static Field getFieldFromClassOrSuperclass(Class<?> clazz, String fieldName) {
+        Class<?> currentClass = clazz;
+        while (currentClass != null) {
+            try {
+                // 尝试在当前类中获取字段
+                return currentClass.getDeclaredField(fieldName);
+            } catch (NoSuchFieldException e) {
+                // 如果当前类中没有该字段,移动到父类继续查找
+                currentClass = currentClass.getSuperclass();
+            }
+        }
+        // 如果在整个类层次结构中都没有找到该字段,则返回 null 或抛出异常
+        return null;
+    }
+
+}

+ 18 - 0
src/main/resources/mapper/UserMapper.xml

@@ -6,4 +6,22 @@
         from users
         where official_number = #{officialNumber}
     </select>
+
+    <update id="updateUser" parameterType="com.njuzr.eaibackend.po.User">
+        UPDATE users
+        <set>
+            <if test="name != null">name=#{name},</if>
+            <if test="password != null">password=#{password}</if>
+            <if test="officialEmail != null">official_email=#{officialEmail},</if>
+            <if test="officialNumber != null">official_number=#{officialNumber},</if>
+            <if test="role != null">role=#{role},</if>
+            <if test="userAvatar != null">user_avatar=#{userAvatar},</if>
+            <if test="englishName != null">english_name=#{englishName},</if>
+            <if test="gender != null">gender=#{gender},</if>
+            <if test="birthday != null">birthday=#{birthday},</if>
+            <if test="phone != null">phone=#{phone},</if>
+            <if test="contentEmail != null">content_email=#{contentEmail}</if>
+        </set>
+        WHERE id=#{id}
+    </update>
 </mapper>