JWT+SpringBoot+SpringMVC参数解析器

栏目: Java · 发布时间: 7年前

内容简介:大家以前都使用过session存储信息,有的交给容器创建,有的存储到mysql或者redis,这次项目用到了JWT,我们把用户的信息和登录的过期时间都封装到一个token字符串里,客户端每次请求只需要在头信息里携带token即可,话不多说,下面是目录结构.该注解主要作用是过滤掉请求拦截器,使用该注解就不会对该请求进行拦截(权限校验),具体使用下面讲.该注解作用是SpringMVC参数解析器,类似于RequestBody注解(希望大家了解springmvc的参数解析机制),和我们后面的resolver相关联

大家以前都使用过session存储信息,有的交给容器创建,有的存储到 mysql 或者redis,这次项目用到了JWT,我们把用户的信息和登录的过期时间都封装到一个token字符串里,客户端每次请求只需要在头信息里携带token即可,话不多说,下面是目录结构.

JWT+SpringBoot+SpringMVC参数解析器

一.annonation注解

package com.demo.annotation;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IgnoreLogin {
}

复制代码

该注解主要作用是过滤掉请求拦截器,使用该注解就不会对该请求进行拦截(权限校验),具体使用下面讲.

package com.demo.annotation;

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

/**
 * 登录用户信息
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {}
复制代码

该注解作用是SpringMVC参数解析器,类似于RequestBody注解(希望大家了解springmvc的参数解析机制),和我们后面的resolver相关联.

二.bean实体类

package com.demo.bean;

public class User {
    private long userId;
    private String userName;
    private String password;
    忽略get/set
}

复制代码

我们的用户信息

package com.demo.bean;

public class Business {
    private String str;
    private int num;
    忽略get/set
}

复制代码

我们的业务参数

三.config配置信息

package com.demo.config;

import com.demo.interceptor.AuthorizationInterceptor;
import com.demo.resolver.UserArgumentResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
 * MVC配置
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private AuthorizationInterceptor authorizationInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authorizationInterceptor).addPathPatterns("/**");
        //注入我们自定义的拦截器,拦截所有请求
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new UserArgumentResolver());
        //注入我们的用户参数解析器
    }
}
复制代码

四.controller

package com.demo.controller;

import com.demo.annotation.IgnoreLogin;
import com.demo.annotation.LoginUser;
import com.demo.bean.Business;
import com.demo.bean.User;
import com.demo.util.JwtUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;


@RestController
public class UserController {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private JwtUtils jwtUtils;

    @PostMapping(value = "/login")
    @IgnoreLogin
    public String login() {
        //在此 我们不做登录检验 假设检验成功

        User user = new User();
        user.setUserId(9527);
        user.setUserName("小星星");
        return jwtUtils.generateToken(user);//这里只是为了测试只返回token,(请求不含IgnoreLogin注解时需要将token放在头信息里)
    }

    @PostMapping("/business")
    public User business(@RequestBody Business business, @LoginUser User user) {//在业务逻辑可以使用注解将我们的user注入进来
        logger.info("用户信息参数id:{},姓名:{}", user.getUserId(), user.getUserName());
        logger.info("我们的业务参数:{},{}", business.getStr(), business.getNum());
        return user;
    }
}

复制代码

可以看到当我们登陆成功后我们可以生成一个token字符串返回给客户端,这个字符串包含了用户信息和时间信息(jwt机制),同时我们做了一个模仿业务的请求,business是我们的业务参数,user是我们根据客户端上发的token解析出来的,下面会讲到如何解析.可以看到只要我们需要user的参数,我们就可以直接使用LoginUser注解和User就可以直接得到,非常方便,客户端并不需要将我们的用户信息参杂到我们的业务参数中.相对安全。

五.exception

package com.demo.exception;

public class RRException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    private String msg;
    private int code = 500;

}

复制代码

这里我就不解析了,根据需求可以和客户端协商相应的错误码

六.interceptor拦截器

package com.demo.interceptor;

import com.demo.annotation.IgnoreLogin;
import com.demo.exception.RRException;
import com.demo.util.JwtUtils;
import io.jsonwebtoken.Claims;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

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

/**
 * 权限(Token)验证
 */
