让老项目加入你的NACOS微服务框架,并集成权限

前言

        为了让老项目加入新的云服务中,可能要进行一些改造,老项目无需加入Security就可以简单的使用权限系统

准备工作

        首先要拥有一个完整的基于NACOS的微服务框架和一个Springboot老项目

        微服务框架使用了Security,并支持Auth2.0 

        老框架没有任何权限系统,或权限系统已经不起作用

接口权限验证接口

        这里定义一个接口专门用来判断当前登录用户的token是否包含某个权限编码

        这里一般将功能集成到微服务框架的Auth模块中

        

public interface TokenService {

    /**
     * 通过token获取权限列表
     * @param token
     * @return
     */
    Collection<GrantedAuthority> getPermissionByToken(String token);
}
@Service
public class TokenServiceImpl implements TokenService {

    @Autowired
    private TokenStore tokenStore;

    @Override
    public Collection<GrantedAuthority> getPermissionByToken(String token) {
        if (StringUtils.isEmpty(token)) {
            return new ArrayList<>();
        }

        OAuth2Authentication auth =  tokenStore.readAuthentication(token);

        if(!auth.isAuthenticated()){
            return new ArrayList<>();
        }

        return auth.getAuthorities();
    }
}
    /**
     * 判断指定token是否包含某个权限值
     * @param token
     * @return
     */
    @GetMapping("/hasPermissions")
    public AjaxResult getPermissionByToken(@RequestParam("token") String token,@RequestParam("permission") String permission)
    {
        Collection<GrantedAuthority> authorities = tokenService.getPermissionByToken(token);
        if(null==authorities||authorities.isEmpty()){
            return AjaxResult.success(false);
        }

        Boolean result = permissionService.hasPermi(authorities,permission);
        System.out.println(result?"有权限":"无权限");
        return AjaxResult.success(result);
    }

 这里是hasPermi方法,不多做解释

    /**
     * 验证用户是否具备某权限
     *
     * @param permission 权限字符串
     * @return 用户是否具备某权限
     */
    public boolean hasPermi(Collection<GrantedAuthority> authorities, String permission)
    {
        if (StringUtils.isEmpty(permission))
        {
            return false;
        }
        if (null==authorities||authorities.isEmpty())
        {
            return false;
        }
        return hasPermissions(authorities, permission);
    }
    /**
     * 判断是否包含权限
     * 
     * @param authorities 权限列表
     * @param permission 权限字符串
     * @return 用户是否具备某权限
     */
    private boolean hasPermissions(Collection<? extends GrantedAuthority> authorities, String permission)
    {
        return authorities.stream().map(GrantedAuthority::getAuthority).filter(StringUtils::hasText)
                .anyMatch(x -> ALL_PERMISSION.contains(x) || PatternMatchUtils.simpleMatch(permission, x));
    }

 

我们可以通过以下URL判断该接口是否可用,当然,这个接口的访问权限是everyone或All

localhost:8080/auth/token/hasPermissions?token=4657eaca-5458-4a55-aa38-a63c02173611&permission=sysuser:edit

老项采用注解方式

        首先老项目最好加入到Nacos发现服务中,这个只要通过配置就可以实现

        当然你也可以让你的老项目单独运行

        在老项目中写一个注解,这里使用shiro的命名方式 RequiresPermissions 当然 你也可以使用其他的命名,比如hasPer等等,这里的例子支持一个权限,你也可以让他支持OR或者AND等逻辑就更完美了

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermissions {

    String[] value();

    //实在是不想写这些逻辑了
//    Logical logical() default Logical.AND;

}

接下来就是切面逻辑了,通过代码 我们可以看出,该切面先到getParamter中获取token如果没有就到header中获取token,最后通过token到微服务框架中判断权限是否存在,这里使用restTemplat,你也可是使用你喜欢的方式比如openFeign甚至HttpClient

/**
 * 权限判断,切面处理类
 */
@Slf4j
@Aspect
@Component
public class RequiresPermissionsAspect {

    @Autowired
    RestTemplate restTemplate;

    @Pointcut("@annotation(com.fangsheng.common.auth.annotation.RequiresPermissions)")
    public void logPointCut() {

    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        //获取请求和响应
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();

        //获取token
        String token = request.getParameter("token");
        if(StringUtils.isEmpty(token)){
            token = request.getHeader("token");
            if(StringUtils.isEmpty(token)){
                log.error("[UNAUTHORIZED] not auth ");
                WebUtils.printErrorPage(401,"无权限:1",response);
                return null;
            }
        }

        //获取标签信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        RequiresPermissions permissions = method.getAnnotation(RequiresPermissions.class);

        Map<String, Object> mapNode = new HashMap<>(2);
        mapNode.put("token", token);
        mapNode.put("permission",permissions);

        //这里使用restTemplate访问微服务Auth模块我们自己定义的接口
        HashMap result = restTemplate.getForObject("http://fangsheng-auth/token/hasPermissions?token={token}&permission={permission}", HashMap.class, mapNode);
        if (200 != (int) result.get("code")) {
            log.error("[UNAUTHORIZED] not auth ");
            WebUtils.printErrorPage(401,"无权限:2",response);
            return null;
        }

        if((boolean)result.get("data")){
            return joinPoint.proceed();
        }

        WebUtils.printErrorPage(401,"无权限:3",response);
        return null;
    }
}

