SpringBoot:前后端分离Token校验(JWT)

SpringBoot:前后端分离Token校验(JWT)

前言:我们这个token校验是用SpringBoot集成JWT实现token验证。Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519)定义了一种简洁的,自包含的方法用于通信双方之间以JSON对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。

pom.xml引入

1
2
3
4
5
6
7
8
9
10
11
<!-- JWT -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>

拦截器配置

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    package com.fufu.config.interceptor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

    /**
    * 拦截配置--调用链
    */
    @Configuration
    public class WebAppConfigurer implements WebMvcConfigurer{

    @Bean
    public HandlerInterceptor getLoginInterceptor(){
    return new SysInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry){
    String[] patterns = new String[] { "/loginToken","/*.html","/swagger-resources/**","/error","/static/*"};
    registry.addInterceptor(getLoginInterceptor())
    .addPathPatterns("/**")
    .excludePathPatterns(patterns);
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry){
    registry.addResourceHandler("/static/**")
    .addResourceLocations("classpath:/static/");
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**")//设置允许跨域的路径
    .allowedOrigins("*")//设置允许跨域请求的域名
    .allowCredentials(true)//是否允许证书 不再默认开启
    .allowedMethods("GET", "POST", "PUT", "DELETE")//设置允许的方法
    .maxAge(3600);//跨域允许时间
    }
    }
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    package com.fufu.config.interceptor;

    import com.fufu.constant.SystemConstant;
    import com.fufu.entity.CheckResult;
    import com.fufu.tools.JsonUtil;
    import com.fufu.tools.JwtUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.util.StringUtils;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;

    /**
    * 拦截器 用户权限校验
    */
    public class SysInterceptor implements HandlerInterceptor {

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

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
    if (handler instanceof HandlerMethod){
    String authHeader = request.getHeader("token");
    if (StringUtils.isEmpty(authHeader)) {
    logger.info("Token不存在");
    print(response, JsonUtil.getInstance().putData("ret", SystemConstant.JWT_ERRCODE_NULL).putData("msg", "Token不存在").pushData());
    return false;
    }else{
    //验证JWT的签名,返回CheckResult对象
    CheckResult checkResult = JwtUtils.validateJWT(authHeader);
    if (checkResult.isSuccess()) {
    return true;
    } else {
    switch (checkResult.getErrCode()) {
    // 签名验证不通过
    case SystemConstant.JWT_ERRCODE_FAIL:
    logger.info("签名验证不通过");
    print(response,JsonUtil.getInstance().putData("ret", checkResult.getErrCode()).putData("msg", "Token验证不通过").pushData());
    break;
    // 签名过期,返回过期提示码
    case SystemConstant.JWT_ERRCODE_EXPIRE:
    logger.info("签名过期");
    print(response,JsonUtil.getInstance().putData("ret", checkResult.getErrCode()).putData("msg", "Token过期").pushData());
    break;
    default:
    break;
    }
    return false;
    }
    }
    }else{
    return true;
    }
    }
    public void print(HttpServletResponse response, Object message){
    try {
    response.setStatus(HttpStatus.OK.value());
    response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
    response.setHeader("Cache-Control", "no-cache, must-revalidate");
    PrintWriter writer = response.getWriter();
    writer.write(message.toString());
    writer.flush();
    writer.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
    ModelAndView modelAndView) throws Exception {
    if(response.getStatus()==500){
    modelAndView.setViewName("/error/500");
    }else if(response.getStatus()==404){
    modelAndView.setViewName("/error/404");
    }
    }

    /**
    * 该方法也是需要当前对应的Interceptor的preHandle方法的返回值为true时才会执行。该方法将在整个请求完成之后,也就是DispatcherServlet渲染了视图执行,
    * 这个方法的主要作用是用于清理资源的,当然这个方法也只能在当前这个Interceptor的preHandle方法的返回值为true时才会执行。
    */
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
    throws Exception {
    }
    }

entity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.fufu.entity;

import io.jsonwebtoken.Claims;