@Component
public class AuthorizationInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private JwtUtils jwtUtils;

    public static final String USER_KEY = "user";

    private Logger logger = LoggerFactory.getLogger(getClass());

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

        if (!(handler instanceof HandlerMethod) || ((HandlerMethod) handler).
                getMethodAnnotation(IgnoreLogin.class) != null) {
            //如果不是HandlerMethod或者忽略登录
            logger.info("无需token校验,handler:{}", handler);
            return true;
        }

        //获取用户凭证
        String token = request.getHeader(jwtUtils.getHeader());
        if (StringUtils.isBlank(token)) {
            token = request.getParameter(jwtUtils.getHeader());
        }

        //凭证为空
        if (StringUtils.isBlank(token)) {
            throw new RRException(jwtUtils.getHeader() + "不能为空", HttpStatus.UNAUTHORIZED.value());
        }

        Claims claims = jwtUtils.getClaimByToken(token);
        if (claims == null || jwtUtils.isTokenExpired(claims.getExpiration())) {
            throw new RRException(jwtUtils.getHeader() + "失效,请重新登录", HttpStatus.UNAUTHORIZED.value());
        }

        //设置userId到request里,后续根据userId,获取用户信息
        request.setAttribute(USER_KEY, jwtUtils.getUser(claims));
        return true;
    }
}

复制代码

我们会过滤掉不是HandlerMethod的请求和带有IgnoreLogin的注解(并不是所有方法都需要校验,例如登录请求,支付回调请求),我们会取出客户端发出的token,解析出来并判断是否过期,没有token或者已过期我们可以需要返回一个错误码给客户端然后重新登录,当我们校验成功后我们会取出用户信息放入到request里(后面会在参数解析器里解析出来),这也是这个拦截器的精髓,既能校验又能获取用户的信息.

七.resolver参数解析器

package com.demo.resolver;

import com.demo.annotation.LoginUser;
import com.demo.bean.User;
import com.demo.interceptor.AuthorizationInterceptor;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpServletRequest;

/**
 * 用户参数解析器
 */
public class UserArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(LoginUser.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        User user = (User) request.getAttribute(AuthorizationInterceptor.USER_KEY);
        return user;
    }
}

复制代码

springmvc的参数解析器,需要继承HandlerMethodArgumentResolver,有两个方法,第一个就是支持什么类型的参数,可以看到我们支持拥有LoginUser注解的参数,第二个方法是从request里取出我们在拦截器中放入的user并返回,这样就实现了user对象的注入.

八.JwtUtils

package com.demo.util;

import com.alibaba.fastjson.JSONObject;
import com.demo.bean.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

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

/**
 * jwt工具类
 */
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtUtils {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private long   expire;
    private String secret;
    private String header;

    /**
     * 生成jwt token
     */
    public String generateToken(User user) {
        Date nowDate = new Date();

        //过期时间
        Date expireDate = new Date(nowDate.getTime() + expire * 1000);
        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(JSONObject.toJSONString(user))
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 解析出来claim
     * @param token
     * @return
     */
    public Claims getClaimByToken(String token) {
        try {
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            logger.debug("validate is token error ", e);
            return null;
        }
    }

    /**
     * 得到user
     * @param claims
     * @return
     */
    public User getUser(Claims claims) {
        return JSONObject.parseObject(claims.getSubject(), User.class);
    }

    /**
     * token是否过期
     * @return true:过期
     */
    public boolean isTokenExpired(Date expiration) {
        return expiration.before(new Date());
    }

    public String getSecret() {
        return secret;
    }

    public void setSecret(String secret) {
        this.secret = secret;
    }

    public long getExpire() {
        return expire;
    }

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

    public String getHeader() {
        return header;
    }

    public void setHeader(String header) {
        this.header = header;
    }
}

复制代码

expire过期时间,secret密钥,header头信息名称 这些数据在application.yml里,这里我们会根据User对象生成一个token字符串,根据token取出claims对象,这里就包含了我们的过期时间和之前我们所存的user信息.

九.springboot启动和yml参数

package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootStart {

    public static void main(String[] agrs) {
        SpringApplication.run(SpringBootStart.class, agrs);
    }
}

application.yml
jwt:
    #加密秘钥
    secret: f4e2e5203fg45sf45g4de581c0f9eb5
    #token,单位秒
    expire: 6000
    header: token


复制代码

以上所述就是小编给大家介绍的《JWT+SpringBoot+SpringMVC参数解析器》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Pro Git (Second Edition)

Pro Git (Second Edition)

Scott Chacon、Ben Straub / Apress / 2014-11-9 / USD 59.99

Scott Chacon is a cofounder and the CIO of GitHub and is also the maintainer of the Git homepage ( git-scm.com ) . Scott has presented at dozens of conferences around the world on Git, GitHub and the ......一起来看看 《Pro Git (Second Edition)》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换