springboot 配置 jjwt

JWT 全名 JSON WEB Token 主要作用为用户身份验证, 广泛应用与前后端分离项目当中.

    JWT 的优缺点 : https://www.jianshu.com/p/af8360b83a9f

 

一、pom.xml 引入jar文件

        <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>

二、添加jwt自定义字段 application.yml

# jwt 配置
custom:
  jwt:
    # header:凭证(校验的变量名)
    header: token
    # 有效期1天(单位:s)
    expire: 5184000
    # secret: 秘钥(普通字符串)
    secret: aHR0cHM6Ly9teS5vc2NoaW5hLm5ldC91LzM2ODE4Njg=
    # 签发者
    issuer: test-kou

三、添加加密解密方法


import io.jsonwebtoken.*;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * JWT 工具类
 * <p>
 * jwt含有三部分:头部(header)、载荷(payload)、签证(signature)
 * (1)头部一般有两部分信息:声明类型、声明加密的算法(通常使用HMAC SHA256)
 * (2)载荷该部分一般存放一些有效的信息。jwt的标准定义包含五个字段:
 * - iss:该JWT的签发者
 * - sub: 该JWT所面向的用户
 * - aud: 接收该JWT的一方
 * - exp(expires): 什么时候过期,这里是一个Unix时间戳
 * - iat(issued at): 在什么时候签发的
 * (3)签证(signature) JWT最后一个部分。该部分是使用了HS256加密后的数据;包含三个部分
 *
 * @author kou
 */
@ConfigurationProperties(prefix = "custom.jwt")
@Data
@Component
public class JwtUtil {

    private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);

    // 秘钥
    private String secret;

    // 有效时间
    private Long expire;

    // 用户凭证
    private String header;

    // 签发者
    private String issuer;

    /**
     * 生成token签名
     *
     * @param subject
     * @return
     */
    public String generateToken(String subject) {

        final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        Date now = new Date();
        // 过期时间
        Date expireDate = new Date(now.getTime() + expire * 1000);

        //Create the Signature SecretKey
        final byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(Base64.getEncoder().encodeToString(getSecret().getBytes()));
        final Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

        final Map<String, Object> headerMap = new HashMap<>();
        headerMap.put("alg", "HS256");
        headerMap.put("typ", "JWT");

        //add JWT Parameters
        final JwtBuilder builder = Jwts.builder()
                .setHeaderParams(headerMap)
                .setSubject(subject)
                .setIssuedAt(now)
                .setExpiration(expireDate)
                .setIssuer(getIssuer())
                .signWith(signatureAlgorithm, signingKey);

        logger.info("JWT[" + builder.compact() + "]");
        return builder.compact();

    }

    /**
     * 解析token
     *
     * @param token token
     * @return
     */
    public Claims parseToken(String token) {

        Claims claims = null;
        try {
            final byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(Base64.getEncoder().encodeToString(getSecret().getBytes()));
            claims = Jwts.parser().setSigningKey(apiKeySecretBytes).parseClaimsJws(token).getBody();
            logger.info("Parse JWT token by: ID: {}, Subject: {}, Issuer: {}, Expiration: {}", claims.getId(), claims.getSubject(), claims.getIssuer(), claims.getExpiration());
        } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException
                | IllegalArgumentException e) {
            logger.info("Parse JWT errror " + e.getMessage());
            return null;
        }
        return claims;
    }

    /**
     * 判断token是否过期
     *
     * @param expiration
     * @return
     */
    public boolean isExpired(Date expiration) {

        return expiration.before(new Date());
    }

}

 

四、添加拦截方法,拦截token处理,我这边使用的是filter拦截处理,根据自己需求自定


import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;

/**
 * 如果请求中(请求头或者Cookie)中存在JWT,则:
 * 1、解析JWT并查找对应的用户信息,然后加入request attribute中
 * 2、更新Cookie时间、更新JWT失效时间放入Header
 * <p>
 * OncePerRequestFilter 一次请求只进入一次filter
 */
