JWT实现Token认证(token续命)

1.什么是jwt

双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库。

JWT架构图
JWT实现Token认证(token续命)_第1张图片

2.Jwt在java项目中的简单使用

2.1、引入maven依赖

<!--引入JWT依赖,由于是基于Java,所以需要的是java-jwt-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>

2.2、创建两个注解,拦截器通过注释区分是否进行权限拦截

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

/**
 * 校验token
 * @author 
 * @date 2019/12/9
 */
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckToken {
    boolean required() default true;
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 跳过token
 * @author 
 * @date 2019/12/9
 */
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreCheckToken {
    boolean required() default true;
}

2.3、编写CommonConstants

public class CommonConstants {

	
	public static String BEARER="Bearer";//token前缀
	public static String USER_ACCOUNT="userAccount";//用户账号
	public static String USER_ID="userId";//用户Id
	public static String PASSWORD="password";//密码
	public static String SECRET_KEY="asd441qw1asdzx1asdas1d3aweqsad";//秘钥,当然也可以把用户的密码作秘钥
	public static String IGNORE_LOGIN="login";//登录方法



	
}

2.4、编写JwtUtil工具类(生成token,解析token,校验token)

import com.admin.manager.common.constant.CommonConstants;
import com.admin.manager.storeService.config.RedisBean;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author 
 * @date 2019/12/9
 */
public class JwtUtil {


    /**
     * 用户登录成功后生成Jwt
     * 使用Hs256算法  私匙使用用户密码
     *
     * @param ttlMillis jwt过期时间
     * @param params    登录成功的userId,userAccount,password
     * @return
     */
    public String createJWT(long ttlMillis, Map<String, String> params) {
        //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        //生成JWT的时间
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        //创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
        Map<String, Object> claims = new HashMap<String, Object>();
        claims.put("userId", params.get("userId"));
        claims.put("userAccount", params.get("userAccount"));
        claims.put("password", params.get("password"));

        //生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
        byte[] secretKey = Base64.getEncoder().encode(CommonConstants.SECRET_KEY.getBytes());

        //生成签发人
        String subject = params.get("userAccount");


        //下面就是在为payload添加各种标准声明和私有声明了
        //这里其实就是new一个JwtBuilder,设置jwt的body
        JwtBuilder builder = Jwts.builder()
                //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
                .setId(UUID.randomUUID().toString())
                //iat: jwt的签发时间
                .setIssuedAt(now)
                //代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
                .setSubject(subject)
                //设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, secretKey);

        String token = builder.compact();
        //token存放redis中,并设置失效时间
        RedisTemplate redisTemplate = RedisBean.redis;
        redisTemplate.opsForValue().set("user:" + params.get("userId") + ":token", token, 6, TimeUnit.HOURS);
        return token;
    }


    /**
     * Token的解密
     *
     * @param token  加密后的token
     * @param params 用户的对象
     * @return
     */
    public Claims parseJWT(String token, Map<String, String> params) {
        //签名秘钥,和生成的签名的秘钥一模一样
        String key = params.get("password");

        //得到DefaultJwtParser
        Claims claims = Jwts.parser()
                //设置签名的秘钥
                .setSigningKey(key)
                //设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }


    /**
     * 验证token思路:(我这里是用同样的私钥操作的,因此我只判断了token的有效期! )
     *
     * 完整的参考下:
     * 1.将传过来的token解析,解析出来用户Id,用户账号,用户密码。
     * 2.比较解析出来的用户密码是否跟传过来的密码一致
     * 3.如果不一致,提示:token错误
     * 4.否则,验证token是否失效
     * 5.如果失效,提示:token已失效
     * 6.否则,进行下一步业务操作。
     */
    public Boolean isVerify(String token) {
        //签名秘钥,和生成的签名的秘钥一模一样
        byte[] secretKey = Base64.getEncoder().encode(CommonConstants.SECRET_KEY.getBytes());
        //得到DefaultJwtParser
        Claims claims = Jwts.parser()
                //设置签名的秘钥
                .setSigningKey(secretKey)
                //设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        String userId = (String) claims.get(CommonConstants.USER_ID);//用户Id
        String userAccount = (String) claims.get(CommonConstants.USER_ACCOUNT);//用户账号
        String password = (String) claims.get(CommonConstants.PASSWORD);//用户密码

        String key = "user:" + userId + ":token";
        RedisTemplate redisTemplate = RedisBean.redis;
        //获取token的有效期
        Long expire = redisTemplate.getExpire(key);
        //如果返回-1,说明key已经过了有效期
        if (expire == -1) {
            return false;
        } else {
            //如果失效时间小于30分钟,将重新给key添加失效时间  (简称:续命)
            if (expire <= 120) {
                redisTemplate.opsForValue().set(key, token, 6, TimeUnit.HOURS);
            }
            return true;
        }

    }


}

2.5、编写拦截器拦截请求进行权限验证

import com.admin.manager.common.annotation.CheckToken;
import com.admin.manager.common.annotation.IgnoreCheckToken;
import com.admin.manager.common.constant.CommonConstants;
import com.admin.manager.common.exception.BusinessException;
import com.admin.manager.common.msg.Msg;
import com.admin.manager.common.pojo.BaseResponse;
import com.admin.manager.storeService.util.JwtUtil;
import com.alibaba.fastjson.JSON;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
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.io.IOException;
import java.lang.reflect.Method;

/**
 * 定义拦截器
 *
 * @author 
 * @date 2019/12/10
 */
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        //如果是登录,跳过校验
        if (CommonConstants.IGNORE_LOGIN.equals(method.getName())) {
            return true;
        }

        CheckToken checkToken = handlerMethod.getMethod().getAnnotation(CheckToken.class);
        IgnoreCheckToken ignoreCheckToken = handlerMethod.getMethod().getAnnotation(IgnoreCheckToken.class);

        //校验token
        if (checkToken != null && ignoreCheckToken == null) {
            String token = request.getHeader("token");
//            System.out.println("校验token");
            if (token == null) {
                response = getMsg(response, Msg.TOKEN_NOT_EMPTY);
                return false;
            }
            Boolean verify = new JwtUtil().isVerify(token);
            if (verify) {
                return true;
            } else {
                response = getMsg(response, Msg.TOKEN_EXPIRED);
                return false;
            }
        }
        //跳过token
        else if (ignoreCheckToken != null && checkToken == null) {
            return true;
        }
        return true;
    }

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

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

    private HttpServletResponse getMsg(HttpServletResponse response, String message) throws IOException {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("UTF-8");
        response.getOutputStream().println(JSON.toJSONString(new BaseResponse(500, message)));
        return response;
    }
}

