SpringBoot整合JJWT实现Token登录认证

Java技术 潘老师 4年前 (2020-09-29) 4409 ℃ (0) 扫码查看

如果你还不知道什么是JWT,可以参考文章:

JWT是什么?有什么作用?一文帮你了解JWT!

一、什么是JWT? 1、引用官网对JWT的描述,翻译意思大概如下: JWT全称为JSON Web Token, […]

我们这里用到SpringBoot整合JJWT实现Token登录认证,JJWT是指Java JWT,适用于 Java 和 Android 的 JSON Web Token(JWT)库。下面我们通过SpringBoot整合JJWT来实现Token登录认证。

1、为了方便演示,我这里只创建了只有Spring Web模块的SpringBoot项目,没有涉及数据库操作,项目创建很简单,我在此只贴两张IDEA中创建的两张图:
SpringBoot整合JJWT实现Token登录认证
只选择Spring Web模块
SpringBoot整合JJWT实现Token登录认证

2、在resources目录中新建application.yml配置文件(去除自动生成的application.properties),只简单配置下服务器端口为8080:

server:
  port: 8080
提示:可以参考JJWT的官方github:点击直达

1、在application.yml新增JWT相关配置,如下:

# jwt 配置
custom:
  jwt:
    # header:凭证(校验的变量名)
    header: Authorization
    # 有效期1天(单位:s)
    expire: 5184000
    # secret: 秘钥(普通字符串) 不能太短,太短可能会导致报错
    secret: 99c2918fe19d30bce25abfac8a3733ec
    # 签发者
    issuer: panziye

2、在pom.xml中引入jjwt相关依赖(我们使用目前最新版0.11.2版本)和fastjson依赖,方便后面操作json,新增配置如下:

<!--    fastjson    -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.9</version>
</dependency>

<!--    jjwt    -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.2</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>

3、在项目中新建util包,在util包下新建JwtUtil类,主要实现创建Token、解析Token和判断Token是否过期功能代码如下:

package com.panziye.jwtdemo.util;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
@ConfigurationProperties(prefix = "custom.jwt")
public class JwtUtil {
    private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
    // 秘钥
    private String secret;
    // 有效时间
    private Long expire;
    // 用户凭证
    private String header;
    // 签发者
    private String issuer;
    /**
     * 生成token签名
     * @param subject
     * @return
     */
    public String createToken(String subject) {
        Date now = new Date();
        // 过期时间
        Date expireDate = new Date(now.getTime() + expire * 1000);

        //创建Signature SecretKey
        final SecretKey key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));

        //header参数
        final Map<String, Object> headerMap = new HashMap<>();
        headerMap.put("alg", "HS256");
        headerMap.put("typ", "JWT");

        //生成token
        String token = Jwts.builder()
                .setHeader(headerMap)
                .setSubject(subject)
                .setIssuedAt(now)
                .setExpiration(expireDate)
                .setIssuer(issuer)
                .signWith(key,SignatureAlgorithm.HS256)
                .compact();

        logger.info("JWT[" + token + "]");
        return token;
    }

    /**
     * 解析token
     *
     * @param token token
     * @return
     */
    public Claims parseToken(String token) {

        Claims claims = null;
        try {
            //创建Signature SecretKey
            final SecretKey key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));

            claims = Jwts.parserBuilder()
                    .setSigningKey(key)
                    .build()
                    .parseClaimsJws(token)
                    .getBody();
            logger.info("Parse JWT token success");
        } catch (JwtException  e) {
            logger.info("Parse JWT errror " + e.getMessage());
            return null;
        }
        return claims;
    }

    /**
     * 判断token是否过期
     *
     * @param expiration
     * @return
     */
    public boolean isExpired(Date expiration) {
        return expiration.before(new Date());
    }

    //getter and setter
    public void setSecret(String secret) {
        this.secret = secret;
    }

    public void setExpire(Long expire) {
        this.expire = expire;
    }

    public void setHeader(String header) {
        this.header = header;
    }
    //用于其他地方获取Header配置信息
    public String getHeader() {
        return header;
    }

    public void setIssuer(String issuer) {
        this.issuer = issuer;
    }
}

4、在项目中新建annotation包,在annotation下新建PassLogin注解类,方便用于后面针对某些请求方法添加此注解可以忽略Token验证,PassLogin.java代码如下:

package com.panziye.jwtdemo.annotation;

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

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

5、在项目中新建http包,在http下新建HttpEnum枚举类,用于存放响应状态码和对应的提示信息,这里面我们只列出一些常用的,可以根据自己需要补充,代码如下:

package com.panziye.jwtdemo.http;

public enum HttpEnum {
    /**
     * 请求处理正常
     */
    OK(200, "请求成功"),