@Component
@Slf4j
public class JWTFilter extends OncePerRequestFilter {

    public static final String SECURITY_USER = "SECURITY_USER";

    // 设置不需要校验的路径
    private static final String[] NOT_CHECK_URL = {"/login", "/registered"};

    @Autowired
    private JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        // 判断是否需要对token处理
        if (!isNotCheck(request.getRequestURI())) {
            // 获取token
            String token = getToken(request);

            if (StringUtils.isBlank(token)) {
                log.info("请求无效,原因:{} 为空!", jwtUtil.getHeader());
                request.setAttribute("exceptionCode", SystemParameters.TOKEN_IS_NULL.getIndex());
                request.setAttribute("exceptionMessage", "请求无效,原因:" + jwtUtil.getHeader() + " 为空!");
                request.getRequestDispatcher("/exception/authentication").forward(request, response);
                // throw new AuthenticationException(SystemParameters.TOKEN_IS_NULL.getIndex(),  "请求无效,原因:" + jwtUtil.getHeader() + " 为空!");
                return;
            }

            Claims claims = jwtUtil.parseToken(token);

            // 判断签名信息
            if (null != claims && !claims.isEmpty() && !jwtUtil.isExpired(claims.getExpiration())) {
                // 获取签名用户信息
                String userId = claims.getSubject();
                //获取相应的用户信息,可以在过滤器中先行获取,也可以先保存用户ID,在需要时进行获取
                // User user = usersService.findById(Long.valueOf(userId));

                request.setAttribute(SECURITY_USER, userId);
                Cookie jwtCookie = new Cookie(jwtUtil.getHeader(), URLEncoder.encode(token, "UTF-8"));
                jwtCookie.setHttpOnly(true);
                jwtCookie.setPath("/");
                response.addCookie(jwtCookie);

                response.addHeader(jwtUtil.getHeader(), token);
            } else {
                log.info("{} 无效,请重新登录!", jwtUtil.getHeader());
                request.setAttribute("exceptionCode", SystemParameters.AUTHENTICATION_FAILED.getIndex());
                request.setAttribute("exceptionMessage", jwtUtil.getHeader() + " 无效,请重新登录!");
                request.getRequestDispatcher("/exception/authentication").forward(request, response);
                // throw new AuthenticationException(SystemParameters.AUTHENTICATION_FAILED.getIndex(), jwtUtil.getHeader() + " 无效,请重新登录!");
                return;
            }
        }

        filterChain.doFilter(request, response);
    }

    /**
     * 获取token
     *
     * @param request
     * @return 返回token
     */
    private String getToken(HttpServletRequest request) {

        //先从header中获取token
        String token = request.getHeader(jwtUtil.getHeader());

        //再从cookie中获取
        if (StringUtils.isBlank(token)) {
            try {
                Cookie[] cookies = request.getCookies();
                if (cookies != null) {
                    for (Cookie cookie : cookies) {
                        if (jwtUtil.getHeader().equals(cookie.getName())) {
                            token = URLDecoder.decode(cookie.getValue(), "UTF-8");
                        }
                    }
                }
            } catch (Exception e) {
                log.error("Can NOT get jwt from cookie -> Message: {}", e);
            }
        }

        return token;
    }

    /**
     * 根据url判断是否需要校验,false需要校验
     *
     * @param url
     * @return 是否需要校验
     */
    private boolean isNotCheck(String url) {

        // 处理路径以"/" 结尾的"/"
        url = url.endsWith("/") ? url.substring(0, url.lastIndexOf("/")) : url;

        for (String path : NOT_CHECK_URL) {
            // 判断是否以 "/**" 结尾
            if (path.endsWith("/**")) {
                return url.startsWith(path.substring(0, path.lastIndexOf("/") + 1))
                        || url.equals(path.substring(0, path.lastIndexOf("/")));
            }

            // 判断url == path
            if (url.equals(path)) {
                return true;
            }
        }

        return false;
    }

}

 

