SpringBoot整合JWT实现API身份校验

上一个项目采用的是session存储uid的方式来确认API调用方的合法身份,这次的app则打算采用token的方式进行身份校验,此二者的优缺点有很多博客进行了论述,这里就不谈了。

本文中的代码以及部分内容参考自
SpringBoot整合Token
原创lamarsan
————————————————
版权声明:本文为CSDN博主「lamarsan」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_16410733/article/details/96625801

这里对JWT进行简单的分析,并解释token中存储的具体内容。

JWT消息构成

一个token分3部分,按顺序为

  • 头部(header)
  • 载荷(payload)
  • 签名(signature)

三部分之间用“.”号做分隔。例如
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

header头部

  • typ: 声明类型,这里是jwt
  • alg: 声明加密的算法 通常直接使用 HMAC SHA256

(如果有需要的话可以自己实现算法并修改com.auth0.jwt的源码以提高安全性)

payload荷载

载荷就是存放有效信息的地方。jwt提供了一部分预设的属性,也可以向claim中添加自定义属性。
其中标准提供的数据为

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
  • 自定义属性存放在claim中

体现在代码中:

        token= JWT.create()
                .withClaim("userId",user.getUserId())			//自定义属性
                .withExpiresAt(DateUtil.rollMon(new Date(),1))  //一个月后过期
                .sign(Algorithm.HMAC256(SECRET));				//指定加密算法并传入密钥

签名signature

jwt的第三部分是一个签证信息,这个签证信息算法如下:

  1. 头部、荷载分别进行base64加密后,以”.”连接获得content

  2. 按照指定的加密算法(是携带密钥的)将content进行加密得到signatureBytes

  3. signatureBytes再进行base64加密 获得signature

  4. 将content和signature以”.”连接获得token。
    在这里插入图片描述
    分析一下:
    token的三段明文是可以直接base64解密的,如:

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 .eyJhdWQiOiIxMjMiLCJwYXNzd29yZCI6IjEyMyIsInVzZXJJZCI6MjMzfQ .0Piz_kzhriwwcoHI7o5A7evyeDMDGBXStK5PxTtmadQ
    

第一段解密可知第三段的加密算法类型。
在这里插入图片描述
第二段解密可知所有默认字段以及自定义字段,所以这里绝对不能明文传输一些敏感信息。
在这里插入图片描述
第三段解密后将得到signatureBytes。
在这里插入图片描述
如果不知道密钥,是无法通过header中指定的加密算法将token前两段映射到第三段的。
也就是说,如果不知道这个密钥,那么即使截获了token,得知前两段的内容,都没有办法伪造出能够通过服务器token校验的signature,JWT就是通过这一特性来确保签名的有效性。

如果校验通过了,说明这个签名确实是我们的服务器下发给客户端的,携带该token的用户具有合法的身份,根据payload中的信息,就能确认他的登陆身份。所以我们只需要在token的payload中放置一个userId即可(必要的话还要添加一个过期时间)。

下面上代码

依赖:`

    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.4.0</version>
    </dependency>`

创建token的工具类

public class TokenUtil {

    public static String SECRET="你的密钥";

	public static String getToken(User user) {
        String token="";
        token= JWT.create()
                .withClaim("userId",user.getUserId())			//自定义属性
                .withExpiresAt(DateUtil.rollMon(new Date(),1))  //一个月后过期
                .sign(Algorithm.HMAC256(SECRET));			    //指定加密算法并传入密钥
        return token;
    }
}

自定义注解—跳过token验证

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required() default true;
}

自定义注解—需要token验证

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
    boolean required() default true;
}

拦截器

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.poorteam.poorguy.webservice.common.anotations.PassToken;
import com.poorteam.poorguy.webservice.common.anotations.UserLoginToken;
import com.poorteam.poorguy.webservice.repository.datasourse1.UserMapper;
import com.poorteam.poorguy.webservice.utils.TokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
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.lang.reflect.Method;

public class AuthenticationInterceptor implements HandlerInterceptor {
    @Autowired
    UserMapper userMapper;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        String token = httpServletRequest.getHeader("token");// 从 http 请求头中取出 token
        // 如果不是映射到方法直接通过
        if (!(object instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();
        //检查是否有passtoken注释,有则跳过认证
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true;
            }
        }
        //检查有没有需要用户权限的注解
        if (method.isAnnotationPresent(UserLoginToken.class)) {
            UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
            if (userLoginToken.required()) {
                // 执行认证
                if (token == null) {
                    throw new RuntimeException("无token,请重新登录");
                }
                // 获取 token 中的 user id
                String userId;
                try {
                    userId = JWT.decode(token).getClaim("userId").asString();
                } catch (JWTDecodeException j) {
                    throw new RuntimeException("401");
                }
                if(userId==null||userId.length()==0){
                    throw new RuntimeException("userId为空");
                }
                // 验证 token
                JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(TokenUtil.SECRET)).build();
                try {
                    jwtVerifier.verify(token);      //这里一旦出现异常就说明校验不通过
                    //把uid放到request中去
                    httpServletRequest.setAttribute("userId",Long.valueOf(userId));
                } catch (JWTVerificationException e) {
                    throw new RuntimeException("401");
                }
                return true;
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest,
                           HttpServletResponse httpServletResponse,
                           Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest,
                                HttpServletResponse httpServletResponse,
                                Object o, Exception e) throws Exception {
    }
}


配置拦截器

/**
 * 配置拦截器
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/**");
    }
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
}


在controller层根据需求给接口添加注解

 @PassToken  //注册接口 跳过token验证
    @RequestMapping(value = "sendRegisterValidateCode", method = RequestMethod.POST)
    BaseResponse sendRegisterValidateCode(@RequestBody JSONObject jsonObject) {
        //获取参数
        String phone = jsonObject.getString("phone");   //手机号

        //校验参数
        if(!phone.matches("^[1](([3][0-9])|([4][5-9])|([5][0-3,5-9])|([6][5,6])|([7][0-8])|([8][0-9])|([9][1,8,9]))[0-9]{8}")){
            return BaseResponse.createByErrorMessage("手机号无效");
        }

        return loginServiceImpl.sendRegisterValidateCode(phone);
    }

收工!在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值