【TGAM-springboot入门练手项目】采用jwt实现token认证(一)

TGAM-springboot入门练手项目

此项目是个人参与工作室的一个项目,分为硬件、IOS、WEB、后端、数据分析等几大部分,具有实际商用价值。而我虽然只是在其中打杂,写的后端也不够好,但是我会努力地将这系列文章写清楚、详细,若有什么疑问,欢迎QQ:674619459和我交流。另外你的点赞、收藏便是对我最大的鼓励!!
GITHUB:https://github.com/lyf712/TGAM-SpringBoot-Vue-Demo>
简单介绍:https://blog.csdn.net/qq_44654974/article/details/112990344

开发篇
-----基础篇
篇1 Springboot学习入门之基本框架搭建和相应准备工作
篇2 Springboot整合mybatis实现简单登录功能
篇3 Springboot调用阿里云SDK短信服务
篇4 Springboot+mybatis实现简单地增删改查-用户管理
篇5 Springboot结合Redis实现数据分控制位上传
篇6 Springboot 定时任务做超时检验
篇7 采用jwt+shiro实现权限管理
篇8 采用对称和非对称算法进行数据加密传输
-----提升篇
篇1 websocket进行长连接传输数据
篇2 NGINX+tomcat集群
篇3 初识多线程并发编程
篇4 初识netty

部署篇

篇1 springboot部署阿里云Tomcat
篇2 springboot+Vue部署阿里云Tomcat并使用NGINX反向代理
篇3 springboot+Vue部署阿里云Tomcat并使用NGINX反向代理



一、Token是什么?

Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。

1.为什么需要token?

在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。刚开始写springboot时,可能就只会简单地去实现登录和增删改查、但是试想一下,如果没有权限去管理这些API的使用,那么登录也没有什么意义,直接访问即可,又有人说此时可以采用前端或者后端拦截器啊?但在现在的前后分离情况下,别人知道了你的API地址,然后直接向你的数据暴力插数据,那么你是不是就凉凉了,此时你可能又会考虑后端控制层去判断,这样又回到了最初的背景。
因此,Token的主要目的为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。

2.大致使用流程

(1)客户端使用用户名跟密码请求登录
(2)服务端收到请求,去验证用户名与密码
(3)验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
(4)客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
(5)客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
(6)APP登录的时候发送加密的用户名和密码到服务器,服务器验证用户名和密码,如果成功,以某种方式比如随机生成32位的字符串作为token,存储到服务器中,并返回token到APP,以后APP请求时,
(7)凡是需要验证的地方都要带上该token,然后服务器端验证token,成功返回所需要的结果,失败返回错误信息,让他重新登录。其中服务器上token设置一个有效期,每次APP请求的时候都验证token和有效期。

3.JWT是什么

JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息作为JSON对象。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对对JWT进行签名。

header头文件信息、 payload 荷载、signature加密验证

(1)用户请求时携带此token(分为三部分,header密文,payload密文,签名)到服务端,服务端解析第一部分(header密文),用Base64解密,可以知道用了什么算法进行签名,此处解析发现是HS256。

(2)服务端使用原来的秘钥与密文(header密文+"."+payload密文)同样进行HS256运算,然后用生成的签名与token携带的签名进行对比,若一致说明token合法,不一致说明原文被修改。

(3)判断是否过期,客户端通过用Base64解密第二部分(payload密文),可以知道荷载中授权时间,以及有效期。通过这个与当前时间对比发现token是否过期。
官网:https://jwt.io/introduction
在这里插入图片描述

二、JWT使用步骤

1.引入依赖

pom.xml加入

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

2.编写工具类生成TOKEN

package com.lyf.utils.authority;

import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.junit.Test;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtUtil {

    //密钥
    public static final String SECRET ="";//自己任意取一个字符串
    //过期时间:秒
    public static final int EXPIRE = 500;// 设置token时效

    /**
     * 生成Token
     * @param password
     * @param userName
     * @return
     * @throws Exception
     */

    public static String createToken(String userName, String password) throws Exception {

        // 现在时间
        Calendar nowTime = Calendar.getInstance();
        nowTime.add(Calendar.SECOND, EXPIRE);
        // 希望的日期,也就失效的日期
        Date expireDate = nowTime.getTime();
        System.out.println(nowTime.getTime());
        Map<String, Object> map = new HashMap<>();

        // header
        map.put("alg", "HS256");
        map.put("typ", "JWT");

        String token = JWT.create()
                .withHeader(map)//头
                .withClaim("userName", userName)
                .withClaim("password", password)
                .withSubject("TGAM验证")//
                .withIssuedAt(new Date())//签名时间
                .withExpiresAt(expireDate)//过期时间
                .sign(Algorithm.HMAC256(SECRET));//签名
        return token;
    }

    /**
     * 验证Token
     * @param token
     * @return
     * @throws Exception
     */
    //
    public static  Map<String, Claim> verifyToken(String token)throws Exception{
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
        DecodedJWT jwt = null;
        try {
            jwt = verifier.verify(token);
        }catch (Exception e){
            //return "凭证已过期,请重新登录";
            throw new RuntimeException("凭证已过期,请重新登录");
        }
        return jwt.getClaims();
       // return jwt.getToken();
    }

    /**
     * 解析Token
     * @param token
     * @return
     */

    public static Map<String, Claim> parseToken(String token){
        DecodedJWT decodedJWT = JWT.decode(token);
        return decodedJWT.getClaims();
    }
    @Test
    public void test() throws Exception {
//        String token = JwtUtil.createToken("3213","Tom22");
//        System.out.println("token:"+token);
//        System.out.println(JwtUtil.verifyToken(token));
        System.out.println(JwtUtil.parseToken("efyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJUR0FN6aqM6K-BIiwidXNlck5hbWUiOiJUb20yMiIsImV4cCI6MTYxMjAxMDgwMCwidXNlcklkIjoiMzIxMyIsImlhdCI6MTYxMjAxMDMwMH0.INDJ9KS53uTwbU25Gys2aK7UWYb0Xd2fpyp3h6DcvRU").get("userId").asString());
    }


}