    /**
     * 请求成功并且服务器创建了新的资源。
     */
    CREATED(201, "创建成功"),
    /**
     * 用户发出的请求有错误,服务器没有进行新建或修改数据的操作
     */
    INVALID_REQUEST(400, "非法请求"),
    /**
     * 访问内容不存在
     */
    NOTFOUND(404, "访问内容不存在"),
    /**
     * 表示用户没有权限(令牌、用户名、密码错误)
     */
    UNAUTHORIZED(401,"抱歉,您没有权限"),
    /**
     * 表示用户得到授权(与401错误相对),但是访问是被禁止的
     */
    FORBIDDEN(403,"禁止访问"),
    /**
     * 系统内部错误
     */
    INTERNAL_SERVER_ERROR(500, "系统内部错误");

    private String msg;

    private int code;

    private HttpEnum(int code, String msg) {
        this.msg = msg;
        this.code = code;
    }

    //获取code
    public int code(){
        return code;
    }
    //获取msg
    public String msg(){
        return msg;
    }
}

6、在http下新建ResponseResult类,用于封装响应状态码、提示信息和数据,这里只构建了一些常用的实例,可以根据自己的需求扩展,代码如下:

package com.panziye.jwtdemo.http;

import java.io.Serializable;

public class ResponseResult<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    //响应编码
    private int code;
    //提示信息
    private String msg;
    //响应数据
    private T data;

    //根据属性构建ResponseResult
    private static <T> ResponseResult<T> build( int code,String msg,T data) {
        return new ResponseResult<T>().setCode(code).setMsg(msg).setData(data);
    }

    //构建一些常用的
    //请求正常
    public static <T> ResponseResult<T> ok(){
        return build(HttpEnum.OK.code(),HttpEnum.OK.msg(),null);
    }
    public static <T> ResponseResult<T> ok(T data){
        return build(HttpEnum.OK.code(),HttpEnum.OK.msg(),data);
    }
    //创建成功
    public static <T> ResponseResult<T> created(){
        return build(HttpEnum.CREATED.code(),HttpEnum.CREATED.msg(),null);
    }
    //非法请求
    public static <T> ResponseResult<T> invalid_request(){
        return build(HttpEnum.INVALID_REQUEST.code(),HttpEnum.INVALID_REQUEST.msg(),null);
    }
    //访问内容不存在
    public static <T> ResponseResult<T> notFound(){
        return build(HttpEnum.NOTFOUND.code(),HttpEnum.NOTFOUND.msg(),null);
    }
    //没有权限
    public static <T> ResponseResult<T> unauthorized(){
        return build(HttpEnum.UNAUTHORIZED.code(),HttpEnum.UNAUTHORIZED.msg(),null);
    }
    //禁止访问
    public static <T> ResponseResult<T> forbidden(){
        return build(HttpEnum.FORBIDDEN.code(),HttpEnum.FORBIDDEN.msg(),null);
    }
    //系统内部错误
    public static <T> ResponseResult<T> internal_server_error(){
        return build(HttpEnum.INTERNAL_SERVER_ERROR.code(),HttpEnum.INTERNAL_SERVER_ERROR.msg(),null);
    }

    //getter和setter
    public int getCode() {
        return code;
    }
    //注意返回值类型
    public ResponseResult<T> setCode(int code) {
        this.code = code;
        return this;
    }

    public String getMsg() {
        return msg;
    }
    //注意返回值类型
    public ResponseResult<T> setMsg(String msg) {
        this.msg = msg;
        return this;
    }

    public T getData() {
        return data;
    }
    //注意返回值类型
    public ResponseResult<T> setData(T data) {
        this.data = data;
        return this;
    }
}

7、在项目中新建interceptor包,在interceptor下新建TokenInterceptor拦截器类,用于在请求被处理之前对Token进行拦截验证,代码如下:

package com.panziye.jwtdemo.interceptor;

import com.alibaba.fastjson.JSONObject;
import com.panziye.jwtdemo.annotation.PassLogin;
import com.panziye.jwtdemo.http.ResponseResult;
import com.panziye.jwtdemo.util.JwtUtil;
import io.jsonwebtoken.Claims;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
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;

