二、SpringBoot 整合 JWT

SpringBoot 整合 JWT

1 什么是 JWT

  JSON Web Token(JWT)是一个非常轻巧的规范,这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息,在 Java 世界中通过 JJWT 实现 JWT 创建和验证。

2 快速上手

2.1 pom.xml
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1<version>
</dependency>
2.2 测试类
import io.jsonwebtoken.*;

import java.util.Date;

/**
 * @author PkyShare
 * @date 2020/1/3 0003 16:06
 */
public class JwtTest {

    private final static String key = "share"; // 秘钥

    public static void main(String[] args) {
        String token = created();
        parse(token);
    }

    /**
     * 创建token
     */
    public static String created() {
        JwtBuilder jwtBuilder = Jwts.builder().setId("123456") // 设置ID
                .setSubject("PkyShare") // 存放的内容
                .setIssuedAt(new Date()) // 签名签发时间
                .signWith(SignatureAlgorithm.HS256, key) // 加密算法以及秘钥
                .claim("school", "gcd");  // 自定义内容,key-value 形式
        String token = jwtBuilder.compact();
        System.out.println(token); // 创建 JwtBuilder 对象并打印
        return token;
    }

    /**
     * 解析 token
     * @param token
     */
    public static void parse(String token) {
        Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
        System.out.println(claims.getId());
        System.out.println(claims.getSubject());
        System.out.println(claims.getIssuedAt());
        String school = (String) claims.get("school");
        System.out.println("school----" + school);
    }
}

2.3 测试结果

在这里插入图片描述

3 SpringBoot 整合 JWT

  以上简单的使用我们基本了解了 token 的生成,接下来完成一个简单的登录逻辑。

3.1 pom.xml
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.2.0</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
3.2 JwtUtil 工具类
import com.huanda.chetaijitoc.commons.domain.userdb.UserInfo;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

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

/**
 * JWT 工具类
 * @author PkyShare
 * @date 2020/1/3 0003 16:39
 */
public class JwtUtil {
    //token 密钥
    private static final String TOKEN_SECRET = "dfsjeo329safei22kdfeiajdeie1";
    //15分钟超时时间
    private static final long OUT_TIME = 150 * 60 * 1000;

    /**
     * 用户登录成功后生成Jwt
     * 使用Hs256算法  私匙使用用户密码
     * @param user 登录成功的user对象
     * @return
     */
    public static String createJWT(UserInfo user) {
        //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        //生成JWT的时间
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        //创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", user.getId());
        claims.put("username", user.getUsername());
        claims.put("password", user.getPassword());
        //生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
//        String key = user.getPassword();
        //生成签发人
        String subject = user.getUsername();
        //下面就是在为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, TOKEN_SECRET);
        long expMillis = nowMillis + OUT_TIME;
        Date exp = new Date(expMillis);
        //设置过期时间
        builder.setExpiration(exp);
        return builder.compact();
    }


    /**
     * Token的解密
     * @param token 加密后的token
     * @return
     */
    public static Claims parseJWT(String token) {
        //签名秘钥,和生成的签名的秘钥一模一样
        //得到DefaultJwtParser
        Claims claims = Jwts.parser()
                //设置签名的秘钥
                .setSigningKey(TOKEN_SECRET)
                //设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }


    /**
     * 校验token
     * 在这里可以使用官方的校验,我这里校验的是token中携带的密码于数据库一致的话就校验通过
     * @param claims Payload(载荷)
     * @param user
     * @return
     */
    public static Boolean isVerify(Claims claims, UserInfo user) {
        if (claims.get("password").equals(user.getPassword())) {
            return true;
        }
        return false;
    }

}
3.3 拦截器

  除了登录、注册请求外,其他请求都需要携带 token,因此需要设置一个拦截器进行拦截。

3.3.1 @LoginToken 登录注解

  方法上用到该注解的则跳过 token 验证。

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

/**
 * @author PkyShare
 * @date 2020/1/3 0003 17:25
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginToken {
    boolean required() default true;
}

3.3.2 @CheckToken 校验注解

  方法上用到该注解的则校验 token。

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

/**
 * @author PkyShare
 * @date 2020/1/3 0003 17:24
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckToken {
    boolean required() default true;
}

3.3.3 拦截器配置

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 PkyShare
 * @date 2020/1/3 0003 17:49
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

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

    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
}

3.3.4 Token 拦截器

import com.alibaba.fastjson.JSONObject;
import com.huanda.chetaijitoc.admin.annotation.CheckToken;
import com.huanda.chetaijitoc.admin.annotation.LoginToken;
import com.huanda.chetaijitoc.admin.annotation.QueryToken;
import com.huanda.chetaijitoc.commons.constant.HttpStatus;
import com.huanda.chetaijitoc.commons.domain.userdb.UserInfo;
import com.huanda.chetaijitoc.commons.service.userdb.UserInfoService;
import com.huanda.chetaijitoc.commons.service.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.SignatureException;
import org.apache.commons.lang3.StringUtils;
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.io.PrintWriter;
import java.lang.reflect.Method;

/**
 * token 拦截器
 * @author PkyShare
 * @date 2020/1/3 0003 17:28
 */
public class AuthenticationInterceptor implements HandlerInterceptor{

