JWT(JSON WEB TOKEN)

传统session认证

传统session,cookie的认证方式,第一次请求是会把用户信息存到服务器内存中,并返回一个JSESSIONId给请求端,之后每次请求都需要携带jsessionId来做认证

传统session,cookie的不足:

JWT官网

JSON Web Token Libraries - jwt.io

官方案例:

pom坐标

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

JWT的结构:

payload中信息可能被拦截到且可以用base64解码,所以中仅仅放非敏感信息,例如用户名 、appId。而不是用户密码等敏感信息

 另外加密过程中的秘钥不能暴露

JWTCreator生成器核心源代码

public final class JWTCreator {

         
    private final Algorithm algorithm;
    // 头json
    private final String headerJson;
    // 负载json
    private final String payloadJson;


    public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCreationException {
            if (algorithm == null) {
                throw new IllegalArgumentException("The Algorithm cannot be null.");
            }
            // 给header中赋值
            headerClaims.put(PublicClaims.ALGORITHM, algorithm.getName());
            if (!headerClaims.containsKey(PublicClaims.TYPE)) {
                headerClaims.put(PublicClaims.TYPE, "JWT");
            }
            String signingKeyId = algorithm.getSigningKeyId();
            if (signingKeyId != null) {
                withKeyId(signingKeyId);
            }
            // 先调用创建jwtCreator对象方法,在调用sign生成token
            return new JWTCreator(algorithm, headerClaims, payloadClaims).sign();
        }

    // 创建jwtCreator对象方法
    private JWTCreator(Algorithm algorithm, Map<String, Object> headerClaims, Map<String, Object> payloadClaims) throws JWTCreationException {
        this.algorithm = algorithm;
        try {
            // map转String 默认值:"{"typ":"JWT","alg":"HS256"}"
            headerJson = mapper.writeValueAsString(headerClaims);
            payloadJson = mapper.writeValueAsString(new ClaimsHolder(payloadClaims));
        } catch (JsonProcessingException e) {
            throw new JWTCreationException("Some of the Claims couldn't be converted to a valid JSON format.", e);
        }
    }

    
    // 签名获取token
    private String sign() throws SignatureGenerationException {
        String header = Base64.getUrlEncoder().withoutPadding().encodeToString(headerJson.getBytes(StandardCharsets.UTF_8));
        String payload = Base64.getUrlEncoder().withoutPadding().encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8));

        byte[] signatureBytes = algorithm.sign(header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8));
        String signature = Base64.getUrlEncoder().withoutPadding().encodeToString((signatureBytes));

        // 格式 header.payload.signature
        return String.format("%s.%s.%s", header, payload, signature);
    }

}

调用JWTCreator生成token工具类:

public class JWTUtils {

    
    private static final String PAYLOAD_ISSUER = "xiaozhen";
    private static final String PAYLOAD_COMPANY_ID = "company_id";
    private static final String PAYLOAD_APP_ID = "app_id";

    /**
     * 生成token timestamp代表sign的生成时间
     * token格式:由三部分组成header.payload.signature,中间用点分割
     * 案例:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ5ZGwiLCJjb21wYW55X3VpZCI6IjEyIiwiZXhwIjoxNjY5MDI0ODYxLCJhcHBfaWQiOiJzZGYiLCJpYXQiOjE2NjkwMTc2NjF9.cy3KN5bOMzzYh4pcTR0pbfFeMD7cl5jqeFXYgxz4JeY
     */
    public static TokenDTO generateToken(String appId, String sign, Long timestamp) {

        // 查业务数据库 根据appId查询appSecret等秘钥信息 (秘钥不能暴露否则校验就没用了)
        String appSecret = "数据库存储的秘钥";

        private static final Algorithm TOKEN_ALGORITHM = Algorithm.HMAC256(appSecret);
        
        
        // 检查签名
        String assertSign = DigestUtils.md5Hex(appId + appSecret);
        if (!sign.equals(assertSign)) {
            throw new XiaoZhenException("code", "签名错误");
        }

        // 是否过期
        boolean expired = (System.currentTimeMillis() - timestamp) > 7200000;
        if (expired) {
            throw new XiaoZhenException("code", "签名过期");
        }


        // 设置Token过期时间
        long issuedAt = System.currentTimeMillis();
        long expiresAt = issuedAt + 7200000;

        // 生成Token, with开头的方法均是在设置payload
        String token = JWT.create()
                .withIssuer(PAYLOAD_ISSUER)
                .withClaim(PAYLOAD_COMPANY_ID, "主体id")
                .withClaim(PAYLOAD_APP_ID, "appId") 
                .withIssuedAt(new Date(issuedAt))
                .withExpiresAt(new Date(expiresAt))
                .sign(TOKEN_ALGORITHM);
        log.info("generateToken token: {}", token);
        return new TokenDTO()
                .setAccessToken(token)
                .setExpiresAt(expiresAt);
    }