五、配置过滤器


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 过滤器配置
 *
 * @author kou
 */
@Configuration
public class FilterConfig {

    @Autowired
    private JWTFilter jwtFilter;

    @Bean
    public FilterRegistrationBean registrationBean() {

        FilterRegistrationBean registration = new FilterRegistrationBean();
        // 设置过滤器
        registration.setFilter(jwtFilter);
        // 设置拦截路径
        registration.addUrlPatterns("/rest/*");
        // 设置过滤器名称
        registration.setName("JWTFilter");
        // 设置过滤器执行顺序
        registration.setOrder(1);

        return registration;
    }
}

六、添加异常处理类


/**
 * 认证异常类
 *
 * @author kou
 */
public class AuthenticationException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    //自定义错误码
    private Integer code;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    /**
     * Creates a new AuthenticationException.
     */
    public AuthenticationException() {
        super();
    }

    /**
     * Constructs a new AuthenticationException.
     *
     * @param message the reason for the exception
     */
    public AuthenticationException(String message) {
        super(message);
    }

    /**
     * Constructs a new AuthenticationException.
     *
     * @param cause the underlying Throwable that caused this exception to be thrown.
     */
    public AuthenticationException(Throwable cause) {
        super(cause);
    }

    /**
     * Constructs a new AuthenticationException.
     *
     * @param message the reason for the exception
     * @param cause   the underlying Throwable that caused this exception to be thrown.
     */
    public AuthenticationException(String message, Throwable cause) {
        super(message, cause);
    }

    /**
     * Constructs a new AuthenticationException.
     *
     * @param message the reason for the exception
     */
    public AuthenticationException(int code, String message) {
        super(message);
        this.code = code;
    }

    /**
     * Constructs a new AuthenticationException.
     *
     * @param cause the underlying Throwable that caused this exception to be thrown.
     */
    public AuthenticationException(int code, Throwable cause) {
        super(cause);
        this.code = code;
    }

    /**
     * Constructs a new AuthenticationException.
     *
     * @param message the reason for the exception
     * @param cause   the underlying Throwable that caused this exception to be thrown.
     */
    public AuthenticationException(int code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    }

}

七、添加全局异常处理类


import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 全局异常处理
 *
 * @author kou
 */
@ControllerAdvice
public class GlobalException {

    /**
     * 授权登录异常
     */
    @ResponseBody
    @ExceptionHandler(AuthenticationException.class)
    public BaseResult authenticationException(AuthenticationException e) {

        return new BaseResult(SystemParameters.FAIL.getIndex(), e.getMessage(), e.getCode());
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    BaseResult exception(Exception e) {

        return new BaseResult(SystemParameters.FAIL.getIndex(), e.getMessage(), SystemParameters.ERROR.getIndex());
    }

}

注意:

     由于使用filter过滤器,springmvc 不能进行filter拦截异常,故通过请求转发来实现统一异常拦截

八、错误异常处理,用来处理filter转发出来的请求拦截异常,进而让springmvc统一处理异常

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

/**
 * 错误控制器
 *
 * @author kou
 */
@RestController
public class ErrorController {

    @RequestMapping("/exception/authentication")
    public BaseResult authenticationException(HttpServletRequest request) {

        throw new AuthenticationException(Integer.valueOf(request.getAttribute("exceptionCode").toString()), request.getAttribute("exceptionMessage").toString());
    }
}

九、返回统一格式


public class BaseResult {

    // 结果状态码
    private int ret;
    // 结果说明
    private String msg;

    private int err;

    public BaseResult() {
    }

    public BaseResult(int ret, String msg, int err) {
        this.ret = ret;
        this.msg = msg;
        this.err = err;
    }

    public int getRet() {
        return ret;
    }

    public void setRet(int ret) {
        this.ret = ret;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public int getErr() {
        return err;
    }

    public void setErr(int err) {
        this.err = err;
    }

}

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章