Quellcode durchsuchen

后端认证框架搭建

uncln vor 4 Jahren
Ursprung
Commit
e34d07970f
24 geänderte Dateien mit 500 neuen und 6 gelöschten Zeilen
  1. 18 0
      pom.xml
  2. 84 0
      src/main/java/com/seecoder/dataanalysis/aspect/auth/AuthAspect.java
  3. 16 0
      src/main/java/com/seecoder/dataanalysis/aspect/auth/Authorized.java
  4. 2 0
      src/main/java/com/seecoder/dataanalysis/data/dao/competency/CompetencyTreeDao.java
  5. 5 0
      src/main/java/com/seecoder/dataanalysis/data/dao/eval/UserDao.java
  6. 0 1
      src/main/java/com/seecoder/dataanalysis/data/entity/competency/CompetencyTree.java
  7. 0 1
      src/main/java/com/seecoder/dataanalysis/data/entity/eval/EvalExamInfo.java
  8. 0 1
      src/main/java/com/seecoder/dataanalysis/data/entity/eval/EvalExamRecord.java
  9. 19 1
      src/main/java/com/seecoder/dataanalysis/data/entity/eval/User.java
  10. 13 0
      src/main/java/com/seecoder/dataanalysis/enums/UserRole.java
  11. 17 0
      src/main/java/com/seecoder/dataanalysis/exception/MyServiceException.java
  12. 19 0
      src/main/java/com/seecoder/dataanalysis/exception/MyServiceExceptionHandler.java
  13. 34 0
      src/main/java/com/seecoder/dataanalysis/logic/user/UserService.java
  14. 19 0
      src/main/java/com/seecoder/dataanalysis/logic/user/convertor/UserConvertor.java
  15. 55 0
      src/main/java/com/seecoder/dataanalysis/logic/user/impl/UserServiceImpl.java
  16. 39 0
      src/main/java/com/seecoder/dataanalysis/util/JwtUtil.java
  17. 29 0
      src/main/java/com/seecoder/dataanalysis/vo/PortalUserVO.java
  18. 16 0
      src/main/java/com/seecoder/dataanalysis/vo/UpdateUserVO.java
  19. 19 0
      src/main/java/com/seecoder/dataanalysis/vo/UserVO.java
  20. 34 0
      src/main/java/com/seecoder/dataanalysis/web/Response.java
  21. 26 0
      src/main/java/com/seecoder/dataanalysis/web/controller/TestController.java
  22. 31 0
      src/main/java/com/seecoder/dataanalysis/web/controller/UserController.java
  23. 5 1
      src/main/resources/application-dev.yml
  24. 0 1
      src/main/resources/application.properties

+ 18 - 0
pom.xml

@@ -40,6 +40,24 @@
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
+        <!-- aop -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+            <version>2.6.3</version>
+        </dependency>
+
+        <!-- jwt -->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+            <version>3.11.0</version>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
     </dependencies>
 
     <build>

+ 84 - 0
src/main/java/com/seecoder/dataanalysis/aspect/auth/AuthAspect.java