   /**
   * 返回sign,timestamp生成时间
   */
   public Map<String,String> getSign (String appId) {
        // 查业务数据库 根据appId查询appSecret等秘钥信息 (秘钥不能暴露否则校验就没用了)
        String appSecret = "数据库存储的秘钥";
        // 根据appSecret,appId 生产sign
        map.put("sign",sign);
		map.put(Constants.TIMESTAMP,String.valueOf(timeStamp));
        return map;
       
   }

}

拦截器验证token

@Component
public class OpenInterceptor extends HandlerInterceptorAdapter {

    private static final Logger log = LoggerFactory.getLogger(OpenInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
        // 获取token
        String token = req.getHeader("Authorization");
        if (StringUtils.isEmpty(token)) {
            resp.setStatus(HttpStatus.UNAUTHORIZED.value());
            return false;
        }

        Boolean result = false;
        try {
            result = bgTokenFacade.parseToken(token);
            // jwt验证器
            JWTVerifier verifier = JWT.require(TOKEN_ALGORITHM)
                .withIssuer(TOKEN_ISSUER)
                .build();
            
            /**
             * 验证token
             * 源码原理:把token按hearder.payload.signature格式拆分,
             *     重新用header和payload生成签名, 和入参中token的signature部分比较,
             *     如果一致说明请求合法,否则为非法请求
             *    (这样不论header、payload、signature哪个被篡改都不会验证通过)
             */
            verifier.verify(accessToken);
        
        } catch (Exception e) {
            log.error("解析token异常", e);
            resp.setStatus(HttpStatus.UNAUTHORIZED.value());
            return false;
        }

        return result;
    }

}

生成token的api层:

@RestController
@RequestMapping("/v1/open/oauth")
@Slf4j
public class BgAuthController {


   @RequestMapping(value = "/getSign", method = RequestMethod.GET)
	@ApiOperation(value = "获取验签")
	public Map<String, String> getSign(@RequestParam(value = "appKey",required = true)String appId) {
         return JWTUtils.getSign(appId);
        
    }


    /**
     * 获取 accessToken
     */
    @ApiOperation(value = "获取 accessToken")
    @PostMapping("/token")
    public TokenDTO token(@RequestBody @Valid TokenReq req) {
        return JWTUtils.generateToken(req.getAppId(),req.getSign, req.getTimestamp());
    }
}


生成的token案例:

格式 header.payload.signature

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ5ZGwiLCJjb21wYW55X3VpZCI6IjEyIiwiZXhwIjoxNjY5MDI0ODYxLCJhcHBfaWQiOiJzZGYiLCJpYXQiOjE2NjkwMTc2NjF9.cy3KN5bOMzzYh4pcTR0pbfFeMD7cl5jqeFXYgxz4JeY

springBoot设置拦截器配置需要token验证的api

@Configuration
public class CustomWebMvcConfigurer implements WebMvcConfigurer {

    @Override
	public void addInterceptors(InterceptorRegistry registry) {
		WebMvcConfigurer.super.addInterceptors(registry);
		//token验证api拦截器
		registry.addInterceptor(openInterceptor)
				.addPathPatterns("/v1/open/**") // 此contorller业务api均拦截
				.excludePathPatterns("/v1/open/oauth/**"); //生成token的api不需拦截
	}
}


// 自定义实现拦截器,拦截器中校验token

@Component
public class OpenInterceptor extends HandlerInterceptorAdapter {


    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
        
        // 获取token
        String token = req.getHeader("Authorization");
        // 解析token
        Boolean result = JWTUtils.verifierToken();

        // 可定义ThreadLocal 把请求头中的用户信息保存起来,供本线程全局使用
        // todo private static final ThreadLocal<BO> THREAD_LOCAL = new ThreadLocal<>();
        return result;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值