前言
为了让老项目加入新的云服务中,可能要进行一些改造,老项目无需加入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
此次分享已经结束,希望能够帮助到大家,感兴趣的小伙伴请点个赞支持下,谢谢