SpringBoot>15 - 自定义注解实现权限控制
简介:
较原始的项目中使用的是JSP页面标签结合后台方法上注解配合实现权限控制,目前前后端完全分离开发已经是主流模式,大型的项目中可能会用到 shiro 或者 spring security 做安全校验,但是在小型项目中可以使用自定义注解达到权限控制的要求。
误区:
很多人认为权限控制就是控制不同用户在页面上拥有不同的功能(即不同的按钮),这是用户体验范围,这种情况下,只要知道后端地址,还是可以请求到后端数据的。权限控制做的是没有权限就不可以操作数据,即安全问题。
数据库表设计:
1、用户表user,存放不同的用户
2、角色表role,不同的角色(管理员、普通用户…)
3、用户角色表user_role,存放用户和角色之间的关系
4、权限表permission、存放不同的权限(用户的增删改查)
5、角色权限表role_permission,存放不同角色和权限之间的关系
省略:使用插件生成对应的实体类、mapper、mapper.xml。
对应表的维护
controller service层的建立
1、用户表、代码很简单的CRUD操作,省略。
2、角色表的维护。
/**
* @Auther: xf
* @Date: 2018/11/24 21:35
* @Description: 角色表的维护
*/
@RequestMapping(value = "role")
@RestController
public class RoleController {
@Autowired
private RoleService roleService;
@GetMapping(value={"list"})
public ApiResult list() {
List<Role> roleList = roleService.list();
return ApiResult.ok(roleList);
}
@PostMapping(value="save")
public ApiResult saver(@RequestBody Role role) {
int result = roleService.save(role);
return ApiResult.ok(result);
}
@GetMapping(value="get/{id}")
public ApiResult getUser(@PathVariable Integer id) {
Role role = roleService.get(id);
return ApiResult.ok(role);
}
@PutMapping(value="update")
public ApiResult putUser(@RequestBody Role role) {
int result = roleService.update(role);
return ApiResult.ok(result);
}
@DeleteMapping(value="delete/{id}")
public ApiResult delete(@PathVariable Integer id) {
int result = roleService.delete(id);
return ApiResult.ok(result);
}
}
3、权限表,这张表的维护很多项目都是开发人员直接维护,不暴露给用户。
4、用户角色表维护,CRUD,代码省略。
5、权限角色表维护
5.1、新建PermissionTree 实体 和 RolePermissionVo 实体
/**
* @Auther: xf
* @Date: 2018/11/24 22:13
* @Description: 权限树
*/
@Data
public class PermissionTree extends Permission{
private Boolean checked = false; // 回显是否被选中
private List<PermissionTree> children = Lists.newArrayList(); // 子集
}
/**
* @Auther: xf
* @Date: 2018/11/24 22:25
* @Description: 修改角色-权限的时候使用
*/
@Data
public class RolePermissionVo {
private String roleId;
List<RolePermission> rolePermissionList = Lists.newArrayList();
}
5.2、一般将权限查询成一棵树,权限角色用于回显该角色拥有哪些权限。
/**
* @Auther: xf
* @Date: 2018/11/24 21:49
* @Description: 权限
*/
@RestController
@RequestMapping(value = "permission")
public class PermissionController {
@Autowired
private PermissionService permissionService;
/**
* 权限树
* @param roleId 角色id 用户查询回显信息
* @return
*/
@GetMapping(value = "getPermissionTree/{roleId}")
public ApiResult getPermissionTree(@PathVariable Integer roleId) {
List<PermissionTree> list = permissionService.getPermissionTree();
return ApiResult.ok(list);
}
/**
* 修改 角色--权限
* @param rolePermissionVo
* @return
*/
@PostMapping(value = "updatePermission}")
public ApiResult updatePermission(@RequestBody RolePermissionVo rolePermissionVo) {
permissionService.updatePermission(rolePermissionVo);
return ApiResult.ok();
}
}
5.3、PermissionService层逻辑
/**
* @Auther: xf
* @Date: 2018/11/24 21:59
* @Description: 权限树
*/
@Service
public class PermissionServiceImpl implements PermissionService {
@Autowired
private PermissionMapper permissionMapper;
@Override
public List<PermissionTree> getPermissionTree(Integer roleId) {
// 存放查询结果
List<PermissionTree> permissionList = Lists.newArrayList();
// 查询所有的权限
List<PermissionTree> permissions = permissionMapper.getAll();
// role_permission 中选中的
List<Integer> permissionIds = permissionMapper.getPermissionIds(roleId);
// 中转容器 map 的索引查询可以提高效率
Map<Integer, PermissionTree> permissionMap = new HashMap<>();
for (PermissionTree p : permissions) {
// 判断有没有选中
if(permissionIds.contains(p.getId())){
p.setChecked(true);
}
permissionMap.put(p.getId(), p);
}
for (PermissionTree p : permissions) {
PermissionTree child = p;
if(p.getPid() == 0){
// 根节点
permissionList.add(p);
}else{
PermissionTree parent = permissionMap.get(child.getPid());
parent.getChildren().add(child);
}
}
return permissionList;
}
// 事物处理
@Transactional(rollbackFor = Exception.class)
@Override
public void updatePermission(RolePermissionVo rolePermissionVo) {
// 1、根据roleId 删除所有的权限
Integer roleId = rolePermissionVo.getRoleId();
permissionMapper.deleteRolePermissionByRoleId(roleId);
// 2. 新增
List<RolePermission> rolePermissionList = rolePermissionVo.getRolePermissionList();
if(rolePermissionList.size() > 0){
for (RolePermission rolePermission : rolePermissionList) {
// 单条更新
permissionMapper.insertRolePermission(rolePermission);
}
}
}
}
注意:权限树的查询方法,可以使用MyBatis自带的递归查询、java代码的递归查询、嵌套for循环等等,本章中使用的方法效率优于以上几种,推荐使用。
5.4、测试权限树
permission表 和 role_permission表如下:
注意: permission表最顶级权限的 pid 一定要是 0 在service 层中根据是否为 0 判断父级。
此处对应的权限为:roleId 为1 的角色拥有用户的全部权限和产品查询的权限。
请求接口:localhost:8080/permission/getPermissionTree/1
部分截图:
至此,以上配置,前端拿到json数据就可以根据不同的角色进行页面显示了,不过这并没有达到真正的权限控制,只是属于用户体验。
自定义注解验证权限:
1、新建注解类
/**
* @Auther: xf
* @Date: 2018/11/24 23:24
* @Description: 自定义注解
*/
// 标注这个类它可以标注的位置
@Target({ElementType.METHOD, ElementType.TYPE})
// 标注这个注解的注解保留时期
@Retention(RetentionPolicy.RUNTIME)
// 是否生成注解文档
@Documented
public @interface RequiredPermission {
String value();
}
2、新建权限拦截器
/**
* @Auther: xf
* @Date: 2018/11/24 23:26
* @Description: 权限验证拦截器
*/
@Component
public class AuthenticationInterceptor extends HandlerInterceptorAdapter {
@Autowired
private UserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 防止 userService 注入不进来
if (null == userService) {
BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
userService = (UserService) factory.getBean("userService");
}
// 验证权限
if (this.hasPermission(handler)) {
return true;
}
// null == request.getHeader("x-requested-with") TODO 暂时用这个来判断是否为ajax请求
// 如果没有权限 则抛403异常 springboot会处理,跳转到 /error/403 页面
response.sendError(HttpStatus.FORBIDDEN.value(), "无权限");
return false;
}
/**
* 是否有权限
*/
private boolean hasPermission(Object handler) {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取方法上的注解
RequiredPermission requiredPermission = handlerMethod.getMethod().getAnnotation(RequiredPermission.class);
// 如果方法上的注解为空 则获取类的注解
if (requiredPermission == null) {
requiredPermission = handlerMethod.getMethod().getDeclaringClass().getAnnotation(RequiredPermission.class);
}
// 如果注解为null, 说明不需要拦截, 直接放过
if (requiredPermission == null) {
return true;
}
// 如果标记了注解,则判断权限
if (StringUtils.isNotBlank(requiredPermission.value())) {
// 应该到 redis 或数据库 中获取该用户的权限信息 并判断是否有权限
//Set<String> permissionSet = userService.getPermissionSet();
// 这里测试使用 直接add
Set<String> permissionSet = new HashSet<>();
permissionSet.add("user:view");
permissionSet.add("user:save");
if (CollectionUtils.isEmpty(permissionSet) ){
return false;
}
return permissionSet.contains(requiredPermission.value());
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
3、修改UserController,用户列表 和 删除的方法上加上自定义的注解
public class UserController {
// 查询用户列表权限
@RequiredPermission("user:view")
@ApiOperation(value="获取用户列表", notes="")
@RequestMapping(value={"getUserList"}, method= RequestMethod.GET)
public ApiResult getUserList() {
List<User> userList = userService.getAllUser();
return ApiResult.ok(userList);
}
// 删除用户的权限
@RequiredPermission("user:delete")
@ApiOperation(value="删除用户", notes="根据url的id来指定删除对象")
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long")
@RequestMapping(value="delete/{id}", method=RequestMethod.DELETE)
public ApiResult deleteUser(@PathVariable Integer id) {
int result = userService.deleteUser(id);
return ApiResult.ok(result);
}
}
分析: 该用户只有“user:view” 和 “user:save” 的权限,所以该用户不可以删除数据(需要"user:delete"权限)。
4、测试:
4.1、用户列表 :localhost:8080/user/getUserList 正常返回数据。
4.2、删除用户:localhost:8080/user/delete/1 抛出异常,没有权限。
至此,服务端权限配置完成。
联系我:
QQ:1421925880
相关springboot、springcloud、docker等文章关注微信公众号: