Springboot2整合JWT的实现

jwt的作用

对于一个技术而言,我们不能为了用它而用它,而且应该从业务出发,我为了解决什么问题才用哪种技术。
首先,jwt全称是Json Web Token,在讨论基于Token的身份认证是如何工作的以及它的好处之前,我们先来看一下以前我们是怎么做的:

由于HTTP协议是无状态的,也就是说,如果我们已经认证了一个用户,那么他下一次请求的时候,服务器不知道我是谁,我们必须再次认证。
传统的做法是将已经认证过的用户信息存储在服务器上,比如Session。用户下次请求的时候带着Session ID,然后服务器以此检查用户是否认证过。

这种基于服务器的身份认证方式存在一些问题:

  • Sessions : 每次用户认证通过以后,服务器需要创建一条记录保存用户信息,通常是在内存中,随着认证通过的用户越来越多,服务器的在这里的开销就会越来越大。

  • Scalability : 由于Session是在内存中的,这就带来一些扩展性的问题。

  • CORS : 当我们想要扩展我们的应用,让我们的数据被多个移动设备使用时,我们必须考虑跨资源共享问题。当使用AJAX调用从另一个域名下获取资源时,我们可能会遇到禁止请求的问题。

  • CSRF : 用户很容易受到CSRF攻击。

JWT与Session的差异

相同点是,它们都是存储用户信息;然而,Session是在服务器端的,而JWT是在客户端的。

Session方式存储用户信息的最大问题在于要占用大量服务器内存,增加服务器的开销。

而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。

Session的状态是存储在服务器端,客户端只有session id;而jwt的token的状态是存储在客户端。

同时jwt也很好的解决了跨域的问题。

如果你正在构建从服务器到服务器或客户端到服务器(如:移动应用 APP 或单页面应用)的 API 服务,那么使用 JWT 是非常明智的。比如:
你的客户端需要通过 API 进行身份验证,并返回 JWT
然后,客户端使用返回的 JWT 经过身份验证去请求其它的 API 服务
这些其它 API 服务通过客户端的 JWT 验证客户端是可信的,并且可以执行某些操作无需再次验证

对于这类 API 服务,JWT 非常适合,因为客户端需要频繁进行请求,并且权限是可控的,通常认证数据以无状态方式持久存在,不需要过多依赖用户信息。

如果你正在构建的应用类似单点登录或 OpenID Connect 认证,JWT 同样十分适合,就是实现一种通过第三方验证用户的方法。

jwt的原理

研究原理当然是推荐先看一下官网文档:https://jwt.io/introduction/
文档上写的也很清楚,我就简单讲讲,不细说了
jwt分为3部分:
Header
Payload
Signature
完整的token样式如:
xxxxx.yyyyy.zzzzz

header示例

示例:
{
“alg”: “HS256”,
“typ”: “JWT”
}
然后使用Base64Url 编码

payload示例

{
“sub”: “1234567890”,
“name”: “John Doe”,
“admin”: true
}
然后使用Base64Url 编码

Signature示例

HMACSHA256(
base64UrlEncode(header) + “.” +
base64UrlEncode(payload),
secret)
然后使用Base64Url 编码

token

最后的token就是 前3部分使用 . 连接起来

jwt的java实现

在这里插入图片描述
官网的libraries https://jwt.io/#libraries 有介绍实现这个的规范的库,我们使用的就是图中的jedis
访问作者的github:https://github.com/jwtk/jjwt ,发现文档写的很详细,大家可以多阅读阅读

但是光使用jwt没有解决注销、修改密码、token自动续签这3个场景。使用redis可以解决,关于实现方式,我们后面再讲。

整合springboot

读完jjwt的文档后就可以上手整合springboot了

导入依赖

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

编写工具类

JwtUtil.java

/**
 * @author hy
 * @date 2019-07-24
 */
@Component
public class JwtUtil {

    Logger logger = LoggerFactory.getLogger(JwtUtil.class);

    private static JwtUtil jwtUtil;

    @Autowired
    AppConfig appConfig;

    private static SecretKey key;

