springboot 集成 jwt实现api的token认证机制

                                   

JWT原理


JWT是Auth0提出的通过对JSON进行加密签名来实现授权验证的方案,编码之后的JWT看起来是这样的一串字符:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

由 . 分为三段,通过解码可以得到

1. 头部(Header)

// 包括类别(typ)、加密算法(alg);

{
  "alg": "HS256",
  "typ": "JWT"
}

jwt的头部包含两部分信息:

声明类型,这里是jwt

声明加密的算法 通常直接使用 HMAC SHA256

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分。

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9


2. 载荷(payload)

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

标准中注册的声明
公共的声明
私有的声明
标准中注册的声明 (建议但不强制使用) :

iss: 该JWT的签发者,一般是服务器,是否使用是可选的;

iat(issued at): 在什么时候签发的(UNIX时间),是否使用是可选的;

exp(expires): 什么时候过期,这里是一个Unix时间戳,是否使用是可选的;

aud: 接收该JWT的一方,是否使用是可选的;

sub: 该JWT所面向的用户,userid,是否使用是可选的;

其他还有:

nbf (Not Before):如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟;,是否使用是可选的;

jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

// 包括需要传递的用户信息;
{ "iss": "Online JWT Builder", 
  "iat": 1416797419, 
  "exp": 1448333419, 
  "aud": "www.gusibi.com", 
  "sub": "uid", 
  "nickname": "goodspeed", 
  "username": "goodspeed", 
  "scopes": [ "admin", "user" ] 
}

将上面的JSON对象进行base64编码可以得到下面的字符串。这个字符串我们将它称作JWT的Payload(载荷)。

eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0MTY3OTc0MTksImV4cCI6MTQ0ODMzMzQxOSwiYXVkIjoid3d3Lmd1c2liaS5jb20iLCJzdWIiOiIwMTIzNDU2Nzg5Iiwibmlja25hbWUiOiJnb29kc3BlZWQiLCJ1c2VybmFtZSI6Imdvb2RzcGVlZCIsInNjb3BlcyI6WyJhZG1pbiIsInVzZXIiXX0
 

信息会暴露:由于这里用的是可逆的base64 编码,所以第二部分的数据实际上是明文的。我们应该避免在这里存放不能公开的隐私信息。


3. 签名(signature)

// 根据alg算法与私有秘钥进行加密得到的签名字串;
// 这一段是最重要的敏感信息,只能在服务端解密;
HMACSHA256(  
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    SECREATE_KEY
)


jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

header (base64后的)
payload (base64后的)
secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); // pq5IDv-yaktw6XEa5GEv07SzS9ehe6AcVSdTj0Ini4o

 将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0MTY3OTc0MTksImV4cCI6MTQ0ODMzMzQxOSwiYXVkIjoid3d3Lmd1c2liaS5jb20iLCJzdWIiOiIwMTIzNDU2Nzg5Iiwibmlja25hbWUiOiJnb29kc3BlZWQiLCJ1c2VybmFtZSI6Imdvb2RzcGVlZCIsInNjb3BlcyI6WyJhZG1pbiIsInVzZXIiXX0.pq5IDv-yaktw6XEa5GEv07SzS9ehe6AcVSdTj0Ini4o


签名的目的:签名实际上是对头部以及载荷内容进行签名。所以,如果有人对头部以及载荷的内容解码之后进行修改,再进行编码的话,那么新的头部和载荷的签名和之前的签名就将是不一样的。而且,如果不知道服务器加密的密钥的话,得出来的签名也一定会是不一样的。
这样就能保证token不会被篡改。

具体的嵌入到springboot代码的过程:


1:jar包依赖

<dependency>

    <groupId>com.auth0</groupId>

    <artifactId>java-jwt</artifactId>

    <version>3.1.0</version>

</dependency>

<dependency>

    <groupId>io.jsonwebtoken</groupId>

    <artifactId>jjwt</artifactId>

    <version>0.6.0</version>

</dependency>

2:jwt加密和解密的工具类

package com.alienlab.news.utils;

import com.alibaba.fastjson.JSONObject;

import io.jsonwebtoken.Claims;

import io.jsonwebtoken.JwtBuilder;

import io.jsonwebtoken.Jwts;

import io.jsonwebtoken.SignatureAlgorithm;

import javax.crypto.spec.SecretKeySpec;

import javax.xml.bind.DatatypeConverter;

import java.security.Key;

import java.util.Date;

/**

 * The type Jwt utils.

 */

public class JwtUtils {