public class TokenInterceptor implements HandlerInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
    @Autowired
    private JwtUtil jwtUtil;
    //在业务处理请求之前处理
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       //设置response响应数据类型为json和编码为utf-8
        response.setContentType("application/json;charset=utf-8");
        // 判断对象是否是映射到一个方法,如果不是则直接通过
        if (!(handler instanceof HandlerMethod)) {
            // instanceof运算符是用来在运行时指出对象是否是特定类的一个实例
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;

        Method method = handlerMethod.getMethod();
        //检查方法是否有PassLogin注解,有则跳过认证
        if (method.isAnnotationPresent(PassLogin.class)){
            return true;
        }

        // 从HTTP请求头中获取Authorization信息,
        String authorization = request.getHeader(jwtUtil.getHeader());
        //判断Authorization是否为空
        if(StringUtils.isEmpty(authorization)){
            logger.info("token无效");
            response.getWriter().write(JSONObject.toJSONString(ResponseResult.unauthorized()));
            return false;
        }
        //获取TOKEN,注意要清除前缀"Bearer "
        String token = authorization.replace("Bearer ","");
        // HTTP请求头中TOKEN解析出的用户信息
        Claims claims = jwtUtil.parseToken(token);
        if(claims == null){
            logger.info("token无效");
            response.getWriter().write(JSONObject.toJSONString(ResponseResult.unauthorized()));
            return false;
        }
        //校验是否过期
        boolean flag = jwtUtil.isExpired(claims.getExpiration());
        if(flag){
            logger.error("token过期");
            response.getWriter().write(JSONObject.toJSONString(ResponseResult.unauthorized()));
            return false;
        }
        //token正常,获取用户信息,比如这里的subject存的是用户id
        String subject = claims.getSubject();
        //将用户信息存入request,以便后面处理请求使用
        request.setAttribute("subject",subject);
        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 {

    }

}

8、在interceptor下新建TokenConfig拦截器配置类,用于配置TokenInterceptor,我们这里配置拦截所有请求,代码如下:

package com.panziye.jwtdemo.interceptor;

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;
@Configuration
public class TokenConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //拦截所有请求
        registry.addInterceptor(tokenInterceptor())
                .addPathPatterns("/**");
    }
    @Bean
    public TokenInterceptor tokenInterceptor() {
        return new TokenInterceptor();
    }
}

1、在项目中新建model包,在model下新建User实体类,代码如下:

package com.panziye.jwtdemo.model;

public class User {
    private Long userId;
    private String username;
    private String password;

    public User(){}

    public User(Long userId, String username, String password) {
        this.userId = userId;
        this.username = username;
        this.password = password;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

2、在项目中新建controller包,在controller下新建UserController实体类,代码如下:

package com.panziye.jwtdemo.controller;

import com.panziye.jwtdemo.annotation.PassLogin;
import com.panziye.jwtdemo.http.ResponseResult;
import com.panziye.jwtdemo.model.User;
import com.panziye.jwtdemo.util.JwtUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@RestController
@RequestMapping("/user")
public class UserController {
    private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
    @Autowired
    private JwtUtil jwtUtil;
    @PostMapping("/login")
    @PassLogin
    public ResponseResult login(User user, HttpServletResponse response){
        //在此省略了service和dao层代码
        if("admin".equals(user.getUsername()) && "123456".equals(user.getPassword())){
            logger.info("登录成功");
            //生成Token,以用户id作为载荷中的subject去创建
            String token = jwtUtil.createToken("1");
            logger.info("token="+token);
            User u = new User(1L,"admin","123456");
            response.setHeader(jwtUtil.getHeader(),"Bearer "+token);
            ResponseResult result = ResponseResult.ok(u);
            return result;
        }else {
            logger.info("登录失败");
            return ResponseResult.invalid_request();
        }
    }

    @GetMapping("/findUser")
    public ResponseResult findUser(HttpServletRequest request){
        //在拦截器中已经将从token中解析好的subject存入了request域,所以这里能获取到
        Long userId = Long.parseLong(request.getAttribute("subject").toString());
        System.out.println("发起请求用户的id===="+userId);
        //模拟返回一个数据
        ResponseResult result = ResponseResult.ok(userId);
        return result;
    }
}
提示:
1)@RestController结合了@Controller和@ResponseBody两项功能,用于类上,类中的方法返回的数据都会被转化为json格式响应
2)@GetMapping注解表示该方法只能接收get请求,@PostMapping注解表示该方法只能接受post请求,相当于@RequestMapping注解加上method属性指定请求方式效果

3、使用Postman工具测试,Postman工具下载地址:点击去下载

1)测试正常登录,发送登录post请求,携带用户名和密码参数,发现用户信息正常响应了
SpringBoot整合JJWT实现Token登录认证
再查看下Header响应头,发现Authorization有返回的Token令牌(不包含Bearer ),我们复制下这个令牌,用于后面测试。
SpringBoot整合JJWT实现Token登录认证
2)测试findUser的get请求,我们先不携带Token直接,发送get请求,发现返回401无权限
SpringBoot整合JJWT实现Token登录认证
然后我们在Header中的Type选择Bearer Token,把我们刚才复制的Token粘贴进去,再发送该请求,发现正常响应数据:
SpringBoot整合JJWT实现Token登录认证


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/java/1349.html
喜欢 (10)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】