2.6、配置拦截器(ps:我使用的是springboot,大家使用ssm配置拦截器的方式不一样)

import com.admin.manager.storeService.Interceptor.AuthenticationInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 *
 * 配置拦截器
 * @author 
 * @date 2019/12/10
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private AuthenticationInterceptor authenticationInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this.authenticationInterceptor)
                .addPathPatterns("/**");    // 拦截所有请求
    }


}

2.7、在Controller中的实际应用

import com.admin.manager.common.annotation.CheckToken;
import com.admin.manager.common.annotation.IgnoreCheckToken;
import com.admin.manager.common.exception.BusinessException;
import com.admin.manager.common.pojo.ObjectResponse;
import com.admin.manager.common.rest.BaseController;
import com.admin.manager.storeService.biz.SysUserBiz;
import com.admin.manager.storeService.config.RedisBean;
import com.admin.manager.storeService.entity.SysUser;
import com.admin.manager.storeService.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.HashMap;
import java.util.Map;


@Controller
@RequestMapping("api/admin/user")
public class SysUserController extends BaseController<SysUserBiz, SysUser> {

    @Autowired
    private SysUserBiz userBiz;
    



    /**
     * 登录
     *
     * @param userAccount
     * @param password
     * @return
     */
    @RequestMapping(value = "login")
    public ObjectResponse login(@RequestParam String userAccount, @RequestParam String password) {
        RedisTemplate redis = RedisBean.redis;
        if (userAccount == null) {
            throw new BusinessException("用户名不能为空");
        }
        if (password == null) {
            throw new BusinessException("密码不能为空");
        }
        SysUser login = userBiz.login(userAccount, password);
        Map<String, String> params = new HashMap();
        params.put("userId", login.getId());
        params.put("userAccount", login.getUserAccount());
        params.put("password", login.getPassword());
        String jwt = new JwtUtil().createJWT(10000, params);
        if (jwt == null) {
            throw new BusinessException("token生成失败");
        }
        params.put("token", jwt);
        return new ObjectResponse(params);
    }


    //查看个人信息
    @CheckToken
    @GetMapping("/getMessage")
    public String getMessage() {
        return "你已通过验证";
    }
	
	//跳过token
    @IgnoreCheckToken
    @GetMapping("/ignore")
    public String ignore(){
        return "你已经跳过验证";
    }

}

3.访问

3.1、登录
JWT实现Token认证(token续命)_第2张图片
3.2、校验token
JWT实现Token认证(token续命)_第3张图片
3.3、跳过token
JWT实现Token认证(token续命)_第4张图片

你可能感兴趣的:(spring,java,jwt)