    public static Claims parseJWT(String jsonWebToken, String base64Security) {

        try {

            Claims claims = Jwts.parser()

                    .setSigningKey(DatatypeConverter.parseBase64Binary(base64Security))

                    .parseClaimsJws(jsonWebToken).getBody();

            return claims;

        catch (Exception ex) {

            return null;

        }

    }

//前三个参数为自己用户token的一些信息比如id,权限,名称等。不要将隐私信息放入(大家都可以获取到)

    public static String createJWT(String name, String userId, String role,

                                   String audience, String issuer, long TTLMillis, String base64Security) {

        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        long nowMillis = System.currentTimeMillis();

        Date now = new Date(nowMillis);

//生成签名密钥 就是一个base64加密后的字符串?

        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary (base64Security);

        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm. getJcaName());

        JSONObject jsonObject = new JSONObject();

        jsonObject.put("userName", name);

        jsonObject.put("userLoginName", userId);

//添加构成JWT的参数

        JwtBuilder builder = Jwts.builder().setHeaderParam("typ""JWT")

                .setIssuedAt(now) //创建时间

                .setSubject(jsonObject.toString()) //主题,也差不多是个人的一些信息

                .setIssuer(issuer) //发送谁

                .setAudience(audience) //个人签名

                .signWith(signatureAlgorithm, signingKey); //估计是第三段密钥

//添加Token过期时间

        if (TTLMillis >= 0) {

//过期时间

            long expMillis = nowMillis + TTLMillis;

//现在是什么时间

            Date exp = new Date(expMillis);

//系统时间之前的token都是不可以被承认的

            builder.setExpiration(exp).setNotBefore(now);

        }

//生成JWT

        return builder.compact();

    }

}

3:使用的条件(该接口允许跨域 cors来配置跨域)


3.1:cors配置允许跨域

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.cors.CorsConfiguration;

import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import org.springframework.web.filter.CorsFilter;

import org.springframework.web.servlet.config.annotation.CorsRegistry;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.ArrayList;

import java.util.List;

/**

 * The type Cors config.

 */

@Configuration

public class CorsConfig extends WebMvcConfigurerAdapter {

    @Override

    public void addCorsMappings(CorsRegistry registry) {

        registry.addMapping("/**").allowedOrigins("*").allowCredentials(true)

                .allowedMethods("GET""POST""DELETE""PUT").maxAge(3600);

    }

    private CorsConfiguration buildConfig() {

        CorsConfiguration corsConfiguration = new CorsConfiguration();

        List<String> list = new ArrayList<>();

        list.add("*");

        corsConfiguration.setAllowedOrigins(list);

        corsConfiguration.addAllowedOrigin("*"); // 1

        corsConfiguration.addAllowedHeader("*"); // 2

        corsConfiguration.addAllowedMethod("*"); // 3

        return corsConfiguration;

    }

    @Bean

    public CorsFilter corsFilter() {

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        source.registerCorsConfiguration("/**", buildConfig()); // 4

        return new CorsFilter(source);

    }

}


3.2:拦截器拦截方法获取token

3.2.1:拦截器配置

import org.springframework.web.servlet.HandlerInterceptor;

import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

/**

 * The type Api interceptor.

 */

public class ApiInterceptor implements HandlerInterceptor {

    @Override

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        System.out.println("拦截了");

        return true;

    }

    @Override

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        System.out.println("拦截了");

    }

    @Override

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        System.out.println("拦截了");

    }

}

3.2.2: 拦截器管理工具

import org.springframework.context.annotation.Configuration;

import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**

 * The type My web app configurer.

 */

@Configuration

public class MyWebAppConfigurer extends WebMvcConfigurerAdapter {

    @Override

    public void addInterceptors(InterceptorRegistry registry) {

//多个拦截器组成一个拦截器链

// addPathPatterns用于添加拦截规则

// excludePathPatterns用户排除拦截

        registry.addInterceptor(new ApiInterceptor()).addPathPatterns("/**"); //对来自/user/** 这个链接来的请求进行拦截

        super.addInterceptors(registry);

    }

}



4:token的发送与获取

ajax为例子:

beforeSend:function(request) {
// token,为登陆时获取到
request.setRequestHeader("token",token);
},


后台获取:

request.getHeader("token");


5:token验证机制


5.1:通过token解密是否成功可以判断token是否正确或者是否过期
5.2:解密完成,可以对比用户属性或者用户的固定token(缓存中或者放入数据库)

源码下载:https://github.com/fleapx/spring-security-jwt-guide

精彩代码视频及GIF动图讲解,请百度搜索:啄木鸟debugIT森林

发布了28 篇原创文章 · 获赞 3 · 访问量 1958

猜你喜欢

转载自blog.csdn.net/as4589sd/article/details/103288888