附:Token令牌和自定义注解

普通令牌写法(了解原理)

在一个完备的springboot项目中,可以通过request获得tonken

  • 判断是否为空
  • 判断redis中是否过期
/**
 * @author : Lcywings
 * @date : 2021/8/11 11:42
 * @acl : true
 * @description : 根据id查询订单详情
 */
@GetMapping("/orderInfo")
@ApiOperation(value = "查询订单详情", notes = "支持Token授权")
public RequestResult<String> queryOrderInfo(HttpServletRequest request, @RequestParam Integer id) {

    // 校验权限,是否已经登录,只有已经登录用户才可以查询订单详情
    String token = request.getHeader("token");

    // 判断token是否为空
    if (StringUtils.isEmpty(token)) {
        return ResultBuildUtil.fail("9001", "token为空,查询失败");
    }

    // 判断token是否存在
    Object userObject = redisUtils.get(token);
    if (ObjectUtils.isEmpty(userObject)) {
        return ResultBuildUtil.fail("9002", "token失效,查询失败");
    }

    //获取登录用户信息,根据用户编号,查询对应的订单信息
    User user = JSON.parseObject(userObject.toString(), User.class);

    log.info("------ 根据用户编号:{},订单编号:{},查询订单详情 ------", user.getUserId(), id);

    // TODO 调用service层接口,到数据库查询订单详情

    return ResultBuildUtil.success("查询订单详情成功!");
}

自定义注解实现:

/**
 * @author : Lcywings
 * @date : 2021/8/11 11:42
 * @acl : true
 * @description : 根据查询订单列表
 */
@GetMapping("/orderList")
@ApiOperation(value = "查询订单列表", notes = "支持Token授权")
@RequestPermission
public RequestResult<String> queryOrderList(javax.servlet.http.HttpServletRequest request) {
    log.info("------ 根据用户编号:{},查询订单列表 ------", "U0001");

    // TODO 调用service层接口,到数据库查询订单详情

    return ResultBuildUtil.success("查询订单详情成功!");
}

实现自定义注解

编写自定义注解类

@RequestPermission--验证是否有令牌以及令牌是否过期

需要元注解支持,修改@Target,添加可以在类上使用

package com.lcywings.sbt.config.annotation;


import java.lang.annotation.*;

/**
 * @author : Lcywings
 * @date : 2021/8/11 11:57
 * @acl : true
 * @description : 自定义鉴权注解
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestPermission {
}

为注解添加拦截功能

  • 自定义一个拦截器实现HandlerInterceptor接口
  • preHandle()方法,在调用控制层方法前调用---即为拦截
  • checkRequestPermission()自定义方法,用来校验是否加了注解
package com.lcywings.sbt.intercept;

import com.lcywings.sbt.config.annotation.RequestPermission;
import com.lcywings.sbt.util.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
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;

/**
 * Created on 2021/8/11.
 * <p>
 * Author : Lcywings
 * <p>
 * Description : 权限拦截器
 */
@Slf4j
public class SecurityInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisUtils redisUtils;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {

        // 判断是否需要做权限校验(是否增加了自定义权限注解),如果需要,就要判断token是否有效,如果不需要,就只请求目标处理方法(放行)
        if (this.checkRequestPermission(handler)) {
            // 获取前端参数token,从redis中删除
            String token = httpServletRequest.getHeader("token");

            // 判断token是否为空
            if (StringUtils.isEmpty(token)) {
                log.warn("------ token为空,权限不足,请先登录再进行操作 ------");
                // return false;
                throw new RuntimeException("token为空,权限不足,请先登录再进行操作");
            }

            // 判断token是否存在
            if (ObjectUtils.isEmpty(redisUtils.get(token))) {
                log.warn("------ token为非法或者已失效,权限不足,请先登录再进行操作 ------");
                // return false;
                throw new RuntimeException("token为非法或者已失效,权限不足,请先登录再进行操作");
            }
        }

