2 Комити 36ba4838a7 ... 04f116ec54

Аутор SHA1 Порука Датум
  liuchuanwei 04f116ec54 feat:添加登录拦截器、登录状态保存、异常处理 пре 1 месец
  liuchuanwei dfcd487ba7 feat:添加jwt工具类 пре 1 месец

+ 19 - 0
pom.xml

@@ -139,5 +139,24 @@
             <artifactId>guava</artifactId>
             <version>33.2.1-jre</version>
         </dependency>
+
+        <!-- JWT -->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-api</artifactId>
+            <version>0.10.7</version>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-impl</artifactId>
+            <version>0.10.7</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-jackson</artifactId>
+            <version>0.10.7</version>
+            <scope>runtime</scope>
+        </dependency>
     </dependencies>
 </project>

+ 24 - 0
src/main/java/com/anyway/exception/GlobalExceptionHandler.java

@@ -2,11 +2,13 @@ package com.anyway.exception;
 
 import com.anyway.util.R;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
 import org.springframework.validation.ObjectError;
 import org.springframework.web.bind.MethodArgumentNotValidException;
 import org.springframework.web.bind.annotation.ControllerAdvice;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
 
 import java.util.stream.Collectors;
 
@@ -21,14 +23,36 @@ import java.util.stream.Collectors;
 public class GlobalExceptionHandler {
     @ExceptionHandler(MethodArgumentNotValidException.class)
     @ResponseBody
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
     public R<Void> handleValidException(MethodArgumentNotValidException e){
         log.error("参数校验失败:{}", e.getMessage(), e);
         String errorMessages = e.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(";"));
         return R.fail(errorMessages);
     }
 
