框架,并集成权限"/>
让老项目加入你的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 {@Autowiredprivate TokenStore tokenStore;@Overridepublic 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 {@AutowiredRestTemplate restTemplate;@Pointcut("@annotation(com.fangshengmon.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();//获取tokenString 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();// 完善ModelEditorSourceObjectNode editorNode = objectMapper.createObjectNode();editorNode.put("id", "canvas");editorNode.put("resourceId", "canvas");ObjectNode stencilSetNode = objectMapper.createObjectNode();stencilSetNode.put("namespace", ".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
此次分享已经结束,希望能够帮助到大家,感兴趣的小伙伴请点个赞支持下,谢谢
更多推荐
让老项目加入你的NACOS微服务框架,并集成权限
发布评论