SpringBoot使用自定义注解校验Token及角色权限

最近学习时,自己项目中用到了Token,涉及到User和Admin两个角色,普通的用户并没有很大的权限,和Admin拥有较大的权限。每次在写代码前都需要重复校验角色,根据角色来决定是否有操作这个接口的权限。

于是我想到,能不能定义一个注解,在注解的参数中输入这个接口可访问的角色,同时再定义一个接口,得到对应token的参数。

项目没有涉及到RBAC模式,就划分Admin和User,因此思路还是比较好理解的。

1.定义一个Token注解


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 自定义注解,实现Token的校验
 * 2021-9-23 09:39:37
 * @author weilinlin
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Token {
    public static final String USER_KEY = "USER_ID";
    //开关,默认打开,设为false时关闭
    boolean validate() default true;
    //角色信息,支持多个角色
    String roles() ;

}

2.设定第二个注解,用于在解析Token时,返回对应的数据,如Id、role

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

/**
 * 自定义注解,获取用户的信息
 * 2021-9-23 09:39:37
 * @author weilinlin
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}

3.编写解析逻辑:,需要将解析后的数据放到request中

import entity.BaseController;
import entity.StatusCode;
import entity.TokenEntity;
import exception.CourseException;
import io.jsonwebtoken.Claims;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import utils.ComStringUtil;
import utils.JwtUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

@Component
public class JwtInterceptor extends HandlerInterceptorAdapter {
    private static final Logger logger = LoggerFactory.getLogger(BaseController.class);
    public static final String USER_KEY = "SMOYU";
    @Autowired
    private JwtUtil jwtUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Token annotation;
        if(handler instanceof HandlerMethod) {
            annotation = ((HandlerMethod) handler).getMethodAnnotation(Token.class);
        }else{
            return true;
        }
        //没有声明需要权限,或者声明不验证权限
        if(annotation == null || annotation.validate() == false){
            return true;
        }

        //没有传送角色信息,默认为不校验权限
        if(ComStringUtil.isEmpty(annotation.roles())){
            return true;
        }

        //从header中获取token
        String header = request.getHeader("Authorization");
        if(ComStringUtil.isEmpty(header)){
            throw new CourseException(StatusCode.ACCESSERROR,"权限不足!");
        }
        logger.info("请求头:"+header);
        //得到token字符串
        String token = ComStringUtil.getTokenBySubs(header);
        if(ComStringUtil.isEmpty(token)){
            throw new CourseException(StatusCode.ACCESSERROR,"权限不足!");
        }
        //解析token
        Claims claims = null;
        try {
            claims = jwtUtil.parseJWT(token);
        }catch (Exception e){

            throw new CourseException(StatusCode.ACCESSERROR,"Token校验失败!请重新登录");
        }
        if(claims == null){

            throw new CourseException(StatusCode.ACCESSERROR,"Token校验失败!请重新登录");
        }
        logger.info("Token解析数据:"+claims);

        //得到角色信息,只要有一个角色符合,就能返回
        String[] roles = annotation.roles().split(",");
        logger.info("角色参数:"+roles);
        TokenEntity entity = new TokenEntity();

        for (String role : roles) {
            if(!role.equals(claims.get("roles"))){
                continue;
            }
            entity.setId(Long.valueOf(claims.getId()));
            entity.setRoles((String) claims.get("roles"));
            request.setAttribute(USER_KEY,entity);
            return  true;
        }

        throw new CourseException(StatusCode.ACCESSERROR,"权限不足!请使用正确的账号操作。");

    }
}

4.从request中拿数据

import com.smoyu.user.annotation.LoginUser;
import entity.TokenEntity;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import java.util.Map;

@Component
public class LoginUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver
{
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return methodParameter.getParameterType().isAssignableFrom(TokenEntity.class)&&methodParameter.hasParameterAnnotation(LoginUser.class);
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        //获取登陆用户信息

        Object object = nativeWebRequest.getAttribute(AuthorizationInterceptor.USER_KEY, RequestAttributes.SCOPE_REQUEST);
        if(object == null){
            return null;
        }
        return (TokenEntity)object;
    }
}

5.拦截对应的路径即可:

@Configuration
public class JwtConfig extends WebMvcConfigurationSupport {


    @Autowired
    private AuthorizationInterceptor jwtInterceptor;
    @Autowired
    private LoginUserHandlerMethodArgumentResolver loginUserHandlerMethodArgumentResolver;
//    private String fileSavePath = "/usr/xiaomoyu/images/";

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/**/login/**");
        super.addInterceptors(registry);
    }
    /**
     * 发现如果继承了WebMvcConfigurationSupport,则在yml中配置的相关内容会失效。 需要重新指定静态资源
     *
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations(
                "classpath:/static/");
        registry.addResourceHandler("swagger-ui.html").addResourceLocations(
                "classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations(
                "classpath:/META-INF/resources/webjars/");
        //设定本地文件访问路径
    /*  registry.addResourceHandler("/images/**")
                .addResourceLocations("file:"+fileSavePath);*/
        super.addResourceHandlers(registry);
    }

    /**
     * 实现LoginUser注解,从请求request中得到参数
     * @param argumentResolvers
     */
    @Override
    protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(loginUserHandlerMethodArgumentResolver);
    }


}

使用方式

    @GetMapping("/userInfo/token")
    @Token(roles = "student")
    public Result getUserInfo(@LoginUser TokenEntity tokenEntity){
}

加入Token,roles如果有多个角色就用逗号隔开。
在方法参数上加入注解@LoginUser即可。

大功告成

你可能感兴趣的:(SpringCloud,分布式,笔记,restful,java,spring)