springboot+vue-element-admin权限控制

·  阅读 1767

概述

后台通过自定义注解以及过滤器实现权限控制,前端是通过指令的方式控制按钮是否有权限

springboot

设计流程

自定义注解

controller的method上如果有自定义注解,则按照注解规则判断是否有权限,反之默认该url是可匿名访问的

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

    //是否允许匿名访问,即无需登录就可访问
    boolean permitAll() default false;

    /**
     * 标注一个方法需要显式的授权后才能访问,即需要在权限系统的配置后
     * false:登录后即可访问
     * true:需要配置角色才能访问
     */
    boolean requireAuthority() default false;

}
复制代码

过滤器

  • 读取权限接口(IAuthDataService)可以匿名访问的url(支持正则),如果有则不进行权限验证
  • 读取系统中所有controller类的方法权限注解,如果没有则不进行权限验证
  • 如果有权限注解,则判断当前是否登陆 > 判断当前用户登陆权限
/**
 * 过滤器:校验接口访问权限
 */
@Component
public class AuthorizationFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(AuthorizationFilter.class);
    @Autowired
    ApplicationContext applicationContext;
    // 各个微服务需实现的接口,主要是提供用户数据(是否登陆以及权限信息)
    @Autowired
    IAuthDataService iAuthDataService;
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 将请求转换成HttpServletRequest 请求
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletResponse rsp = (HttpServletResponse) servletResponse;
        boolean isAuthUrl = false;// true 该链接有权限    false:该链接无权限

        // 取得接口URI 并将 path类型的链接替换成{}  eg:/user/delete/1 替换成 /user/delete/{}
        String currentURI = req.getRequestURI().replaceAll("/[0-9]+", "/{}");

        // 读取系统内所有controller请求映射关系
        Map<String, HandlerMethod> mapRet = getMapRet();
        //获取controller 方法
        HandlerMethod  method =  mapRet.get(currentURI);

        // 如果该链接不在该系统范畴内则默认可匿名访问
        if (method == null) {
            isAuthUrl = true;
        }

        /**
         * 获取可匿名访问的链接 eg:swagger等
         */
        if (!isAuthUrl) {
            List<String> permitAllUrls = iAuthDataService.permitAllUrls();
            if (Collections3.isNotEmpty(permitAllUrls)) {
                for (String url : permitAllUrls) {
                    if (currentURI.matches(url)) {//正则匹配该url
                        isAuthUrl = true;
                        break;
                    }
                }
            }
        }
        // 判断mothed 注解的权限配置 以及  该用户菜单权限
        if (!isAuthUrl) {
            String erroeMsg = "";
            Security security = mapRet.get(currentURI).getMethodAnnotation(Security.class);
            boolean permitAll = false;
            boolean requireAuthority = false; //必须配置权限才能访问
            if (security != null) {
                permitAll = security.permitAll();
                requireAuthority = security.requireAuthority();
                if (permitAll) { //可匿名访问
                    isAuthUrl = true;
                }
            }else{
                isAuthUrl = true;
            }
            UserData userData = iAuthDataService.getUserData();
            Boolean isLogin = userData.getIsLogin();//是否登陆
            if (!isAuthUrl && !isLogin) { //如果没有登陆
                erroeMsg = "您当前未登录,请登录";
            }
            if (!isAuthUrl && isLogin) { //登陆状态
                if (requireAuthority) {//必须授权才能访问
                    erroeMsg = "当前用户没有权限,需先授权";
                    List<String> apiUrls = userData.getUrls();
                    if (Collections3.isNotEmpty(apiUrls)) {
                        for (String url : apiUrls) {
                            if (currentURI.indexOf(url) >= 0) {
                                isAuthUrl = true;
                                break;
                            }
                        }
                    }

                } else {// 只需要登陆即可访问
                    isAuthUrl = true;
                }

            }
            if (!isAuthUrl) {
                rsp.setCharacterEncoding("UTF-8");
                rsp.setContentType("application/json; charset=utf-8");
                rsp.getWriter().write(JSON.toJSONString(ResponseVoUtil.failResult(erroeMsg)));
            }

        }
        if (isAuthUrl) {
            filterChain.doFilter(servletRequest, servletResponse);
        }
    }

    @Override
    public void destroy() {

    }

    /**
     * 获取当前微服务所有api
     * 如果请求类型是path则替换成{} eg: delete/{id} 替换成 delete/{}
     * @return
     */
    public Map<String, HandlerMethod> getMapRet() {
        AbstractHandlerMethodMapping<RequestMappingInfo> objHandlerMethodMapping = (AbstractHandlerMethodMapping<RequestMappingInfo>) applicationContext
                .getBean("requestMappingHandlerMapping");
        Map<RequestMappingInfo, HandlerMethod> mapRet = objHandlerMethodMapping.getHandlerMethods();
        Map<String, HandlerMethod> methodMap = new HashMap<>();
        for (RequestMappingInfo mapping : mapRet.keySet()) {
            Set<String> paths =  mapping.getPatternsCondition().getPatterns();
            for(String path:paths){
                path = path.replaceAll("/\\{[^\\{|^\\}]*\\}","/{}");
                methodMap.put(path, mapRet.get(mapping));
            }
        }
        return methodMap;
    }
}