@@ -0,0 +1,84 @@
+package com.seecoder.dataanalysis.aspect.auth;
+
+
+import com.auth0.jwt.exceptions.JWTVerificationException;
+import com.auth0.jwt.interfaces.Claim;
+import com.seecoder.dataanalysis.data.entity.eval.User;
+import com.seecoder.dataanalysis.exception.MyServiceException;
+import com.seecoder.dataanalysis.logic.user.UserService;
+import com.seecoder.dataanalysis.logic.user.convertor.UserConvertor;
+import com.seecoder.dataanalysis.util.JwtUtil;
+import com.seecoder.dataanalysis.vo.PortalUserVO;
+import com.seecoder.dataanalysis.vo.UpdateUserVO;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * @author mars
+ */
+@Aspect
+@Configuration
+@Order(1)
+public class AuthAspect {
+
+    private final UserService userService;
+
+    @Autowired
+    public AuthAspect(UserService userService) {
+        this.userService = userService;
+    }
+
+    private User getUserByToken(String token) throws JWTVerificationException {
+        try {
+            Claim claim = JwtUtil.parseJwt(token);
+            PortalUserVO userVO = claim.as(PortalUserVO.class);
+            return new User(userVO.getId(), userVO.getName(), userVO.getRole(), userVO.getEmail(), userVO.getPhone());
+        } catch (MyServiceException e) {
+            throw new  MyServiceException(e.getCode(), e.getMessage());
+        }
+    }
+
+    @Before(value = "execution(public * com.seecoder.dataanalysis.web.controller.*.*(..)) && @annotation(authorized)")
+    public void authCheck(JoinPoint joinPoint, Authorized authorized) {
+        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
+        String token = Optional.ofNullable(httpServletRequest.getHeader("Authorization")).
+                orElseThrow(() -> new MyServiceException("A0223", "用户未获得第三方登录授权"));
+        User user = getUserByToken(token);
+        // 创建或者更新用户
+        if (userService.existsById(user.getUserId())) {
+            userService.updateUser(new UpdateUserVO(user.getUserId(), user.getUsername(), user.getEmail()));
+        } else {
+            //TODO 这里没有对HR和TEACHER作区分
+            userService.createUser(UserConvertor.convertToVO(user));
+        }
+
+        // 判断切面方法是否包含当前token对应的角色
+        if (!Arrays.stream(authorized.roles()).collect(Collectors.toList()).contains(user.getRole())) {
+            throw new MyServiceException("A0301", "访问未授权");
+        } else {
+            // 将token的对象赋值给切面方法的user参数
+            Object[] objects = joinPoint.getArgs();
+            for (Object o : objects) {
+                if (o instanceof User) {
+                    System.out.println(user);
+                    BeanUtils.copyProperties(user, o);
+                    break;
+                }
+            }
+        }
+    }
+}

+ 16 - 0
src/main/java/com/seecoder/dataanalysis/aspect/auth/Authorized.java

@@ -0,0 +1,16 @@
+package com.seecoder.dataanalysis.aspect.auth;
+
+import com.seecoder.dataanalysis.enums.UserRole;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Authorized {
+    UserRole[]  roles() default {
+            UserRole.ADMIN, UserRole.STUDENT, UserRole.HR, UserRole.TEACHER, UserRole.STAFF, UserRole.SEEC, UserRole.SEECX, UserRole.ASSISTANT};
+    String message() default "";
+}

+ 2 - 0
src/main/java/com/seecoder/dataanalysis/data/dao/competency/CompetencyTreeDao.java

@@ -5,4 +5,6 @@ import org.springframework.data.mongodb.repository.MongoRepository;
 
 public interface CompetencyTreeDao extends MongoRepository<CompetencyTree, String> {
 
+    CompetencyTree findByStudentId(Integer studentId);
+
 }

+ 5 - 0
src/main/java/com/seecoder/dataanalysis/data/dao/eval/UserDao.java

@@ -3,5 +3,10 @@ package com.seecoder.dataanalysis.data.dao.eval;
 import com.seecoder.dataanalysis.data.entity.eval.User;
 import org.springframework.data.mongodb.repository.MongoRepository;
 
+
 public interface UserDao extends MongoRepository<User, String> {
+
+    User findByUserId(Integer userId);
+
+    Boolean existsByUserId(Integer userId);
 }

+ 0 - 1
src/main/java/com/seecoder/dataanalysis/data/entity/competency/CompetencyTree.java

@@ -22,7 +22,6 @@ public class CompetencyTree {
      * 能力树ID
      **/
     @Id
-    @Field("competency_id")
     private Integer competencyId;
 
     /**

+ 0 - 1
src/main/java/com/seecoder/dataanalysis/data/entity/eval/EvalExamInfo.java

@@ -23,7 +23,6 @@ public class EvalExamInfo {
      * 试卷Id
      */
     @Id
-    @Field("exam_id")
     private Integer examId;
 
     /**

+ 0 - 1
src/main/java/com/seecoder/dataanalysis/data/entity/eval/EvalExamRecord.java

@@ -24,7 +24,6 @@ public class EvalExamRecord {
      * 记录Id
      */
     @Id
-    @Field("record_id")
     private Integer recordId;
 
     /**

+ 19 - 1
src/main/java/com/seecoder/dataanalysis/data/entity/eval/User.java

@@ -1,6 +1,7 @@
 package com.seecoder.dataanalysis.data.entity.eval;
 
 
+import com.seecoder.dataanalysis.enums.UserRole;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
@@ -20,7 +21,6 @@ public class User {
      * 学生ID
      **/
     @Id
-    @Field("user_id")
     private Integer userId;
 
     /**
@@ -28,4 +28,22 @@ public class User {
      **/
     @Field("username")
     private String username;
+
+    /**
+     * 邮箱
+     */
+    @Field("role")
+    private UserRole role;
+
+    /**
+     * 邮箱
+     */
+    @Field("email")
+    private String email;
+
+    /**
+     * 手机
+     */
+    @Field("phone")
+    private String phone;
 }

+ 13 - 0
src/main/java/com/seecoder/dataanalysis/enums/UserRole.java

@@ -0,0 +1,13 @@
+package com.seecoder.dataanalysis.enums;
+
+public enum UserRole {
+
+    TEACHER, //老师
+    STUDENT, //学生
+    HR, //HR
+    ADMIN, //网站管理员
+    STAFF, //员工
+    SEEC, //Seecoder开发人员
+    SEECX, //Seecoder开发人员X
+    ASSISTANT
+}

+ 17 - 0
src/main/java/com/seecoder/dataanalysis/exception/MyServiceException.java

@@ -0,0 +1,17 @@
+package com.seecoder.dataanalysis.exception;
+
+import lombok.Data;
+
+@Data
+public class MyServiceException extends RuntimeException {
+    private String code;
+
+    public MyServiceException(String message, Throwable root) {
+        super(message, root);
+    }
+
+    public MyServiceException(String code, String message) {
+        super(message);
+        this.code = code;
+    }
+}

+ 19 - 0
src/main/java/com/seecoder/dataanalysis/exception/MyServiceExceptionHandler.java

@@ -0,0 +1,19 @@
+package com.seecoder.dataanalysis.exception;
+
+import com.seecoder.dataanalysis.web.Response;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * @author mars
+ */
+@RestControllerAdvice(basePackages = {"com.seecoder.dataanalysis.web.controller", "com.seecoder.dataanalysis.aspect.auth"})
+public class MyServiceExceptionHandler {
+    @ExceptionHandler(MyServiceException.class)
+    @ResponseBody
+    private Response handleMyServiceException(MyServiceException e) {
+        return new Response(e.getCode(), e.getMessage());
+    }
+
+}

+ 34 - 0
src/main/java/com/seecoder/dataanalysis/logic/user/UserService.java

@@ -0,0 +1,34 @@
+package com.seecoder.dataanalysis.logic.user;
+
+import com.seecoder.dataanalysis.vo.UpdateUserVO;
+import com.seecoder.dataanalysis.vo.UserVO;
+
+public interface UserService {
+    /**
+     * 新建用户
+     * @param userVO
+     * @return 从门户系统新建的用户
+     */
+    UserVO createUser(UserVO userVO);
+
+    /**
+     * 通过id获得用户
+     * @param id
+     * @return
+     */
+    UserVO getUserById(Integer id);
+
+    /**
+     * 从token获得的信息更新用户,保持和门户系统的一致。只能更新邮箱和用户名
+     * @param updateUserVO
+     * @return
+     */
+    UserVO updateUser(UpdateUserVO updateUserVO);
+
+    /**
+     * 判断某用户是否存在
+     * @param id
+     * @return
+     */
+    boolean existsById(Integer id);
+}

+ 19 - 0
src/main/java/com/seecoder/dataanalysis/logic/user/convertor/UserConvertor.java

@@ -0,0 +1,19 @@
+package com.seecoder.dataanalysis.logic.user.convertor;
+
+import com.seecoder.dataanalysis.data.entity.eval.User;
+import com.seecoder.dataanalysis.vo.UserVO;
+import org.springframework.beans.BeanUtils;
+
+public class UserConvertor {
+    public static User convertToEntity(UserVO userVO) {
+        User user = new User();
+        BeanUtils.copyProperties(userVO, user);
+        return user;
+    }
+
+    public static UserVO convertToVO(User user) {
+        UserVO userVO = new UserVO();
+        BeanUtils.copyProperties(user, userVO);
+        return userVO;
+    }
+}

+ 55 - 0
src/main/java/com/seecoder/dataanalysis/logic/user/impl/UserServiceImpl.java

@@ -0,0 +1,55 @@
+package com.seecoder.dataanalysis.logic.user.impl;
+
+import com.seecoder.dataanalysis.data.dao.eval.UserDao;
+import com.seecoder.dataanalysis.data.entity.eval.User;
+import com.seecoder.dataanalysis.exception.MyServiceException;
+import com.seecoder.dataanalysis.logic.user.UserService;
+import com.seecoder.dataanalysis.logic.user.convertor.UserConvertor;
+import com.seecoder.dataanalysis.vo.UpdateUserVO;
+import com.seecoder.dataanalysis.vo.UserVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class UserServiceImpl implements UserService {
+
+    private final UserDao userDao;
+
+    @Autowired
+    public UserServiceImpl(UserDao userDao) {
+        this.userDao = userDao;
+    }
+
+    @Override
+    public UserVO createUser(UserVO userVO) {
+        User user = userDao.save(UserConvertor.convertToEntity(userVO));
+        return UserConvertor.convertToVO(user);
+    }
+
+    @Override
+    public UserVO getUserById(Integer id) {
+        User user = userDao.findByUserId(id);
+        if (user != null) {
+            return UserConvertor.convertToVO(user);
+        } else {
+            throw new MyServiceException("A0201", "用户账号不存在");
+        }
+    }
+
+    @Override
+    public UserVO updateUser(UpdateUserVO updateUserVO) {
+        User user = userDao.findByUserId(updateUserVO.getId());
+        if (user != null) {
+            user.setEmail(updateUserVO.getEmail());
+            user.setUsername(updateUserVO.getName());
+            return UserConvertor.convertToVO(user);
+        } else {
+            throw new MyServiceException("A0201", "用户账号不存在");
+        }
+    }
+
+    @Override
+    public boolean existsById(Integer id) {
+        return userDao.existsByUserId(id);
+    }
+}

+ 39 - 0
src/main/java/com/seecoder/dataanalysis/util/JwtUtil.java

@@ -0,0 +1,39 @@
+package com.seecoder.dataanalysis.util;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.JWTVerificationException;
+import com.auth0.jwt.exceptions.TokenExpiredException;
+import com.auth0.jwt.interfaces.Claim;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.seecoder.dataanalysis.exception.MyServiceException;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+public class JwtUtil {
+    private static String jwtSecret;
+
+    @Value("${jwt.secret}")
+    public void setJwtSecret(String jwtSecret) { //通过set让static方法读取配置文件中的值
+        JwtUtil.jwtSecret = jwtSecret;
+    }
+
+    public static Claim parseJwt(String token){
+        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecret)).build();
+        DecodedJWT decodedjwt;
+        try {
+            decodedjwt = jwtVerifier.verify(token);
+        } catch (TokenExpiredException e) {
+            throw new MyServiceException("A0230", "用户登陆已过期");
+        } catch (JWTVerificationException | IllegalArgumentException e) { //解析错误 或者 token写错
+            throw new MyServiceException("A0200", "登陆失败");
+        }
+//        if (decodedjwt.getClaim("exp").asDate().before(new Date())) {
+//            throw new  MyServiceException("A0230", "用户登陆已过期");
+//        }
+        return decodedjwt.getClaim("user_info");
+    }
+
+}

+ 29 - 0
src/main/java/com/seecoder/dataanalysis/vo/PortalUserVO.java

