Spring Boot JWT 用户认证

  1. 添加 Maven 引用 
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.0</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.11.0</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
        <version>0.11.0</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.3.7</version>
    </dependency>

     
  2. 添加 annotation JwtIgnore 用于忽略不需要验证的接口
    package com.vipsoft.web.boot.annotation;
    
    import java.lang.annotation.*;
    
    
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface JwtIgnore {
    }
  3. 添加工具类 JwtUtils

    package com.vipsoft.web.boot.utils;
    
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    import java.util.Date;
    
    
    @ConfigurationProperties(prefix = "vipsoft.jwt")
    @Component
    public class JwtUtils {
        private Logger logger = LoggerFactory.getLogger(getClass());
        private String secret;
        private long expire;
        private String header;
    
        /**
         * 生成jwt token
         */
        public String generateToken(long userId) {
            Date nowDate = new Date();
            //过期时间
            Date expireDate = new Date(nowDate.getTime() + expire * 1000);
    
            return Jwts.builder()
                    .setHeaderParam("typ", "JWT")
                    .setSubject(userId+"")
                    .setIssuedAt(nowDate)
                    .setExpiration(expireDate)
                    .signWith(SignatureAlgorithm.HS256, secret)
                    .compact();
        }
    
        public Claims getClaimByToken(String token) {
            try {
                return Jwts.parser()
                        .setSigningKey(secret)
                        .parseClaimsJws(token)
                        .getBody();
            }catch (Exception e){
                logger.debug("validate is token error ", e);
                return null;
            }
        }
    
        /**
         * token是否过期
         * @return  true:过期
         */
        public boolean isTokenExpired(Date expiration) {
            return expiration.before(new Date());
        }
    
        public String getSecret() {
            return secret;
        }
    
        public void setSecret(String secret) {
            this.secret = secret;
        }
    
        public long getExpire() {
            return expire;
        }
    
        public void setExpire(long expire) {
            this.expire = expire;
        }
    
        public String getHeader() {
            return header;
        }
    
        public void setHeader(String header) {
            this.header = header;
        }
    }

  4. 添加拦截器 AuthorizationInterceptor

    package com.vipsoft.web.boot.interceptor;
    
    
    import cn.hutool.core.util.StrUtil;
    import com.vipsoft.web.boot.annotation.JwtIgnore;
    import com.vipsoft.web.boot.exception.CustomException;
    import com.vipsoft.web.boot.utils.JwtUtils;
    import io.jsonwebtoken.Claims;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpMethod;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Component;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    
    @Component
    public class AuthorizationInterceptor extends HandlerInterceptorAdapter {
        private Logger logger = LoggerFactory.getLogger(this.getClass());
    
        @Autowired
        private JwtUtils jwtUtils;
    
    
        public static final String USER_KEY = "userId";
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            // 忽略带JwtIgnore注解的请求, 不做后续token认证校验
            if (handler instanceof HandlerMethod) {
                HandlerMethod handlerMethod = (HandlerMethod) handler;
                JwtIgnore jwtIgnore = handlerMethod.getMethodAnnotation(JwtIgnore.class);
                if (jwtIgnore != null) {
                    return true;
                }
            }
    
            if (HttpMethod.OPTIONS.equals(request.getMethod())) {
                response.setStatus(HttpServletResponse.SC_OK);
                return true;
            }
    
            //获取用户凭证
            String token = request.getHeader(jwtUtils.getHeader());
            if (StrUtil.isBlank(token)) {
                token = request.getParameter(jwtUtils.getHeader());
            }
    
            //凭证为空
            if (StrUtil.isBlank(token)) {
                throw new CustomException(HttpStatus.UNAUTHORIZED.value(), "token 不能为空");
            }
    
            Claims claims = jwtUtils.getClaimByToken(token);
            if (claims == null || jwtUtils.isTokenExpired(claims.getExpiration())) {
                throw new CustomException(HttpStatus.UNAUTHORIZED.value(), "token  失效,请重新登录");
            }
    
            //设置userId到request里,后续根据userId,获取用户信息
            request.setAttribute(USER_KEY, Long.parseLong(claims.getSubject()));
    
            return true;
        }
    }

  5. 添加 WebConfig,启用拦截器

    package com.vipsoft.web.boot.config;
    
    import com.vipsoft.web.boot.interceptor.AuthorizationInterceptor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    
    @Configuration
    public class WebConfig implements WebMvcConfigurer {
        @Autowired
        private AuthorizationInterceptor authorizationInterceptor;
    
        /**
         * 添加拦截器
         */
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //拦截路径可自行配置多个 可用 ,分隔开
            registry.addInterceptor(authorizationInterceptor).addPathPatterns("/**");
        }
    
        /**
         * 跨域支持
         *
         * @param registry
         */
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOrigins("*")
                    .allowCredentials(true)
                    .allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH", "OPTIONS", "HEAD")
                    .maxAge(3600 * 24);
        }
    
    }

  6. 添加返回对象 Result

    package com.vipsoft.web.boot.utils;
    
     
    
    /**
     * 返回的对象,项目中要移到Core中
     */ 
    public class Result<T> {
        //操作代码
        int code;
        //提示信息
        String message;
    
        T Data;
    
        public Result(int code, String message){
            this.code = code;
            this.message = message;
        }
    
        public Result(int code, String message,T data){
            this.code = code;
            this.message = message;
            this.setData(data);
        }
    
        public int getCode() {
            return code;
        }
    
        public void setCode(int code) {
            this.code = code;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        public T getData() {
            return Data;
        }
    
        public void setData(T data) {
            Data = data;
        }
    }

  7. 添加自定义异常类 CustomException.java 用于拦截器中的异常捕获

    package com.vipsoft.web.boot.exception;
    
    public class CustomException extends RuntimeException {
        private static final long serialVersionUID = 1L;
    
        private int code;
        private String msg;
    
        public CustomException(String msg) {
            super(msg);
            this.code = 500;
            this.msg = msg;
        }
    
        public CustomException(String msg, Throwable e) {
            super(msg, e);
            this.msg = msg;
        }
    
        public CustomException(int code, String msg) {
            super(msg);
            this.code = code;
            this.msg = msg;
        }
    
        public CustomException(int code, String msg, Throwable e) {
            super(msg, e);
            this.code = code;
            this.msg = msg;
        }
    
        public int getCode() {
            return code;
        }
    
        public void setCode(int code) {
            this.code = code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    
    
    }

  8. 添加全局异常处理类 GlobalExceptionHandler.java , 可以将 返回 前端的数据处理成 JSON格式

    package com.vipsoft.web.boot.exception;
    
    import com.vipsoft.web.boot.utils.Result;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.validation.BindException;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.FieldError;
    import org.springframework.validation.ObjectError;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    import java.util.List;
    
    /**
     * 全局异常处理器
     */
    @RestControllerAdvice
    public class GlobalExceptionHandler {
    
        private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
        /**
         * 处理自定义异常
         */
        @ExceptionHandler(CustomException.class)
        public Result handleException(CustomException e) {
            // 打印异常信息
            logger.error("### 异常信息:{} ###", e.getMessage());
            return new Result(e.getCode(), e.getMessage());
        }
    
        /**
         * 参数错误异常
         */
        @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
        public Result handleException(Exception e) {
    
            if (e instanceof MethodArgumentNotValidException) {
                MethodArgumentNotValidException validException = (MethodArgumentNotValidException) e;
                BindingResult result = validException.getBindingResult();
                StringBuffer errorMsg = new StringBuffer();
                if (result.hasErrors()) {
                    List<ObjectError> errors = result.getAllErrors();
                    errors.forEach(p -> {
                        FieldError fieldError = (FieldError) p;
                        errorMsg.append(fieldError.getDefaultMessage()).append(",");
                        logger.error("### 请求参数错误:{" + fieldError.getObjectName() + "},field{" + fieldError.getField() + "},errorMessage{" + fieldError.getDefaultMessage() + "}");
                    });
                }
            } else if (e instanceof BindException) {
                BindException bindException = (BindException) e;
                if (bindException.hasErrors()) {
                    logger.error("### 请求参数错误: {}", bindException.getAllErrors());
                }
            }
            return new Result(10001, "参数无效");
        }
    
        /**
         * 处理所有不可知的异常
         */
        @ExceptionHandler(Exception.class)
        public Result handleOtherException(Exception e) {
            //打印异常堆栈信息
            e.printStackTrace();
            // 打印异常信息
            logger.error("### 不可知的异常:{} ###", e.getMessage());
            return new Result(40001, "系统内部错误");
        }
    
    }

  9. 配置 application.yml

    vipsoft:
      jwt:
        # 加密秘钥
        secret: d3d3LnZpcHNvZnQuY29tLmNuLjQ3MjYyOTQ3LnNwcmluZyBib290
        # token有效时长,单位秒
        expire: 60 # 方便测试,设成 60 秒
        header: token

  10. 登录测试代码

        /**
         * 登录
         */
        @JwtIgnore
        @GetMapping("login")
        public Map<String, Object> login(HttpServletRequest request){
    
            //用户登录
            long  userId = 47262947;
    
            //生成token
            String token = jwtUtils.generateToken(userId);
    
            Map<String, Object> map = new HashMap<>();
            map.put("token", token);
            map.put("expire", jwtUtils.getExpire());
    
            return map;
        }







     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值