复制代码

权限接口

接口

各个微服务实现该接口,通过用户相关信息(是否登陆,登陆权限)


/**
 * @author lichangchao
 * 权限过滤需要的相关数据
 */
public interface IAuthDataService {
    /**
     * 获取用户登陆信息
     *
     * @return
     */
    UserData getUserData();

    /**
     * 允许哪些url可匿名访问,正则匹配
     *
     * @return
     */
    List<String> permitAllUrls();
}

复制代码

bean

@Data
public class UserData {
    // 用户配置的api url地址
    List<String> urls;
    // 是否登陆
    Boolean isLogin;
}

复制代码

vue-element-admin

  • 后台根据当前用户的菜单,转化成路由
  • 自定义指定,获取路由上面的按钮权限编码

动态路由

import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
import Layout from '@/layout'
const _import = require('./_import_' + process.env.NODE_ENV)

// import { asyncRoutes } from '@/router'

NProgress.configure({ showSpinner: false }) // NProgress Configuration

const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist

router.beforeEach(async(to, from, next) => {
  // start progress bar
  NProgress.start()

  // set page title
  document.title = getPageTitle(to.meta.title)

  // determine whether the user has logged in
  const hasToken = getToken()

  if (hasToken) {
    if (to.path === '/login') {
      // if is logged in, redirect to the home page
      next({ path: '/' })
      NProgress.done()
    } else {
      // determine whether the user has obtained his permission roles through getInfo
      const hasRoles = store.getters.name && store.getters.name.length > 0
      if (hasRoles) {
        next()
      } else {
        try {
          // get user info

          const { menus } = await store.dispatch('user/getInfo')

          const asyncRoutes = routerGo(to, next, menus)
          // generate accessible routes map based on roles
          // const accessRoutes1 = await store.dispatch('permission/generateRoutes', roles)
          const accessRoutes = await store.dispatch('permission/addAsyncRoutes', asyncRoutes)

          // dynamically add accessible routes
          router.addRoutes(accessRoutes)

          // hack method to ensure that addRoutes is complete
          // set the replace: true, so the navigation will not leave a history record
          next({ ...to, replace: true })
        } catch (error) {
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    /* has no token*/
    if (whiteList.indexOf(to.path) !== -1) {
      // in the free login whitelist, go directly
      next()
    } else {
      // other pages that do not have permission to access are redirected to the login page.
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  // finish progress bar
  NProgress.done()
})
function routerGo(to, next, getRouter) {
  getRouter = filterAsyncRouter(getRouter) // 过滤路由

  getRouter.map(v => { router.options.routes.push(v) })// 将接口获取到的路由push到现有路由中
  router.addRoutes(getRouter)// 动态添加路由
  global.antRouter = router.options.routes // 将路由数据传递给全局变量,做侧边栏菜单渲染工作
  next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace:
}

function filterAsyncRouter(asyncRouterMap = []) { // 遍历后台传来的路由字符串,转换为组件对象
  const accessedRouters = asyncRouterMap.filter(route => {
    if (route.component) {
      if (route.component === 'Layout') { // Layout组件特殊处理
        route.component = Layout
      } else {
        console.log(route.component)
        route.component = _import(`${route.component}`)
      }
    }
    if (route.children && route.children.length) {
      route.children = filterAsyncRouter(route.children)
    } else {
      delete route.children
    }
    return true
  })

  return accessedRouters
}


复制代码

指令

export default {
  inserted(el, binding, vnode) {
    const { value } = binding
    const btnAuths = vnode.context.$route.meta.btnAuths
    if (value && value instanceof Array && value.length > 0) {
      const permissionRoles = value

      const hasPermission = btnAuths.some(role => {
        return permissionRoles.includes(role)
      })

      if (!hasPermission) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    } else {
      throw new Error(`need roles! Like v-permission="['admin','editor']"`)
    }
  }
}

// 页面用法 eg
 <el-button v-permission="['delete']" type="text" size="small" @click="deleteRole(scope.row)">删除</el-button>

复制代码
分类:
后端
标签:
分类:
后端
标签: