基于 spring cloud 和 JWT 的简易轻量接口角色权限控制构建方法

2,018 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

之前在文章《基于 spring cloud 的简易 JWT 颁发与刷新构建方法》中,实现了简单的 token 颁发与刷新,并且可以拦截不带 token 或者 token 过期的请求,但还不能细化到控制携带合法 token 的请求是否有权限访问某些接口的功能,也就是类似使用 spring cloud security 中的 @PreAuthorize("hasAuthority('admin')") 注解的效果。

这篇文章将使用 spring cloud 中的拦截器来达到类似目的,接下来是具体做法:

  1. 首先定义注解 HasRole
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface HasRole {

    String role() default "";

}
  1. 接下来就是存放 role 位置的方式,如果注重安全性,防止 token 信息被客户端篡改,可以在颁发 token 时,将角色信息存入 redis 中。这里为了方便展示拦截器功能,实现方式选用直接存入 token 的载荷中,所以 JwtModel 改写如下:
public class JwtModel {

    public JwtModel(Integer id, String userName){
        this.userName = userName;
        this.id = id;
    }
    
    public JwtModel(Integer id, String userName, List<String> roles){
        this.userName = userName;
        this.id = id;
        this.roles = roles;
    }
    
    private Integer id;

    private String userName;
    
    private List<String> roles;

    public Integer getId(){
        return this.id;
    }
    
    public void setId(Integer id){
        this.id = id;
    }
    
    public String getUserName(){
        return this.userName;
    }
    
    public void setUserName(String userName){
        this.userName = userName;
    }
    
    public List<String> getRoles(){
        return this.roles;
    }
    
    public void setRoles(List<String> roles){
        this.roles = roles;
    }
}
  1. 增加过滤器解析 token 将,信息存入 request 信息中,方便后续调用
// 该过滤器应该在 spring cloud gateway 的全局过滤器之后
@Order(1)
@Component
public class AttributeFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        if (request.getHeader("Authorization") != null){
            JwtModel jwtModel = JwtUtil.getModel(request.getHeader("Authorization").toString());
            servletRequest.setAttribute("id", jwtModel.getId());
            if(jwtModel.getRoles != null){
                servletRequest.setAttribute("roles", jwtModel.getRoles());
            }
        }

        filterChain.doFilter(servletRequest, servletResponse);
    }
}
  1. 然后就是拦截器实现与配置
public class HasRoleInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        HasRole hasRole = handlerMethod.getMethodAnnotation(HasRole.class);
        if(hasRole == null){
            return true;
        }
        String role = hasRole.role();
        if(role == ""){
            return true;
        }
        if(request.getAttribute("roles") == null){
            setRes(response, new CommonResult(403,"暂无角色权限"));
        }
        List<String> roleList = (List<String>) request.getAttribute("roles");
        if(roleList.contains(role)){
            return true;
        } else {
            setRes(response, new CommonResult(403,"暂无角色权限"));
            return false;
        }
    }
    
    // 设置返回信息
    private void setRes(HttpServletResponse response, CommonResult cr){
        ObjectMapper om = new ObjectMapper();
        response.setStatus(HttpStatus.OK.value());
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        try{
            om.writeValue(response.getWriter(), cr);
        } catch (IOException e){
            System.out.print(e.getMessage());
        }
    }
}

添加拦截器配置

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new HasRoleInterceptor());
    }
}
  1. 具体使用
@RestController
@RefreshScope
public class TestController{

    @GetMapping("/roletest")
    @HasRole(role = "admin")
    public CommonResult roleTest(HttpServletRequest request){
        return new CommonResult(200, "SUCCESS", request.getAttribute("id") + " has role");
    }
}

上述方法流程可在具体接口方法层面拦截不具有特定权限的角色的访问,更加细化的实现和利用了 token 的功能。

*注:上面代码中,使用到的未作具体实现的类或方法可以在上篇文章《基于 spring cloud 的简易 JWT 颁发与刷新构建方法》中找到。