    @Autowired
    UserInfoService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
        // 从 http 请求头中取出 token
        String token = request.getHeader("token");
        // 如果不是映射到方法直接通过
        if (!(object instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();
        //检查是否有LoginToken注释,有则跳过认证
        if (method.isAnnotationPresent(LoginToken.class)) {
            LoginToken loginToken = method.getAnnotation(LoginToken.class);
            if (loginToken.required()) {
                return true;
            }
        }
        //检查是否有QueryToken注释,有则判断token
        if(method.isAnnotationPresent(QueryToken.class)) {
            QueryToken queryToken = method.getAnnotation(QueryToken.class);
            if(queryToken.required()) {
                return setRequest(request, response, token);
            }
        }
        //检查有没有需要用户权限的注解
        if (method.isAnnotationPresent(CheckToken.class)) {
            CheckToken checkToken = method.getAnnotation(CheckToken.class);
            if (checkToken.required()) {
                return checkToken(request, response, token);
            }
        }
        return true;
    }

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

    }

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

    }

    /**
     * 违章查询时设置request
     * @param request
     * @param response
     * @param token
     * @return
     * @throws Exception
     */
    private boolean setRequest(HttpServletRequest request, HttpServletResponse response, String token) throws Exception {
        if(StringUtils.isBlank(token)) {
            return true;
        }
        return checkToken(request, response, token);
    }

    /**
     * 校验 token
     * @param request
     * @param response
     * @param token
     * @return
     */
    private boolean checkToken(HttpServletRequest request, HttpServletResponse response, String token) throws Exception {
        // 执行认证
        if (StringUtils.isBlank(token)) {
            setResultJson(response, HttpStatus.MISSING_PARAMS.getCode(), "Token 不可为空");
            return false;
        }
        // 获取 token 中的 user id
        Long userId;
        Claims claims;
        try {
            claims = JwtUtil.parseJWT(token);
            userId = (Long) claims.get("id");
        }
        catch (ExpiredJwtException e) { // 签名过期
            setResultJson(response, HttpStatus.TOKEN_EXPIRED.getCode(), HttpStatus.TOKEN_EXPIRED.getTitle());
            return false;
        }
        catch (SignatureException e) { // 签名错误
            setResultJson(response, HttpStatus.TOKEN_ERROR.getCode(), HttpStatus.TOKEN_ERROR.getTitle());
            return false;
        }
        catch (Exception e) { // 其他未知异常
            setResultJson(response, HttpStatus.ABNORMAL_ACCESS.getCode(), HttpStatus.ABNORMAL_ACCESS.getTitle());
            return false;
        }
        UserInfo user = userService.getById(userId);
        if (user == null) {
            setResultJson(response, HttpStatus.USER_NOT_EXIT.getCode(), HttpStatus.USER_NOT_EXIT.getTitle());
            return false;
        }
        Boolean verify = JwtUtil.isVerify(claims, user);
        if (!verify) {
            setResultJson(response, HttpStatus.USER_INFO_ERROR.getCode(), HttpStatus.USER_INFO_ERROR.getTitle());
            return false;
        }
        request.setAttribute("userInfo", user);
        return true;
    }

    /**
     * 设置响应json数据
     * @param response
     * @param code 状态码
     * @param title 返回说明
     * @return
     */
    private void setResultJson(HttpServletResponse response, Integer code, String title) throws Exception{
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        JSONObject resultJson = new JSONObject();
        resultJson.put("code", code);
        resultJson.put("count", 0);
        resultJson.put("title", title);
        writer.append(resultJson.toJSONString());
    }

}
3.4 自定义返回状态码
/**
 * HTTP 状态码
 */
public enum HttpStatus {
    OK(20000, "请求成功"),
    SUCCESS(20001, "保存成功"),
    DELETE(20004, "删除成功"),
    FORBIDDEN(40003, "权限不足"),
    NOT_FOUND(40004, "资源未找到"),
    USER_NOT_EXIT(40081, "用户不存在"),
    TOKEN_ERROR(40082, "签名错误"),
    TOKEN_EXPIRED(40083, "Token 过期,请重新登录"),
    USER_INFO_ERROR(40084, "账号或密码错误"),
    ABNORMAL_ACCESS(40091, "异常访问"),

