Парсер параметров JWT+SpringBoot+SpringMVC

задняя часть сервер GitHub Безопасность

git-адрес: GitHub.com/Spicy is/JW T…

Введение

Каждый раньше использовал сеанс для хранения информации. Некоторые создаются контейнером, а некоторые хранятся в mysql или Redis. В этом проекте используется JWT. Мы инкапсулируем информацию о пользователе и срок действия входа в строку токена. Клиенту нужно только нести token в информации заголовка для каждого запроса.Без лишних слов, ниже представлена ​​структура каталогов.

Аннотация One.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), который связан с резолвером позади нас.

2. класс сущности бина

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
}

Наши бизнес-параметры

Информация о конфигурации Three.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());
        //注入我们的用户参数解析器
    }
}

Четыре.контроллер

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;
    }
}

Видно, что при успешном входе в систему мы можем сгенерировать строку токена и вернуть ее клиенту.Эта строка содержит информацию о пользователе и информацию о времени (механизм jwt).В то же время мы делаем запрос на имитацию бизнеса , бизнес это наш бизнес Параметр, user парсится по токену присланному клиентом, а как его парсить будет описано ниже.Видно что пока нам нужен параметр user мы можем напрямую использовать LoginUser аннотацию и пользователя, чтобы получить ее напрямую, что очень удобно. Клиенту не нужно смешивать нашу пользовательскую информацию с нашими бизнес-параметрами. Относительно безопасно.

Пять.исключение

package com.demo.exception;

public class RRException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    private String msg;
    private int code = 500;

}

Я не буду разбирать его здесь и могу согласовать соответствующий код ошибки с клиентом в соответствии с потребностями.

6. перехватчик-перехватчик

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 (не все методы должны быть проверены, такие как запросы входа в систему, запросы обратного вызова платежа), мы вытащим токен, отправленный клиентом, разберем его и определим, является ли он срок действия токена истек, нет Если срок действия токена истек, нам может потребоваться вернуть код ошибки клиенту, а затем снова войти в систему.Когда проверка будет успешной, мы вытащим информацию о пользователе и поместим ее в запрос (который будет анализируется в парсере параметров позже), что также является перехватом.Это суть устройства, которое может не только проверить, но и получить информацию о пользователе.

7. Анализатор параметров преобразователя

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.Второй метод - вывести нас из запроса в перехватчик, Поместите пользователя и верните его, который реализует внедрение пользовательского объекта.

8. 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;
    }
}

время истечения срока действия, секретный ключ, имя информации заголовка заголовка. Эти данные находятся в application.yml, здесь мы сгенерируем строку токена в соответствии с объектом пользователя и вытащим объект утверждений в соответствии с токеном, который содержит наше время истечения срока действия и предыдущее хранилище информация о пользователе.

9. параметры запуска 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


10. Резюме

Код маленький и пять внутренних органов полные.Вот и разбираемся в процессе.

1: Пользователь отправляет запрос на вход, и мы возвращаем токен (включая информацию о проверке и информацию о пользователе).

2: когда клиент отправляет запрос, он должен перенести токен в информацию заголовка, а сервер должен его проверить.

3: сервер перехватывает запрос, извлекает токен, анализирует его и сохраняет информацию о пользователе в запросе.

4: В springmvc мы добавили парсер параметров, который выводит пользователя из запроса и возвращает его, на этом парсинг и инъекция параметров завершены.

5: Мы можем использовать наш пользовательский объект в логическом коде контроллера.

Пишу первый раз, не знаю как выразить, надеюсь все меня простят...

гит-адрес: GitHub.com/Spicy is/JW T…