三、项目设计
4.4 后台权限管理
首先要知道,权限分为几大类:
- 按钮
前端通过判断权限动态加载按钮 - 菜单
前端根据权限的不同,对隐藏与现实进行一定的控制 - 接口api
我们无论在前端如何设置属性,都可以从html代码中将某些受到权限管理的api找到并且执行,这是不安全的,所以就要在后端利用权限验证判断是否可以有足够的权限访问api
思路:从上文可知,我们需要在设置权限的时候对权限的种类进行判断,所以就要在数据库加上相应字段,如1 2 3分别对应三种权限,但是在设计return 的json的时候,我们需要把三种权限对应的结果一并写入进去,存入一个map,通过type对应的值,取出对应权限的结果。而判断这个人权限对应的type的方法,可以用一种编码来判断。
这是用powerDesigner画出的数据库逻辑图,我们首先要创建出菜单表,页面元素表,API接口表,总体权限表和用户表与角色表的实体类,两张关联表可以写在实体类中用@JoinTable来关联两张表(用户-角色,角色-总体权限)
设置关联表的目的就不用多说了,我们要删或者添加一个人的时候,所增加的信息都是成套的。
这样就完成了权限管理中Entity的设计,然后是对应的dao,service与controller,dao也是写出菜单表,页面元素表,API接口表,总体权限表和用户表与角色表的实体类的,而controller只需要写出总体权限表,用户与角色即可。
思路:
-
实现功能在基本的增删改查上增加了权限分配,角色分配,角色与权限绑定的过程。其余的实现方法可参照之前。
-
权限分配:我们将功能实现在Permission的系列中。
Bean to Map
:不管是角色,权限,还是使用者都会有一定的json信息,创建一个Bean to Map 的类,将对象以Map的形式进行转储,然后提取API,按钮,菜单对应的信息是我们这个类的最终目的。
public class BeanMapUtils {
/**
* 将对象属性转化为map结合
*/
public static <T> Map<String, Object> beanToMap(T bean) {
Map<String, Object> map = new HashMap<>();
if (bean != null) {
BeanMap beanMap = BeanMap.create(bean);
for (Object key : beanMap.keySet()) {
map.put(key+"", beanMap.get(key));
}
}
return map;
}
/**
* 将map集合中的数据转化为指定对象的同名属性中
*/
public static <T> T mapToBean(Map<String, Object> map,Class<T> clazz) throws Exception {
T bean = clazz.newInstance();
BeanMap beanMap = BeanMap.create(bean);
beanMap.putAll(map);
return bean;
}
}
保存权限(save)
:首先我们知道权限分为三种,API,按钮,菜单,当用户对着三者发送Request的时候,使用我们的BeanToMap进行转储。再通过传过来的type进行判断是哪种权限,对应的将信息去进行保存即可。
public void save(Map<String,Object> map) throws Exception {
//设置主键的值
String id = idWorker.nextId()+"";
//1.通过map构造permission对象
Permission perm = BeanMapUtils.mapToBean(map,Permission.class);
perm.setId(id);
//2.根据类型构造不同的资源对象(菜单,按钮,api)
int type = perm.getType();
switch (type) {
case PermissionConstants.PERMISSION_MENU:
PermissionMenu menu = BeanMapUtils.mapToBean(map,PermissionMenu.class);
menu.setId(id);
permissionMenuDao.save(menu);
break;
case PermissionConstants.PERMISSION_POINT:
PermissionPoint point = BeanMapUtils.mapToBean(map,PermissionPoint.class);
point.setId(id);
permissionPointDao.save(point);
break;
case PermissionConstants.PERMISSION_API:
PermissionApi api = BeanMapUtils.mapToBean(map,PermissionApi.class);
api.setId(id);
permissionApiDao.save(api);
break;
default:
throw new CommonException(ResultCode.FAIL);
}
//3.保存
permissionDao.save(perm);
}
分类的思想是一致的。
更新(update)
:同样,根据请求头,来判断用户的信息和权限,然后根据是更新哪一种权限信息去save。
查询(findById)
:通过id进行单个用户的查询是相对简单的,只需要判断是因为触发哪种条件并且对应该条件进行json->map的转型即可,然后其他还有什么属性一并用getter存储即可
查询(findAll)
:查询所有人的时候需要一个limit条件,通过不同的查询条件可以得到不同的结果,这是额外新加的一个功能。这里主要是用了父id,enVisible,type进行查询。写的方法很多很多,能熟练使用toPredicate即可。
删除(deleteById)
:和上面的思想是一样的。
- 角色分配:我们将功能实现的Role的系列中。
权限分配(assignPerms)
:第一步,我们要将request过来的对象信息获取到;第二步,我们要将确定给予角色的权限以set的方式存进来并且对应着权限去给予对象实际的能力;第三步,将角色与权限的关系进行数据绑定到联合表中。第四步,保存。
- 将用户与角色进行绑定,这样用户-角色-权限就绑定起来了。
4.5 认证机制
常见的认证机制如下:
HTTP Basic Auth
简单来说,直接账号密码验证,过了就能登录。
Cookie Auth
登录的时候在Server端建立一个Session对象,同时在Client的浏览器端创建一个Cookie对象。通过Client带上来Cookie对象来与服务端的Session对象匹配来通过登录认证。并且cookie可以设置失效时间。
但是在微服务的时候,我们一使用跨域访问多端口的业务,cookie就不一样了,就会出错,所以cookie现在也不太好了。
OAuth
开放授权,用第三方去授权登录网站。
Token Auth
使用基于Token的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:
- 客户端使用用户名密码登录
- Server端收到请求进行验证
- 验证通过,Server端会签发一个Token,再把Token发送到客户端
- 之后前端再访问api的时候就用token当请求头用了,只要验证成功就可以get到数据
优点
- 支持跨域
- 不需要保存登录信息在Server端
- 不影响页面显示效果
- token可以在任何地方生成,不必非得在登录系统中用
- 很难伪造出token进行跨站伪造访问(CSRF)
- 性能强
- 基于标准化,可以采用JWT(Json Web Token)
- 做测试的时候可以用之前生成好的令牌
4.5.1 JWT(Java Web Token)
利用生成的token完成用户认证与登录
设置认证Jwt
前端将用户名和密码填写完整之后登录,便会触发jwt。
步骤:
- 根据用户名查询用户是否存在
- 比较密码
- 生成对应的token
- 令前端访问后,可以凭借token访问
思路:
(1) 引入jwt工具类
(2) 在userController中添加用户登录
(3) 验证用户信息
(4) 返回
@PostMapping(value = "/login")
public Result login(@RequestBody Map<String,String> loginMap) {
String mobile = loginMap.get("mobile");
String password = loginMap.get("password");
User user = userService.findByMobile(mobile);
if (user == null || !user.getPassword().equals(password)) {
return new Result(ResultCode.MOBILEORPASSWORDERROR);//登录失败
} else {
Map<String, Object> map = new HashMap<>();
map.put("companyId", user.getCompanyId());
map.put("companyName", user.getCompanyName());//claims添加
String jwt = jwtUtils.createJWT(user.getId(), user.getUsername(), map);
//根据id 用户名和自定义的claims生成token
return new Result(ResultCode.SUCCESS, jwt);
}
}
设置解析Jwt
功能:获取用户信息,像权限标识(三种),角色之类的
思路 :
- 获取头信息的token
- 从token中提取userid
- 查询用户
- 构造数据
/**
* 获取用户信息
* 1.获取用户id
* 2.根据用户id查询用户
* 3.构建返回值
* 4.响应
*/
@PostMapping(value = "/profile")
public Result profile(HttpServletRequest request) throws Exception {
/**
* 从请求头信息中获取token数据
* 1.获取请求头信息 名称=Authorization
* 2.替换Bearer+空格
* 3.解析token
* 4.获取clamis
*/
// 1.从请求头信息中获取token数据
String authorization = request.getHeader("Authorization");
if (StringUtils.isEmpty(authorization)) {
throw new CommonException(ResultCode.UNAUTHENTICATED);
}
// 2.替换Bearer+空格
String token = authorization.replace("Bearer ","");//去除Bearer,提取纯正的token
// 3.解析token,4.获取clamis
Claims claims = jwtUtils.parseJwt(token);
String userId = claims.getId();
//获取用户信息
User user = userService.findByid(userId);
//根据不同的用户级别获取用户级别权限
ProfileResult profileResult = null;
if ("user".equals(user.getLevel())) {//最低级
profileResult = new ProfileResult(user);
} else {
Map map = new HashMap();
if ("coAdmin".equals(user.getLevel())) {//第二个级别
map.put("enVisible", "1");
}
List<Permission> list = permissionService.findAll(map);
profileResult = new ProfileResult(user, list);
}
return new Result(ResultCode.SUCCESS, profileResult);
}