1、部门数据动态过滤
来源renren的动态数据过滤权限实现
1、自定义注解DataFilter
/**
* 数据过滤
*
* @author Mark sunlightcs@gmail.com
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataFilter {
/** 表的别名 */
String tableAlias() default "";
/** true:没有本部门数据权限,也能查询本人数据 */
boolean user() default true;
/** true:拥有子部门数据权限 */
boolean subDept() default false;
/** 部门ID */
String deptId() default "dept_id";
/** 用户ID */
String userId() default "user_id";
}
2、AOP切面处理类DataFilterAspect
/**
* 数据过滤,切面处理类
*
* @author Mark sunlightcs@gmail.com
*/
@Aspect
@Component
public class DataFilterAspect {
@Autowired
private SysDeptService sysDeptService;
@Autowired
private SysUserRoleService sysUserRoleService;
@Autowired
private SysRoleDeptService sysRoleDeptService;
@Pointcut("@annotation(io.renren.common.annotation.DataFilter)")
public void dataFilterCut() {
}
@Before("dataFilterCut()")
public void dataFilter(JoinPoint point) throws Throwable {
Object params = point.getArgs()[0];
if(params != null && params instanceof Map){
SysUserEntity user = ShiroUtils.getUserEntity();
//如果不是超级管理员,则进行数据过滤
if(user.getUserId() != Constant.SUPER_ADMIN){
Map map = (Map)params;
map.put(Constant.SQL_FILTER, getSQLFilter(user, point));
}
return ;
}
throw new RRException("数据权限接口,只能是Map类型参数,且不能为NULL");
}
/**
* 获取数据过滤的SQL
*/
private String getSQLFilter(SysUserEntity user, JoinPoint point){
MethodSignature signature = (MethodSignature) point.getSignature();
DataFilter dataFilter = signature.getMethod().getAnnotation(DataFilter.class);
//获取表的别名
String tableAlias = dataFilter.tableAlias();
if(StringUtils.isNotBlank(tableAlias)){
tableAlias += ".";
}
//部门ID列表
Set<Long> deptIdList = new HashSet<>();
//用户角色对应的部门ID列表
List<Long> roleIdList = sysUserRoleService.queryRoleIdList(user.getUserId());
if(roleIdList.size() > 0){
List<Long> userDeptIdList = sysRoleDeptService.queryDeptIdList(roleIdList.toArray(new Long[roleIdList.size()]));
deptIdList.addAll(userDeptIdList);
}
//用户子部门ID列表
if(dataFilter.subDept()){
List<Long> subDeptIdList = sysDeptService.getSubDeptIdList(user.getDeptId());
deptIdList.addAll(subDeptIdList);
}
StringBuilder sqlFilter = new StringBuilder();
sqlFilter.append(" (");
if(deptIdList.size() > 0){
sqlFilter.append(tableAlias).append(dataFilter.deptId()).append(" in(").append(StringUtils.join(deptIdList, ",")).append(")");
}
//没有本部门数据权限,也能查询本人数据
if(dataFilter.user()){
if(deptIdList.size() > 0){
sqlFilter.append(" or ");
}
sqlFilter.append(tableAlias).append(dataFilter.userId()).append("=").append(user.getUserId());
}
sqlFilter.append(")");
if(sqlFilter.toString().trim().equals("()")){
return null;
}
return sqlFilter.toString();
}
}
原理是返回sqlFilter sql的过滤条件
3、业务类方法添加注解DataFilter使用
/**
* 系统用户
*
* @author Mark sunlightcs@gmail.com
*/
@Service("sysUserService")
public class SysUserServiceImpl extends ServiceImpl<SysUserDao, SysUserEntity> implements SysUserService {
@Override
@DataFilter(subDept = true, user = false)
public PageUtils queryPage(Map<String, Object> params) {
String username = (String)params.get("username");
IPage<SysUserEntity> page = this.page(
new Query<SysUserEntity>().getPage(params),
new QueryWrapper<SysUserEntity>()
.like(StringUtils.isNotBlank(username),"username", username)
.apply(params.get(Constant.SQL_FILTER) != null, (String)params.get(Constant.SQL_FILTER))
);
for(SysUserEntity sysUserEntity : page.getRecords()){
SysDeptEntity sysDeptEntity = sysDeptService.getById(sysUserEntity.getDeptId());
sysUserEntity.setDeptName(sysDeptEntity.getName());
}
return new PageUtils(page);
}
}
2、URI访问请求权限控制
看了renren的权限认证使用了shiro的@RequiresPermissions注解,必须具有指定权限才能进入cntroller控制器的请求方法
/**
* 数据字典
*
* @author Mark sunlightcs@gmail.com
*/
@RestController
@RequestMapping("sys/dict")
public class SysDictController {
@Autowired
private SysDictService sysDictService;
/**
* 列表
*/
@RequestMapping("/list")
@RequiresPermissions("sys:dict:list")
public R list(@RequestParam Map<String, Object> params){
PageUtils page = sysDictService.queryPage(params);
return R.ok().put("page", page);
}
/**
* 信息
*/
@RequestMapping("/info/{id}")
@RequiresPermissions("sys:dict:info")
public R info(@PathVariable("id") Long id){
SysDictEntity dict = sysDictService.getById(id);
return R.ok().put("dict", dict);
}
/**
* 保存
*/
@RequestMapping("/save")
@RequiresPermissions("sys:dict:save")
public R save(@RequestBody SysDictEntity dict){
//校验类型
ValidatorUtils.validateEntity(dict);
sysDictService.save(dict);
return R.ok();
}
/**
* 修改
*/
@RequestMapping("/update")
@RequiresPermissions("sys:dict:update")
public R update(@RequestBody SysDictEntity dict){
//校验类型
ValidatorUtils.validateEntity(dict);
sysDictService.updateById(dict);
return R.ok();
}
/**
* 删除
*/
@RequestMapping("/delete")
@RequiresPermissions("sys:dict:delete")
public R delete(@RequestBody Long[] ids){
sysDictService.removeByIds(Arrays.asList(ids));
return R.ok();
}
}
权限标识字符串sys:dict:delete
是我们可以通过前端界面配置到菜单表sys_menu
org.apache.shiro.authz.aop.PermissionAnnotationHandler类对@RequiresPermissions注解进行了权限拦截处理,主要有两个方法getAnnotationValue()和assertAuthorized()
renren做ShiroConfig配置类时,指定了权限认证实现类
@Configuration
public class ShiroConfig {
@Bean("securityManager")
public SecurityManager securityManager(UserRealm userRealm, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
securityManager.setSessionManager(sessionManager);
securityManager.setRememberMeManager(null);
return securityManager;
}
}
UserRealm就是权限认证的实现类,它继承了org.apache.shiro.realm.AuthorizingRealm;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.renren.common.utils.Constant;
import io.renren.modules.sys.dao.SysMenuDao;
import io.renren.modules.sys.dao.SysUserDao;
import io.renren.modules.sys.entity.SysMenuEntity;
import io.renren.modules.sys.entity.SysUserEntity;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 认证
*
* @author Mark sunlightcs@gmail.com
*/
@Component
public class UserRealm extends AuthorizingRealm {
@Autowired
private SysUserDao sysUserDao;
@Autowired
private SysMenuDao sysMenuDao;
/**
* 授权(验证权限时调用)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SysUserEntity user = (SysUserEntity)principals.getPrimaryPrincipal();
Long userId = user.getUserId();
List<String> permsList;
//系统管理员,拥有最高权限
if(userId == Constant.SUPER_ADMIN){
List<SysMenuEntity> menuList = sysMenuDao.selectList(null);
permsList = new ArrayList<>(menuList.size());
for(SysMenuEntity menu : menuList){
permsList.add(menu.getPerms());
}
}else{
permsList = sysUserDao.queryAllPerms(userId);
}
//用户权限列表
Set<String> permsSet = new HashSet<>();
for(String perms : permsList){
if(StringUtils.isBlank(perms)){
continue;
}
permsSet.addAll(Arrays.asList(perms.trim().split(",")));
}
// 权限列表放入到认证对象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permsSet);
return info;
}
/**
* 认证(登录时调用)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authcToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken)authcToken;
//查询用户信息
SysUserEntity user = sysUserDao.selectOne(new QueryWrapper<SysUserEntity>().eq("username", token.getUsername()));
//账号不存在
if(user == null) {
throw new UnknownAccountException("账号或密码不正确");
}
//账号锁定
if(user.getStatus() == 0){
throw new LockedAccountException("账号已被锁定,请联系管理员");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName());
return info;
}
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();
shaCredentialsMatcher.setHashAlgorithmName(ShiroUtils.hashAlgorithmName);
shaCredentialsMatcher.setHashIterations(ShiroUtils.hashIterations);
super.setCredentialsMatcher(shaCredentialsMatcher);
}
}
两个核心接口AuthorizationInfo管理授权和AuthenticationInfo管理认证
ruoyi的请求方法权限管理
看了ruoyi-cloud的@RequiresPermissions注解,它自定义的注解,是怎么实现请求方法的权限控制的
package com.ruoyi.common.security.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 权限认证:必须具有指定权限才能进入该方法
*
* @author ruoyi
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface RequiresPermissions
{
/**
* 需要校验的权限码
*/
String[] value() default {};
/**
* 验证模式:AND | OR,默认AND
*/
Logical logical() default Logical.AND;
}
请求方法添加@RequiresPermissons注解
/**
* 数据字典信息
*
* @author ruoyi
*/
@RestController
@RequestMapping("/dict/data")
public class SysDictDataController extends BaseController
{
@Autowired
private ISysDictDataService dictDataService;
@Autowired
private ISysDictTypeService dictTypeService;
@RequiresPermissions("system:dict:list")
@GetMapping("/list")
public TableDataInfo list(SysDictData dictData)
{
startPage();
List<SysDictData> list = dictDataService.selectDictDataList(dictData);
return getDataTable(list);
}
@Log(title = "字典数据", businessType = BusinessType.EXPORT)
@RequiresPermissions("system:dict:export")
@PostMapping("/export")
public void export(HttpServletResponse response, SysDictData dictData) throws IOException
{
List<SysDictData> list = dictDataService.selectDictDataList(dictData);
ExcelUtil<SysDictData> util = new ExcelUtil<SysDictData>(SysDictData.class);
util.exportExcel(response, list, "字典数据");
}
}
它大概的实现流程是怎样的