@@ -0,0 +1,29 @@
+package com.seecoder.dataanalysis.vo;
+
+import com.seecoder.dataanalysis.enums.UserRole;
+import lombok.Data;
+import lombok.ToString;
+
+@Data
+@ToString
+public class PortalUserVO {
+    private Integer id;
+
+    private String phone;
+
+    private String username;
+
+    private String name;
+
+    private String email;
+
+    private String photo;
+
+    private UserRole role = UserRole.STUDENT;
+
+    private String signature = "无";
+
+    private String createdTime;
+
+    private Boolean valid = true;
+}

+ 16 - 0
src/main/java/com/seecoder/dataanalysis/vo/UpdateUserVO.java

@@ -0,0 +1,16 @@
+package com.seecoder.dataanalysis.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.ToString;
+
+@Data
+@AllArgsConstructor
+@ToString
+public class UpdateUserVO {
+    private Integer id;
+
+    private String name;
+
+    private String email;
+}

+ 19 - 0
src/main/java/com/seecoder/dataanalysis/vo/UserVO.java

@@ -0,0 +1,19 @@
+package com.seecoder.dataanalysis.vo;
+
+import com.seecoder.dataanalysis.enums.UserRole;
+import lombok.Data;
+import lombok.ToString;
+
+@Data
+@ToString
+public class UserVO {
+    private Integer id;
+
+    private UserRole role;
+
+    private String name;
+
+    private String email;
+
+    private String phone;
+}

+ 34 - 0
src/main/java/com/seecoder/dataanalysis/web/Response.java

@@ -0,0 +1,34 @@
+package com.seecoder.dataanalysis.web;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author mars
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Response {
+    private String code;
+    private String msg;
+    private Object result;
+
+    public Response(String code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public static Response buildSuccess(Object result) {
+        return new Response("00000", "Success", result);
+    }
+
+    public static Response buildFailed(String code, RuntimeException e) {
+        return new Response(code, e.getLocalizedMessage(), null);
+    }
+
+    public static Response buildFailed(String code, String msg) {
+        return new Response(code, msg, null);
+    }
+}

+ 26 - 0
src/main/java/com/seecoder/dataanalysis/web/controller/TestController.java

@@ -0,0 +1,26 @@
+package com.seecoder.dataanalysis.web.controller;
+
+
+import com.seecoder.dataanalysis.aspect.auth.Authorized;
+import com.seecoder.dataanalysis.enums.UserRole;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author uncln
+ */
+@RestController
+@RequestMapping(path = "/test")
+public class TestController {
+
+    @Autowired
+    public TestController(){}
+
+    @Authorized(roles = {UserRole.STUDENT})
+    @GetMapping("/test1")
+    public String testRequest() {
+        return "OK";
+    }
+}

+ 31 - 0
src/main/java/com/seecoder/dataanalysis/web/controller/UserController.java

@@ -0,0 +1,31 @@
+package com.seecoder.dataanalysis.web.controller;
+
+import com.seecoder.dataanalysis.aspect.auth.Authorized;
+import com.seecoder.dataanalysis.data.entity.eval.User;
+import com.seecoder.dataanalysis.logic.user.UserService;
+import com.seecoder.dataanalysis.web.Response;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author mars
+ */
+@RestController
+@RequestMapping(path = "/api/user")
+public class UserController {
+    private final UserService userService;
+    @Autowired
+    public UserController(UserService userService) {
+        this.userService = userService;
+    }
+
+    @GetMapping("")
+    @Authorized()
+    public Response getCurrentUser(User user) {
+        return Response.buildSuccess(userService.getUserById(user.getUserId()));
+    }
+
+}

+ 5 - 1
src/main/resources/application-dev.yml

@@ -6,4 +6,8 @@ spring:
       port: 27017
       username: root
       password: root
-      database: seec_data
+      database: seec_data
+
+jwt:
+  header: Authorization
+  secret: UenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRWUenM4KGb6DskRW

+ 0 - 1
src/main/resources/application.properties

@@ -1 +0,0 @@
-