+    /**
+     * 登录异常
+     *
+     * @param e
+     * @return
+     */
+    @ExceptionHandler(LoginException.class)
+    @ResponseBody
+    @ResponseStatus(HttpStatus.UNAUTHORIZED)
+    public R<Void> handleLoginException(LoginException e){
+        log.error("登录失败:{}", e.getMessage(), e);
+        return R.fail("请登录");
+    }
+
+    /**
+     * 处理不可预知的异常
+     *
+     * @param e
+     * @return
+     */
     @ExceptionHandler(RuntimeException.class)
     @ResponseBody
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
     public R<Void> handleRuntimeException(RuntimeException e){
         log.error("系统异常:{}", e.getMessage(), e);
         return R.fail("系统异常:" + e.getMessage());

+ 18 - 0
src/main/java/com/anyway/exception/LoginException.java

@@ -0,0 +1,18 @@
+package com.anyway.exception;
+
+/**
+ * 业务异常
+ */
+public class LoginException extends RuntimeException{
+
+    private String message;
+
+    public LoginException(String message) {
+        this.message = message;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+}

+ 11 - 2
src/main/java/com/anyway/favor/controller/UserController.java

@@ -2,12 +2,15 @@ package com.anyway.favor.controller;
 
 import com.anyway.favor.model.User;
 import com.anyway.favor.service.UserService;
+import com.anyway.util.JwtUtils;
 import com.anyway.util.PageQuery;
 import com.anyway.util.R;
+import com.google.common.collect.ImmutableMap;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import javax.servlet.http.HttpServletResponse;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -28,10 +31,16 @@ public class UserController {
 
 
     @PostMapping("/login")
-    public R<Void> login(@RequestBody User user) {
+    public R<Void> login(@RequestBody User user, HttpServletResponse response) {
         log.info("登录表单:{}", user);
         User loginUser = userService.findByUserName(user.getUserName());
-        log.info("登录用户:{}", loginUser);
+        if (loginUser == null || !loginUser.getPassword().equals(user.getPassword())) {
+            return R.fail("用户名或密码错误");
+        }
+        //生成token并返回
+        Map<String, Object> map = ImmutableMap.of("userName", loginUser.getUserName());
+        String token = JwtUtils.createToken(map);
+        response.setHeader(JwtUtils.TOKEN_HEADER, token);
         return R.ok();
     }
 

+ 72 - 0
src/main/java/com/anyway/favor/interceptor/LoginInterceptor.java

@@ -0,0 +1,72 @@
+package com.anyway.favor.interceptor;
+
+import com.anyway.exception.LoginException;
+import com.anyway.favor.model.User;
+import com.anyway.favor.service.UserService;
+import com.anyway.util.JwtUtils;
+import com.anyway.util.SessionUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Map;
+
+/**
+ * 登录拦截器
+ *
+ * @author liuchuanwei
+ * @date 2024-08-16
+ */
+@Slf4j
+public class LoginInterceptor implements HandlerInterceptor {
+
+    @Autowired
+    private UserService userService;
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+        SessionUtils.clearCurrentUser();
+        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
+    }
+
+    @Override
+    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
+        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
+    }
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        String token = request.getHeader(JwtUtils.TOKEN_HEADER);
+        if (token != null) {
+            //校验
+            int tokenVerifyResult = JwtUtils.verifyToken(token);
+            if (tokenVerifyResult == 0) {
+                Map<String, Object> map = JwtUtils.parseToken(token);
+                if (map != null) {
+                    //校验通过
+                    //获取用户信息
+                    String userName = (String) map.get("userName");
+                    //从数据库中查询用户信息
+                    User user = userService.findByUserName(userName);
+                    //将用户信息放入session中
+                    SessionUtils.putCurrentUser(user);
+                    return true;
+                }
+            } else if (tokenVerifyResult == 1) {
+                log.info("token过期");
+            } else if (tokenVerifyResult == 2) {
+                log.info("token无效");
+            } else if (tokenVerifyResult == 3) {
+                log.info("token解析失败");
+            } else if (tokenVerifyResult == 4) {
+                log.info("token签名错误");
+            } else if (tokenVerifyResult == 5) {
+                log.info("token非法参数");
+            }
+        }
+        throw new LoginException("请登录");
+    }
+}

+ 92 - 0
src/main/java/com/anyway/util/JwtUtils.java

@@ -0,0 +1,92 @@
+package com.anyway.util;
+
+import io.jsonwebtoken.*;
+import io.jsonwebtoken.security.SignatureException;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.crypto.spec.SecretKeySpec;
+import java.security.Key;
+import java.util.Date;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * JSON Web Token 工具类
+ *
+ * @author LiuChuanWei
+ * @date 2019-12-11
+ */
+@Slf4j
+public class JwtUtils {
+
+    /**
+     * token 请求头
+     */
+    public static final String TOKEN_HEADER = "Authorization";
+
+    /**
+     * key(按照签名算法的字节长度设置key)
+     */
+    private final static String SECRET_KEY = "Yq0xcQit0QKDqGKiQDXIWoBKH3vCKeeo";
+    private final static Key KEY = new SecretKeySpec(SECRET_KEY.getBytes(), SignatureAlgorithm.HS256.getJcaName());
+
+    /**
+     * 过期时间(毫秒单位)
+     */
+    private final static long TOKEN_EXPIRE_MILLIS = 1000 * 60 * 60;
+
+    /**
+     * 创建token
+     * @param claimMap
+     * @return
+     */
+    public static String createToken(Map<String, Object> claimMap) {
+        long currentTimeMillis = System.currentTimeMillis();
+        return Jwts.builder()
+                .setId(UUID.randomUUID().toString())
+                .setIssuedAt(new Date(currentTimeMillis))    // 设置签发时间
+                .setExpiration(new Date(currentTimeMillis + TOKEN_EXPIRE_MILLIS))   // 设置过期时间
+                .addClaims(claimMap)
+                .signWith(KEY)
+                .compact();
+    }
+
+    /**
+     * 验证token
+     * @param token
+     * @return 0 验证成功,1、2、3、4、5 验证失败
+     */
+    public static int verifyToken(String token) {
+        try {
+            Jwts.parser().setSigningKey(KEY).parseClaimsJws(token);
+            return 0;
+        } catch (ExpiredJwtException e) {
+            log.error(e.getMessage(), e);
+            return 1;
+        } catch (UnsupportedJwtException e) {
+            log.error(e.getMessage(), e);
+            return 2;
+        } catch (MalformedJwtException e) {
+            log.error(e.getMessage(), e);
+            return 3;
+        } catch (SignatureException e) {
+            log.error(e.getMessage(), e);
+            return 4;
+        } catch (IllegalArgumentException e) {
+            log.error(e.getMessage(), e);
+            return 5;
+        }
+    }
+
+    /**
+     * 解析token
+     * @param token
+     * @return
+     */
+    public static Map<String, Object> parseToken(String token) {
+        return Jwts.parser()  // 得到DefaultJwtParser
+                .setSigningKey(KEY) // 设置签名密钥
+                .parseClaimsJws(token)
+                .getBody();
+    }
+}

+ 11 - 4
src/main/java/com/anyway/util/SessionUtils.java

@@ -1,7 +1,6 @@
 package com.anyway.util;
 
 import com.anyway.favor.model.User;
-import com.anyway.favor.service.UserService;
 
 /**
  * Session 工具类
@@ -10,15 +9,15 @@ import com.anyway.favor.service.UserService;
  * @date 2024-02-21
  */
 public class SessionUtils {
+
+    private static ThreadLocal<User> currentUser = new ThreadLocal<>();
     /**
      * 获取当前登录用户
      *
      * @return
      */
     public static User currentUser() {
-        //TODO 暂时固定用户
-        UserService userService = SpringUtils.getBean("userServiceImpl");
-        return userService.findById(1L);
+        return currentUser.get();
     }
     /**
      * 获取当前登录用户ID
@@ -29,4 +28,12 @@ public class SessionUtils {
         User currentUser = SessionUtils.currentUser();
         return currentUser.getId();
     }
+
+    public static void putCurrentUser(User user) {
+        currentUser.set(user);
+    }
+
+    public static void clearCurrentUser() {
+        currentUser.remove();
+    }
 }

+ 10 - 1
src/main/webapp/WEB-INF/spring-servlet.xml

@@ -24,9 +24,18 @@
 	</mvc:annotation-driven>
 	<!-- 设置使用注解的类所在的jar包 -->
 	<context:component-scan base-package="com.anyway.favor.**.controller"/>
+	<!-- 拦截器 -->
+	<mvc:interceptors>
+		<!-- 登录拦截器 -->
+		<mvc:interceptor>
+			<mvc:mapping path="/**"/>
+			<mvc:exclude-mapping path="/user/login"/>
+			<bean class="com.anyway.favor.interceptor.LoginInterceptor" />
+		</mvc:interceptor>
+	</mvc:interceptors>
 	<!-- 资源映射,因为spring mvc接受了所有的请求,所以要在这个地方设置一下资源 -->
 	<mvc:resources mapping="/asserts/**" location="/asserts/"/>
 	<!-- 对转向页面的路径解析。prefix:前缀, suffix:后缀 -->
 	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/pages/" p:suffix=".jsp" />
 
-</beans>
+</beans>