        // 放行请求,或者权限校验通过,访问目标请求处理方法
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, Exception e) throws Exception {

    }

    /**
     * @author : Lcywings
     * @date : 2021/8/11 14:39
     * @acl : true
     * @description : 判断目标方法是否增加了自定义权限校验注解
     */
    private boolean checkRequestPermission(Object handler) {

        // 判断当前handler对象是否映射到目标请求处理方法
        if (handler instanceof HandlerMethod) {

            // 强制转换为目标请求处理方法对象(封装了目标请求处理方法的所有内容)
            HandlerMethod handlerMethod = (HandlerMethod) handler;

            // 获取目标请求处理方法的目标注解
            RequestPermission requestPermission = handlerMethod.getMethod().getAnnotation(RequestPermission.class);

            // 判断是否增加了权限校验自定义注解
            if (null == requestPermission) {
                // 没有加自定义权限校验注解,不代表不需要权限校验,还要看档期处理类上有没有加对应的权限校验注解
                requestPermission = handlerMethod.getMethod().getDeclaringClass().getAnnotation(RequestPermission.class);
            }

            // 判断是否获取到了自定义权限注解对象,如果获取到了,代表需要做权限校验,否则放行
            return null != requestPermission;

        }

        // 不需要权限校验,直接放行
        return false;
    }
}

拦截器需要配置类

  • 要将自定义拦截器注入容器
  • 在addInterceptors()方法中添加拦截器
package com.lcywings.sbt.config.annotation;

import com.lcywings.sbt.intercept.SecurityInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;

import java.util.List;

/**
 * Created on 2021/8/11.
 * <p>
 * Author : Lcywings
 * <p>
 * Description :
 */
@Configuration
public class PermissionConfig implements WebMvcConfigurer {

    @Bean
    public SecurityInterceptor securityInterceptor() {
        return new SecurityInterceptor();
    }

    @Override
    public void configurePathMatch(PathMatchConfigurer pathMatchConfigurer) {

    }

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer contentNegotiationConfigurer) {

    }

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer asyncSupportConfigurer) {

    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer defaultServletHandlerConfigurer) {

    }

    @Override
    public void addFormatters(FormatterRegistry formatterRegistry) {

    }

    @Override
    public void addInterceptors(InterceptorRegistry interceptorRegistry) {
        // 添加自定义拦截器
        interceptorRegistry.addInterceptor(securityInterceptor()).addPathPatterns("/**");
    }


    @Override
    public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) {

    }

    @Override
    public void addCorsMappings(CorsRegistry corsRegistry) {

    }

    @Override
    public void addViewControllers(ViewControllerRegistry viewControllerRegistry) {

    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry viewResolverRegistry) {

    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) {

    }

    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) {

    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> list) {

    }

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> list) {

    }

    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {

    }

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {

    }

    @Override
    public Validator getValidator() {
        return null;
    }

    @Override
    public MessageCodesResolver getMessageCodesResolver() {
        return null;
    }
}

自定义注解

@RequestUser--验证令牌中是否含有用户信息

控制层

 /**
     * @author : Lcywings
     * @date : 2021/8/12 17:03
     * @acl : true
     * @description : 拉取用户信息
     */
    @RequestAllow
    @RequestMapping("/pullUserInfo")
    public RequestResult<User> pullUserInfo(HttpServletRequest request, @RequestUser User u) {
        return ResultBuildUtil.success(u);
    }

注解类

package com.lcywings.sbt.config.annoation;

import java.lang.annotation.*;

/**
 * @author : Lcywings
 * @date : 2021/8/12 8:38
 * @acl : true
 * @description : 请求用户信息自定义注解,当参数增加此注解,就自动从redis中接续用户信息,封装到参数中
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestUser {
}

解析包下解析类

package com.lcywings.sbt.resolve;

import com.alibaba.fastjson.JSON;
import com.lcywings.sbt.beans.User;
import com.lcywings.sbt.config.annoation.RequestUser;
import com.lcywings.sbt.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
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.jws.soap.SOAPBinding;

/**
 * Created on 2021/8/12.
 * <p>
 * Author : Lcywings
 * <p>
 * Description : 用户信息注解解析器
 */
@Slf4j
public class RequestUserResolver implements HandlerMethodArgumentResolver {

