RBAC项目总结
基于角色访问控制(RBAC:Role Based Access Control)
对于基本的增删改查
1.通过看接口文档要求这个接口使用什么方式发送请求,需要响应的数据的格式是什么
2.若请求的参数由其他对象或者数组组成的,就需要重新定义一个类来进行接收,后端接收的时候使用**@RequestBody**注解将传进来的数据进行封装
3.若相应的参数也是由对象或者数组组成的,也使用重新创建类的方式进行响应
4.若前端只传进来的一个id,后端使用**/{id}**接收
5.将增删改查的逻辑代码写在service里面,service在调用mapper中的方法
6.若存在关联表的增删改操作,则在service里面同时对两张表进行操作,若不进行删除就会出现很多脏数据,此时可以使用事务保证操作能同时进行,删除的时候先删除外键,再删除主表的字段,否则数据库会有异常
7.在mapper文件中如果需要传递多个参数给xml文件,需要使用**@Param**注解保证唯一性
8.mapper传进来的是一个对象的话,直接使用对象的属性获取,不可以使用对象.属性获取
9.当传过来的数据类型是数组/集合类型时,使用进行遍历
<!--修改员工角色关联,不可用-->
<update id="updateEmployeeRole">
update employee_role set
<foreach collection="roleIds" item="roleId" separator=",">
(#{employee_id},#{roleId})
</foreach>
where employee_id= #{employee_id}
</update>
10.需要进行模糊查询或者过滤查询的时候使用where/if等动态sql
11.执行一对多查询的时候,使用association标签,执行多对一查询的时候用collection标签
分页查询
使用插件做分页,下载源码修改PageInfo,将不需要的属性使用**@JsonIgnore**注解让他不生效
PageHelper.startPage(qo.getPageNum(), qo.getPageSize());
List<Employee> list = mapper.selectForList(qo);
将上一段贴在哪个方法就对那个进行分页
mysql使用foreach标签的弊端
里面权限分配的时候遍历使用了mysql自己的标签,若更换数据库就无法使用这个遍历的方法,但是效率较高
在业务层进行遍历就可以不用考虑数据库问题,但是效率较低
拦截器的设计
只有登录和获取验证码的路径是不用拦截的,其他路径都进行拦截
编写拦截器
实现HandlerInterceptor接口,要贴Controller注解
可以多次使用registry.addInterceptor()添加过滤的条件
注意事项
当用户没有进行登录的时候就进行其他操作是会进行拦截的
现在的过滤的条件有用户是否进行了登录和用户是否有这个权限访问这个资源
此时通过id进行用户权限查询是没有的,值为null,此时进行查询的时候就会报numberFormatException
可以使用if来判断是否为null,为null就直接返回错误信息
注册/配置,这样才能生效
实现WebMvcConfigurer接口,要贴configuration注解
指向拦截的路径:addPathPatterns("/")**
排除的拦截路径:.excludePathPatterns(“/api/login”, “/api/code”);
接口统一异常处理
避免用户看到丑陋的报错页面
单独使用一个类进行统一异常处理
@ControllerAdvice
public class CommonControllerAdvice {
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public JsonResult handler(RuntimeException e){
e.printStackTrace();
return JsonResult.fail(500, e.getMessage());
}
@ControllerAdvice:Controller的增强
可以使用断言的方式将异常抛出,在这里接收异常,将异常以Json的格式
怎样进行权限的加载
将所有的权限保存到数据库中
描述每个接口需要哪些权限(注解)
注解:元数据,用于来描述程序元素(类,方法,字段,形参…)的数据
现目前没有现成的注解可以使用,就自己定义注解
// 该注解能贴在哪
@Target(ElementType.METHOD)
// 该注解能保存到什么时期
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiredPermission {
String name();
String expression();
}
在接口方法上贴上该注解,描述该方法需要什么权限才能访问
@RequiredPermission(name="员工添加和修改",expression="employee:saveOrUpdate")
从spring中找容器对象
@Autowired
private ApplicationContext ctx; // 容器对象
Map<String, Object> beansWithAnnotation = ctx.getBeansWithAnnotation(RestController.class);
找到Controller对象中所有的方法
Collection<Object> controllers = beansWithAnnotation.values();
遍历所有的方法,扫描所有Contrller中的@RequiredPermission注解
Method[] methods = controller.getClass().getDeclaredMethods();
for (Method method : methods) {
if(method.isAnnotationPresent(RequiredPermission.class)) {
如果有的话就获取注解中传递的参数
RequiredPermission rp = method.getAnnotation(RequiredPermission.class);
String name = rp.name();
String expression = rp.expression()
查询所有的权限表达式,判断现在的权限表达式是否包含在集合对象中
List<Permission> permissions = mapper.selectAll();
如果不存在的话,就将参数封装到permission对象中,并保存到数据库中
Permission p = new Permission(null, name, expression);
if(!permissions.contains(p)) {
mapper.insert(p);
}
默认比较的是地址,通过重写permission实体类中的equs和hashcode进行值的比较,不需要比较id就将id删除,
否则会在数据库中添加重复的数据
登录和验证码
1.使用工具类将code,uuid,code取到,并将code使用uuid作为key存储到redis中
2.可以将工具类里面获取的数据直接使用map集合的方式转成json格式发送给前端
3.验证码校验的时候可以使用equalsIgnoreCase方法来不区分大小写
4.前端将账户密码和验证码传到后端之后,将redis中的code和前端的code进行值比较
5.将账户密码拿到数据库中进行比较,如果能查到这个对象,就将这个对象以json格式存储到redis中
6.通过查询的员工数据,将里面的id取到,通过用户的id对三张表进行联查,查询用户所拥有的权限,将权限转成json存储到redis中
做注销
通过前端传进来的userId使用"req.getHeader(“userId”)"获取,使用该id将redis中的整个用户的数据删除
拦截器拦截的时候因为检查不到redis中有用户的数据就会退出登录
权限认证,拦截器中
用户发送一个请求的时候会访问一个资源,若这个资源的方法上面没有贴权限的注解或者该用户是管理员就直接放行
1.从redis中通过传进来的userId拿到整个用户的JSON,并转换成对象
2.判断是超管就直接放行
3.使用handler获取当前访问资源的权限,若方法上面没有权限的注解就直接放行
if(!method.isAnnotationPresent(RequiredPermission.class)) {
return true;
}
4.有权限的话就获取Controller方法上的权限的表达式
RequiredPermission rp = method.getAnnotation(RequiredPermission.class);
String expression = rp.expression();
5.从redis中通过userId拿到这个用户所有的权限JSON字符串,并将其转换成集合的形式,将这个请求的权限表达式和集合中数据进行比较,若存在就放行
String expressionsJson = redisUtils.get("EMPLOYEE:PERMISSION:" + userId);
List<String> expressions = JSON.parseObject(expressionsJson, ArrayList.class);
if(expressions.contains(expression)) {
return true; }
6.若不存在就进行拦截,并使用response.getWriter.print(“JSON字符串”)将错误信息进行响应
跨域问题
虽然配置了代理的请求方式,此时只能进行登录操作,不能对里面的数据进行操作
出现的原因就是:浏览器会发送一个预请求,看服务器是否支持跨域,若支持就发送真实数据,但是不会携带userId
出现预请求的时候就直接放行,使用全局配置,注意导包的时候使用Spring的
1.使用注解:@CrossOrigin(allowCredentials = “true”)
只是在每个接口上面都必须贴注解,操作非常的麻烦
2.使用全局配置
值需要写一个类就能满足需求
@Configuration
public class SpringMvcConfiguration implements WebMvcConfigurer {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 设置访问源地址
config.addAllowedOrigin("*");
// 设置访问源请求头
config.addAllowedHeader("*");
// 设置访问源请求方法
config.addAllowedMethod("*");
// 有效期 1800秒
config.setMaxAge(1800L);
// 添加映射路径,拦截一切请求
UrlBasedCorsConfigurationSource source = new
UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
// 返回新的CorsFilter
return new CorsFilter(source);
}
0秒
config.setMaxAge(1800L);
// 添加映射路径,拦截一切请求
UrlBasedCorsConfigurationSource source = new
UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
// 返回新的CorsFilter
return new CorsFilter(source);
}