3.配置注解、拦截器

https://www.cnblogs.com/shihaiming/p/9565835.html

(1)配置注解、并在相应的方法注解(在需要token验证(尤其是数据安地方)的地方注解LoginToken,不需要的地方(如登录接口)注解PassToken)


/**
 * @AUTHOR LYF
 * @DATE 2021-01-31
 * @DESC跳过验证的PassToken
 */
@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required()default true;
}

/**
 * @AUTHOR LYF
 * @DATE 2021-01-30
 * @DESC 需要登录才能进行的操作
 *
 */

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

(2)配置拦截器

注入拦截配置

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/**");// 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
    }
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
}

实现拦截处理器:在preHandle中进行token的认证(判断header中是否带token=>判断token是否合法、有无权限=>判断token是否失效)
相应的处理之后,进行异常抛出,在一个统一异常处理类中进行数据返回。

package com.lyf.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.Claim;
import com.google.gson.JsonObject;
import com.lyf.controller.ErrorController;
import com.lyf.dao.mapper.UserMapper;
import com.lyf.sercurity.LoginToken;
import com.lyf.sercurity.PassToken;
import com.lyf.utils.authority.JwtUtil;
import com.lyf.utils.result.Result;
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;
import java.util.Map;


public class AuthenticationInterceptor implements HandlerInterceptor {


    @Autowired
    UserMapper userMapper;
    @Autowired
    ErrorController errorController;

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

       // Enumeration<String> token =  request.getHeaders("token");
        System.out.println("path"+request.getContextPath());
        System.out.println("token"+request.getHeader("token"));


        String token = request.getHeader("token");//从header中获取token

        // handler??
        if(!(handler instanceof HandlerMethod)){
            return true;
        }

        HandlerMethod handlerMethod =(HandlerMethod) handler;

        Method method = handlerMethod.getMethod();

        // 检查是否有PassToken注解,有则跳过
        if(method.isAnnotationPresent(PassToken.class)){
            PassToken passToken = method.getAnnotation(PassToken.class);
            if(passToken.required()){
                return true;
            }
        }

        // 检查有无需要用户权限的注解
        if(method.isAnnotationPresent(LoginToken.class)){
            LoginToken loginToken = method.getAnnotation(LoginToken.class);

            // 执行认证
            if(loginToken.required()){

                if (token == null){

                   // return resp;
                  throw new RuntimeException("无token,请重新登录");
                }

                try{

                    Claim username = JwtUtil.parseToken(token).get("userName");
                    Claim password = JwtUtil.parseToken(token).get("password");

                }catch (JWTDecodeException e){

                    throw new RuntimeException("无权限");
                }

                try {
                    JwtUtil.verifyToken(token);
                }catch (Exception e){
                    throw new RuntimeException("凭证已过期,请重新登录");
                }
                return true;
            }
        }
        return true;
    }

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

    }

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

    }
}

异常处理类

    @org.springframework.web.bind.annotation.ExceptionHandler
    @ResponseBody
    public JSONObject RuntimeHandler(RuntimeException e){
        logger.error("token验证拦截",e.getMessage());
        JSONObject resp;
        resp = Result.ERROR();
        resp.put("detail",e.getMessage());
        return resp;
    }
 

更多关于拦截器的配置详细介绍,请参考:

https://blog.csdn.net/wilsonsong1024/article/details/80452072

https://www.cnblogs.com/shihaiming/p/9565835.html

4.测试使用

无token登录
在这里插入图片描述
登录获取token
在这里插入图片描述
带token登录(若失效则出现以下情况,未失效则可以正常获取相应接口数据,失效自己根据需要设置)在这里插入图片描述
在使用JWT时,一个让人纠结的问题就是“Token的时限多长才合适?”。对此,Stormpath的这篇文章给出了一个可供参考的建议:
(1)面对极度敏感的信息,如钱或银行数据,那就根本不要在本地存放Token,只存放在内存中。这样,随着App关闭,Token也就没有了。
此外,将Token的时限设置成较短的时间(如1小时)。
(2)对于那些虽然敏感但跟钱没关系,如健身App的进度,这个时间可以设置得长一点,如1个月。
(3)对于像游戏或社交类App,时间可以更长些,半年或1年。
<font size=3
并且,文章还建议增加一个“Token吊销”过程来应对Token被盗的情形,类似于当发现银行卡或电话卡丢失,用户主动挂失的过程。
关于“Token吊销”的实现,文章建议个方式如下:
在DB中记录用户对应的Token
实现一个Api Endpoint,负责将指定用户的Token从DB中删除


  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值