    @Autowired
    private RedisUtils redisUtils;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {

        // 强制指定是否解析,方法的形参必须是增加了自定义用户信息注解:@RequestUser,且注解解析的目标实体类型就是此处指定的类型
        return parameter.hasParameterAnnotation(RequestUser.class)
                && parameter.getParameterType().isAssignableFrom(User.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, @NotNull NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        // 执行参数解析,只有上面方法为true才会执行表
        // 从redis中获取用户信息json字符串对象
        // 校验权限,是否已经登录,只有已经登录用户才可以查询订单详情
        // 此处无序做token有效性校验,一般需要解析用户讯息必须这么权限校验
        Object userObj = redisUtils.get(webRequest.getHeader("token"));
        log.info("redisUtils.get({}):        -----      {}", webRequest.getHeader("token"), userObj);

        // 返回目标实体类型
        return JSON.parseObject(userObj.toString(), User.class);
    }
}

在配置中注入bean

package com.lcywings.sbt.config;

import com.lcywings.sbt.config.annoation.RequestAllow;
import com.lcywings.sbt.config.annoation.RequestUser;
import com.lcywings.sbt.intercept.SecurityInterceptor;
import com.lcywings.sbt.resolve.RequestUserResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;package com.lcywings.sbt.resolve;

import com.alibaba.fastjson.JSON;
import com.lcywings.sbt.beans.User;
import com.lcywings.sbt.config.annoation.RequestUser;
import com.lcywings.sbt.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
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.jws.soap.SOAPBinding;

/**
 * Created on 2021/8/12.
 * <p>
 * Author : Lcywings
 * <p>
 * Description : 用户信息注解解析器
 */
@Slf4j
public class RequestUserResolver implements HandlerMethodArgumentResolver {

    @Autowired
    private RedisUtils redisUtils;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {

        // 强制指定是否解析,方法的形参必须是增加了自定义用户信息注解:@RequestUser,且注解解析的目标实体类型就是此处指定的类型
        return parameter.hasParameterAnnotation(RequestUser.class)
                && parameter.getParameterType().isAssignableFrom(User.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, @NotNull NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        // 执行参数解析,只有上面方法为true才会执行表
        // 从redis中获取用户信息json字符串对象
        // 校验权限,是否已经登录,只有已经登录用户才可以查询订单详情
        // 此处无序做token有效性校验,一般需要解析用户讯息必须这么权限校验
        Object userObj = redisUtils.get(webRequest.getHeader("token"));
        log.info("redisUtils.get({}):        -----      {}", webRequest.getHeader("token"), userObj);

        // 返回目标实体类型
        return JSON.parseObject(userObj.toString(), User.class);
    }
}

import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;

import java.util.List;

/**
 * Created on 2021/8/12.
 * <p>
 * Author : Lcywings
 * <p>
 * Description :
 */
@Configuration
public class PermissionConfig implements WebMvcConfigurer {

    @Bean
    public SecurityInterceptor securityInterceptor() {
        return new SecurityInterceptor();
    }

    @Bean
    public RequestUserResolver requestUserResolver() {
        return new RequestUserResolver();
    }

    @Override
    public void configurePathMatch(PathMatchConfigurer pathMatchConfigurer) {

    }

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer contentNegotiationConfigurer) {

    }

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer asyncSupportConfigurer) {

    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer defaultServletHandlerConfigurer) {

    }

    @Override
    public void addFormatters(FormatterRegistry formatterRegistry) {

    }

    @Override
    public void addInterceptors(InterceptorRegistry interceptorRegistry) {
        interceptorRegistry.addInterceptor(securityInterceptor()).addPathPatterns("/**");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) {

    }

    @Override
    public void addCorsMappings(CorsRegistry corsRegistry) {

    }

    @Override
    public void addViewControllers(ViewControllerRegistry viewControllerRegistry) {

    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry viewResolverRegistry) {

    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolver) {
        argumentResolver.add(requestUserResolver());
    }

    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) {

    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> list) {

    }

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> list) {

    }

    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {

    }

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {

    }

    @Override
    public Validator getValidator() {
        return null;
    }

    @Override
    public MessageCodesResolver getMessageCodesResolver() {
        return null;
    }
}


课后作业:

将之前课程讲的微信授权登录+今天的token整合到一起(登录还支持授权登录,从微信拉取用户信息,进行登录)用户信息存储的token值:建议使用微信官方返回的access_token,它有实效,如果失效有其它限制,可以自定义超时时间出去面试:可以说token用的微信授权获取的access_token,有实效,自带刷新接口,如果是用户名密码登录,使用内部规则生成的,存入redis

Q.E.D.