    @PostConstruct
    public void init(){
        jwtUtil = this;
        logger.info("----------------初始化 JWT 成功");
    }

    public static SecretKey getSecretKey(){
        if (key == null){
            key = Keys.hmacShaKeyFor(jwtUtil.appConfig.getSecretKey().getBytes());
        }
        return key;
    }

    /**
     * 获得token
     * @param subject 需要携带的json信息
     * @return token
     */
    public synchronized static String getToken(String subject){
        SecretKey key = getSecretKey();
        String token = Jwts.builder()
                .setIssuer(jwtUtil.appConfig.getAppName())
                .setAudience("client")
                .setSubject(subject)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 1000))
                .signWith(key)
                .setId(String.valueOf(UUID.randomUUID()))
                .compact();
        return token;
    }

    /**
     * 获得token
     * @param iss 发起方
     * @param aud 接收方
     * @param exp 过期时间,单位分钟
     * @param subject 需要携带的json信息
     * @return token
     */
    public static String getToken(String iss,String aud,Integer exp,String subject){
        SecretKey key = getSecretKey();
        String token = Jwts.builder()
                .setIssuer(iss)
                .setAudience(aud)
                .setSubject(subject)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + exp * 60 * 1000))
                .signWith(key)
                .setId(String.valueOf(UUID.randomUUID()))
                .compact();
        return token;
    }

    public static Object parseToken(String token){
        String keySecret = jwtUtil.appConfig.getSecretKey();
        SecretKey key = Keys.hmacShaKeyFor(keySecret.getBytes());
        try {
            Claims body = Jwts.parser()
                    .setSigningKey(key)
                    .parseClaimsJws(token)
                    .getBody();
            return body;
        }catch (ExpiredJwtException expiredJwtException){
            return ResultCode.TIME_OUT;
        }
    }

}

这里面我使用了AppConfig 这个配置类,也可以不用,我这么做只是方便在配置文件中修改 secretKey
上面只是实现了一个工具类,我们还需要一个filter才能让jwt更好 的发挥作用。

JwtFilter.java


@Configuration
public class JwtFilter extends OncePerRequestFilter {

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

        String servletPath = request.getServletPath();
        if (StringUtils.isEmpty(servletPath)){
            return;
        }
        if (servletPath.startsWith("/test") || servletPath.startsWith("/druid") || servletPath.startsWith("/user/hello") || servletPath.startsWith("/user/login") ){
            //放行
            filterChain.doFilter(request, response);
            return;
        }
        String authorization = request.getHeader("Authorization");
        if (StringUtils.isEmpty(authorization)){
            fail(response,ResultCode.ERROR_TOKEN);
            return;
        }
        String prefix = "Bearer ";
        if (!authorization.startsWith(prefix)){
            fail(response,ResultCode.ERROR_TOKEN);
            return;
        }
        String token = authorization.substring(prefix.length());
        if (StringUtils.isEmpty(token)){
            fail(response,ResultCode.ERROR_TOKEN);
            return;
        }

        Object r = JwtUtil.parseToken(token);
        if (r instanceof Integer){
            fail(response, (Integer) r);
        }else if (r instanceof Claims){
            //放行
            filterChain.doFilter(request, response);
        }
    }

    private void fail(HttpServletResponse response,Integer resultCode) throws IOException {
        // 失败
        response.setContentType("application/json; charset=utf-8");
        PrintWriter writer = response.getWriter();
        JsonResult result = new JsonResult(resultCode,"token验证失败");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("result", result);
        writer.print(jsonObject);
        writer.close();
    }
}

JwtFilter能够统一验证处理token

使用示例:

    @PostMapping(value = "/login")
    public String login(
            @RequestBody JSONObject in
            ){
        String username = in.getString("username");
        String password = in.getString("password");
        //验证账户
        String token = JwtUtil.getToken(in.toJSONString());
        redisUtil.set("token", token);
        return redisUtil.get("token");
    }

验证成功返回了token后,前端将token存进header的Authorization里,并且每次请求携带这个Authorization就行了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值