    private Integer code;
    private String title;

    HttpStatus(Integer code, String title) {
        this.code = code;
        this.title = title;
    }

    public Integer getCode() {
        return code;
    }

    public String getTitle() {
        return title;
    }
}
3.5 Controller
package com.huanda.chetaijitoc.admin.controller.userdb;

import com.huanda.chetaijitoc.admin.annotation.LoginToken;
import com.huanda.chetaijitoc.admin.controller.base.AbstractBaseController;
import com.huanda.chetaijitoc.commons.constant.HttpStatus;
import com.huanda.chetaijitoc.commons.domain.userdb.UserInfo;
import com.huanda.chetaijitoc.commons.dto.AbstractBaseResult;
import com.huanda.chetaijitoc.commons.dto.LoadReturnData;
import com.huanda.chetaijitoc.commons.service.userdb.UserInfoService;
import com.huanda.chetaijitoc.commons.utils.BeanValidator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;

/**
 * 用户基本信息表控制器
 */
@RestController
@RequestMapping(value = "users")
public class UserInfoController extends AbstractBaseController<UserInfo> {

    @Autowired
    UserInfoService userInfoService;

    /**
     * 登录
     * @param userInfo 用户信息
     * @return
     */
    @PostMapping(value = "login")
    @LoginToken
    public AbstractBaseResult login(@RequestBody UserInfo userInfo) {
        LoadReturnData<UserInfo> loadReturnData = new LoadReturnData<>();
        loadReturnData = userInfoService.getByUsername(loadReturnData, userInfo);
        return result(loadReturnData.getCode(), loadReturnData.getMsg(), loadReturnData.getToken());
    }

    /**
     * 注册
     * @param userInfo 用户信息
     * @return
     */
    @LoginToken
    @PostMapping(value = "register")
    public AbstractBaseResult register(@RequestBody UserInfo userInfo) {
        // 数据校验
        String message = BeanValidator.validator(userInfo);
        if(StringUtils.isNotBlank(message)) {
            return result(HttpStatus.MISSING_PARAMS.getCode(), 0, message, null);
        }
        LoadReturnData<UserInfo> loadReturnData = new LoadReturnData<>();
        loadReturnData = userInfoService.registe(loadReturnData, userInfo);
        return result(loadReturnData.getCode(), 0, loadReturnData.getMsg(), null);
    }
    
    @CheckToken
    @PostMapping(value = "/test")
    public AbstractBaseResult test() {
        return result(HttpStatus.OK.getCode(), 0, HttpStatus.OK.getTitle(), null);
    }
    
}

注:上述 AbstractBaseResult(统一返回结果)、LoadReturnData、BeanValidator 和 AbstractBaseController 是自己封装的,这里可以暂不理会,用自己的写的返回即可。

3.6 UserInfoserviceImpl
    /**
     * 通过用户名获取用户信息并设置 token
     * @param loadReturnData 承载数据模型
     * @param userInfo 登录用户信息
     * @return
     */
    @Override
    public LoadReturnData<UserInfo> getByUsername(LoadReturnData<UserInfo> loadReturnData, UserInfo userInfo){
        Example example = new Example(UserInfo.class);
        example.createCriteria().andEqualTo("username", userInfo.getUsername());
        UserInfo userDB = userInfoMapper.selectOneByExample(example);
        if(userDB == null) {
            loadReturnData.setMsg(HttpStatus.USER_NOT_EXIT.getTitle());
            loadReturnData.setCode(HttpStatus.USER_NOT_EXIT.getCode());
            return loadReturnData;
        }
        if(!userDB.getPassword().equals(userInfo.getPassword())) {
            loadReturnData.setMsg(HttpStatus.USER_INFO_ERROR.getTitle());
            loadReturnData.setCode(HttpStatus.USER_INFO_ERROR.getCode());
            return loadReturnData;
        }
        loadReturnData.setCode(HttpStatus.OK.getCode());
        loadReturnData.setMsg("登录成功");
        loadReturnData.setToken(JwtUtil.createJWT(userDB)); 
        return loadReturnData;
    }
3.7 Token 校验测试
  • 登录测试
    在这里插入图片描述
    在这里插入图片描述
  • 需要校验 token

在这里插入图片描述
在这里插入图片描述

至此,springboot 整合 JWT 基本完成。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值