这里用到一个 printErrorPage方法 贴出来方便大家复制

/**
     * 输出错误页
     * @param code
     * @param msg
     * @param resp
     */
    public static void printErrorPage(int code, String msg, HttpServletResponse resp) {
        try{

            resp.setHeader("content-type", "text/html;charset=utf-8");
            resp.setCharacterEncoding("UTF-8");
            resp.setStatus(code);
            PrintWriter out = resp.getWriter();
            out.write("<meta http-equiv='content-type' content='text/html;charset=UTF-8'/>");
            out.write("<h1>服务器发生错误 code:"+ code +"</h1>");
            out.write("<script>alert('"+msg+"')</script>");
            out.write("<p>"+msg+"</p>");
            out.flush();
            out.close();

        }catch (Exception e){
            resp.setStatus(500);
        }
    }

最后将这个注解使用到接口方法上即可

    @RequiresPermissions("process:processSet:list")

在Acitivi5流程引擎中 传递使用token

        这部分内容在老系统使用activiti5工作流时使用

        首先要找到 ModelSaveRestResource.java 将注解用在saveModel方法上

    @RequiresPermissions("process:processSet:list")
    @RequestMapping(value = "/model/{modelId}/save/{typeId}", method = RequestMethod.PUT)
    @ResponseStatus(value = HttpStatus.OK)
    public R saveModel(@PathVariable String modelId, @PathVariable String typeId

        这时会发现无论有没有权限,都无法保存,因为token并没有传递过来

        这时候我们需要找到相应html页面将token传递

        首先找到 toolbar-default-actions.js 文件中的 $scope.save = function (successCallback) 方法、行数在330行左右

        在这个方法的上面,增加如下方法

        

$scope.getQueryVariable=function(variable){
            var query = window.location.search.substring(1);
            var vars = query.split("&");
            for (var i=0;i<vars.length;i++) {
                var pair = vars[i].split("=");
                if(pair[0] == variable){return pair[1];}
            }
            return(false);
        };

        然后在save方法中增加如下代码,其中包含token 这个typeId如果业务需求不需要可以不用

var typeId = $scope.getQueryVariable("typeId")
var token = $scope.getQueryVariable("token")

        最后修改该方法中的ajax请求 修改如下 (主要是header放了一个token其他不变

            // Update
            $http({
                method: 'PUT',
                data: params,
                ignoreErrors: true,
                headers: {
                    "token":token,
                    'Accept': 'application/json',
                    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
                },
                transformRequest: function (obj) {
                    var str = [];
                    for (var p in obj) {
                        str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
                    }
                    return str.join("&");
                },
                url: KISBPM.URL.putModel(modelMetaData.modelId, typeId)

到这里基本OK了,其他的就是看如何将token传递到这个editor的页面中

在这里举了一个新建流程的传入token的例子

这里注意到使用了我们自己创建的注解,并且把token传递到了对应的modeler.html页面中

/**
     * 新建一个空模型
     *
     * @return
     * @throws UnsupportedEncodingException
     */
    @RequiresPermissions("process:processSet:list")
    @GetMapping("newModel")
    public Object newModel(String typeId, @RequestParam("token") String token) throws UnsupportedEncodingException {

        System.out.println(token);

        // 初始化一个空模型
        Model model = repositoryService.newModel();
        // 设置一些默认信息
        String name = "new-process";
        String description = "";
        int revision = 1;
        String key = "process";
        ObjectNode modelNode = objectMapper.createObjectNode();
        modelNode.put(ModelDataJsonConstants.MODEL_NAME, name);
        modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
        modelNode.put(ModelDataJsonConstants.MODEL_REVISION, revision);
        model.setName(name);
        model.setKey(key);
        model.setMetaInfo(modelNode.toString());
        repositoryService.saveModel(model);
        String id = model.getId();
        // 完善ModelEditorSource
        ObjectNode editorNode = objectMapper.createObjectNode();
        editorNode.put("id", "canvas");
        editorNode.put("resourceId", "canvas");
        ObjectNode stencilSetNode = objectMapper.createObjectNode();
        stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
        editorNode.replace("stencilset", stencilSetNode);
        repositoryService.addModelEditorSource(id, editorNode.toString().getBytes("utf-8"));
        return "redirect:/modeler.html?modelId=" + id + "&token="+ token +"&typeId=" + typeId;
    }

测试时使用这样的URL

http://localhost/dev-api/activiti/models/newModel?typeId=1506534943352815616&token=4657eaca-5458-4a55-aa38-a63c02173611

 

此次分享已经结束,希望能够帮助到大家,感兴趣的小伙伴请点个赞支持下,谢谢

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值