/**
* 验证信息
*/
public class CheckResult {
private int errCode;

private boolean success;

private Claims claims;

public int getErrCode() {
return errCode;
}

public void setErrCode(int errCode) {
this.errCode = errCode;
}

public boolean isSuccess() {
return success;
}

public void setSuccess(boolean success) {
this.success = success;
}

public Claims getClaims() {
return claims;
}

public void setClaims(Claims claims) {
this.claims = claims;
}
}

constant

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.fufu.constant;
/**
* 系统级静态变量
*/
public class SystemConstant {
/**
* token
*/
public static final int RESCODE_REFTOKEN_MSG = 1006; //刷新TOKEN(有返回数据)
public static final int RESCODE_REFTOKEN = 1007; //刷新TOKEN

public static final int JWT_ERRCODE_NULL = 4000; //Token不存在
public static final int JWT_ERRCODE_EXPIRE = 4001; //Token过期
public static final int JWT_ERRCODE_FAIL = 4002; //验证不通过

/**
* JWT
*/
public static final String JWT_SECERT = "8677df7fc3a34e26a61c034d5ec8245d"; //密匙
public static final long JWT_TTL = 60 * 60 * 1000; //token有效时间
}

tools

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.fufu.tools;

import com.fufu.constant.SystemConstant;
import com.fufu.entity.CheckResult;
import io.jsonwebtoken.*;
import org.bouncycastle.util.encoders.Base64;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date;

/**
* jwt加密和解密的工具类
*/
public class JwtUtils {
/**
* 签发JWT
*/
public static String createJWT(String id, String subject, long ttlMillis) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
SecretKey secretKey = generalKey();
JwtBuilder builder = Jwts.builder()
.setId(id)
.setSubject(subject) // 主题
.setIssuer("fufu") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey); // 签名算法以及密匙
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
builder.setExpiration(expDate); // 过期时间
}
return builder.compact();
}
/**
* 验证JWT
*/
public static CheckResult validateJWT(String jwtStr) {
CheckResult checkResult = new CheckResult();
Claims claims = null;
try {
claims = parseJWT(jwtStr);
checkResult.setSuccess(true);
checkResult.setClaims(claims);
} catch (ExpiredJwtException e) {
checkResult.setErrCode(SystemConstant.JWT_ERRCODE_EXPIRE);
checkResult.setSuccess(false);
} catch (SignatureException e) {
checkResult.setErrCode(SystemConstant.JWT_ERRCODE_FAIL);
checkResult.setSuccess(false);
} catch (Exception e) {
checkResult.setErrCode(SystemConstant.JWT_ERRCODE_FAIL);
checkResult.setSuccess(false);
}
return checkResult;
}
public static SecretKey generalKey() {
byte[] encodedKey = Base64.decode(SystemConstant.JWT_SECERT);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}

/**
* 解析JWT字符串
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RequestMapping(value="loginToken")
@ResponseBody
public String login(String username, String password, HttpServletResponse response) {
User user = loginService.findByUsername(username);
if(user!=null){
if(user.getPassword().equals(password)){
//把token返回给客户端-->客户端保存至cookie-->客户端每次请求附带cookie参数
String JWT = JwtUtils.createJWT("1", username, SystemConstant.JWT_TTL);
return JsonUtil.getInstance().putData("ret", 1).putData("token", JWT).pushData();
}else{
return JsonUtil.getInstance().putData("ret", -1).putData("msg", "token校验失败").pushData();
}
}else{
return JsonUtil.getInstance().putData("ret", -1).putData("msg", "token校验失败").pushData();
}
}

流程

  • 新增loginToken接口
  • 把token返回给客户端
  • 客户端保存至cookie
  • 客户端每次请求附带cookie参数通过拦截器进行校验

源码:https://github.com/qq1028951741/springbootdemo or 右上角github进去,springbootdemo项目,如果对您有帮助,麻烦点下star,谢谢


人生两苦:想要却不得,拥有却失去。 –褚禄山
珍惜当下,与君共勉~


本文标题:SpringBoot:前后端分离Token校验(JWT)

文章作者:fufua

发布时间:2018年12月06日 - 10:43:13

最后更新:2018年12月06日 - 10:30:41

原始链接:https://qq1028951741.github.io/2018/12/06/SpringBoot:前后端分离Token校验(JWT)/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

this is end, thank you for reading