1、常见的认证机制
1.1 HTTP Basic Auth
HTTP Basic Auth:简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合
RESTful API 使用的最简单的认证方式,只需提供用户名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越来越少。因此,在开发对外开放的RESTful API时,尽量避免采用HTTP Basic Auth。
具体可参考:https://www.cnblogs.com/yuqiangli0616/p/9389273.html
1.2 Cookie Auth
Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个Cookie对象;通过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理的。默认的,当我们关闭浏览器的时候,cookie会被删除。但可以通过修改cookie 的expire time使cookie在一定时间内有效。
1.3 OAuth
OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在某一web服务上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。 OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容:
这种基于OAuth的认证机制适用于个人消费者类的互联网产品,如社交类APP等应用,但是不太适合拥有自有认证权限管理的企业应用。
可以参考:https://www.cnblogs.com/kaleidoscope/p/9507261.html
1.4 Token Auth
使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:
1. 客户端使用用户名跟密码请求登录
2. 服务端收到请求,去验证用户名与密码
3. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里
5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
6. 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
Token Auth的优点
1、支持跨域访问:Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输;
2、无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息;
3、更适用CDN:可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可;
4、去耦:不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可;
5、更适用于移动应用:当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多;
6、CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范;
7、性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算 的Token验证和解析要费时得多;
8、不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要为登录页面做特殊处理;
9、基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET、Ruby、Java、Python、PHP)和多家公司的支持(如:Firebase,Google, Microsoft)。
2、HRM中的TOKEN签发与验证
2.1 什么是JWT
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。在Java世界中通过JJWT实现JWT创建和验证。
Json web token(JWT)是为了网络应用环境间传递声明而执行的一种基于JSON的开发标准(RFC 7519),该token被设计为紧凑且安全的,特别适用于分布式站点的单点登陆(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
2.2 JJWT的快速入门
2.2.1 添加依赖
在我们的ihrmcommon模块的pom.xml文件中添加如下依赖:
<!--添加token认证依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
2.2.2 生成token
在ihrmcommon工程的test/java下面创建包com.zdw.ihrm.token,新建类:CreateTokenTet
package com.zdw.ihrm.token;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class CreateTokenTest {
public static void main(String[] args) {
JwtBuilder jwtBuilder = Jwts.builder().setId("zdw").setSubject("token").setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, "mykey123");//mykey123表示的私钥,这个是约定的
String token = jwtBuilder.compact();
System.out.println("token:"+token);
//eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJ6ZHciLCJzdWIiOiJ0b2tlbiIsImlhdCI6MTU3NDkxMDIyN30.B8UT0FuVZQ3sGREPpd9t0wXosXVgBjPUEgtPvtZcfN0
}
}
2.2.3 解析token
我们刚才已经创建了token ,在web应用中这个操作是由服务端进行然后发给客户端,客户端在下次向服务端发送请求时需要携带这个token(这就好像是拿着一张门票一样),那服务端接到这个token 应该解析出token中的信息(例如用户id),根据这些信息查询数据库返回相应的结果。
创建ParseTokenTest:
package com.zdw.ihrm.token;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
public class ParseTokenTest {
public static void main(String[] args) {
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJ6ZHciLCJzdWIiOiJ0b2tlbiIsImlhdCI6MTU3NDkxMDI1N30.jsvTBeBMDu9TLuihVMWm1-MaZY5gwRb94R0lqQvcOM0";
//密钥是mykey123,这跟我们生成token的是要保持一致的
Claims claims = Jwts.parser().setSigningKey("mykey123").parseClaimsJws(token).getBody();
System.out.println(claims.getId()); //zdw
System.out.println(claims.getSubject()); //token
System.out.println(claims.getIssuedAt());//Thu Nov 28 11:04:17 CST 2019
}
}
试着将token或签名秘钥篡改一下,会发现运行时就会报错,所以解析token也就是验证token
2.2.4 自定义claims
上面的代码中我们都是调用的本身的方法设置Id,Subject等值,当然我们在实际开发中肯定需要传递一些跟业务相关的数据给服务器的,而这个操作就成为自定义claims。
修改上面的CreateTokenTest:
package com.zdw.ihrm.token;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class CreateTokenTest {
public static void main(String[] args) {
//为了方便测试,我们把过期实际设置为60秒
long now = System.currentTimeMillis();//当前时间
long exp = now + 1000*60;//过期时间
JwtBuilder jwtBuilder = Jwts.builder()
.setId("zdw")
.setSubject("token")
.setIssuedAt(new Date())
.setExpiration(new Date(exp))//设置过期时间
.signWith(SignatureAlgorithm.HS256, "mykey123")
.claim("companyId","123") //自定义的claims数据
.claim("companyName","酱油公司"); //自定义的claims数据
String token = jwtBuilder.compact();
System.out.println(token);
}
}
修改ParseTokenTest:
package com.zdw.ihrm.token;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ParseTokenTest {
public static void main(String[] args) {
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJ6ZHciLCJzdWIiOiJ0b2tlbiIsImlhdCI6MTU3NDkxMTk2MywiZXhwIjoxNTc0OTEyMDIyLCJjb21wYW55SWQiOiIxMjMiLCJjb21wYW55TmFtZSI6IumFseayueWFrOWPuCJ9.1chYBqEh-hS0uJtnMznI4msL56o48KMULQl-5q14-qU";
//密钥是mykey123,这跟我们生成token的是要保持一致的
Claims claims = Jwts.parser().setSigningKey("mykey123").parseClaimsJws(token).getBody();
System.out.println(claims.getId());
System.out.println(claims.getSubject());
System.out.println(claims.getIssuedAt());
System.out.println("自定义的claims数据companyId:"+claims.get("companyId"));
System.out.println("自定义的claims数据companyName:"+claims.get("companyName"));
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
System.out.println("token签发时间:"+sdf.format(claims.getIssuedAt()));
System.out.println("token过期时间:"+sdf.format(claims.getExpiration()));
System.out.println("当前时间:"+sdf.format(new Date()));
}
}
注意:我们设置了 token的过期时间是60秒,所以生成token和解析token要控制60秒之内,如果是正常解析,打印结果如下:
zdw
token
Thu Nov 28 11:32:43 CST 2019
自定义的claims数据companyId:123
自定义的claims数据companyName:酱油公司
token签发时间:2019-11-28 11:32:43
token过期时间:2019-11-28 11:33:42
当前时间:2019-11-28 11:33:09
如果token已经过期的话,就会报下面的异常信息:
Exception in thread "main" io.jsonwebtoken.ExpiredJwtException: JWT expired at 2019-11-28T11:24:42+0800. Current time: 2019-11-28T11:30:19+0800
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:365)
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:458)
at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:518)
at com.zdw.ihrm.token.ParseTokenTest.main(ParseTokenTest.java:13)
2.2 JWT工具类
在ihrmcommon工程的utils包下,创建JwtUtil,用来进行签发token和解析token。
package com.ihrm.common.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Date;
import java.util.Map;
@Getter
@Setter
@ConfigurationProperties(value = "jwt.config")
public class JwtUtil {
private String key;//密钥
private long ttl;//token的有效时间,单位毫秒
/**
* 签发(生成)token
* @param userId 用户id
* @param username 用户名
* @param map 其他业务参数
* @return
*/
public String createJwtToken(String userId,String username,Map<String,Object> map){
long now = System.currentTimeMillis();//当前时间
long expTime = now+ttl;//过期时间
JwtBuilder jwtBuilder = Jwts.builder()
.setId(userId).setSubject(username)
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, key);
//设置业务参数
for (Map.Entry<String,Object> entry : map.entrySet()){
jwtBuilder.claim(entry.getKey(),entry.getValue());
}
//设置过期时间
if(ttl>0){
jwtBuilder.setExpiration(new Date(expTime));
}
//签发token
String token = jwtBuilder.compact();
return token;
}
/**
* 解析token,得到Claims对象
* @param token 服务器签发给客户端的token
* @return
*/
public Claims parseJwtToken(String token){
Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
return claims;
}
}
修改ihrm_system工程的application.yml,添加配置(哪个工程用到了这个工具类,就在对应配置文件中添加):
jwt:
config:
key: saas-ihrm
#设置token有效期为1小时
ttl: 3600000
2.3 登录成功签发token
2.3.1 JwtUtil交由Spring管理
在ihrm_system模块的启动类中,配置JwtUtil,将其交给Spring管理
@Bean //签发和解析token的工具类交给spring管理
public JwtUtil jwtUtil(){
return new JwtUtil();
}
2.3.2 添加登录方法
在UserController中添加用户登录的方法:
@Autowired
private JwtUtil jwtUtil;
/**
* 用户登录的方法
* @param loginMap 登录的手机号和密码是在请求体body中的,所以可以用map来接收
* @return
*/
@RequestMapping(value = "/login",method = RequestMethod.POST)
public Result login(@RequestBody Map<String,String> loginMap) throws Exception {
String mobile = loginMap.get("mobile");
String password = loginMap.get("password");
//根据mobile查询用户信息
User user = userService.findByMobile(mobile);
if(user ==null || !password.equals(user.getPassword())){
throw new CommonException(ResultCode.MOBILEORPASSWORDERROR);//提示用户名或密码错误
}else{
//登录成功,签发token并把token响应给客户端
Map<String,Object> map = new HashMap<>();
map.put("companyId",user.getCompanyId());
map.put("companyName",user.getCompanyName());
String token = jwtUtil.createJwtToken(user.getId(), user.getUsername(), map);
return new Result(ResultCode.SUCCESS,token);
}
}
在UserSrvice和UserDao中添加根据mobile查询用户的方法:
//根据用户mobile查询用户
public User findByMobile(String mobile) {
return userDao.findByMobile(mobile);
}
public interface UserDao extends JpaRepository<User,String>,JpaSpecificationExecutor<User> {
User findByMobile(String mobile);
}
2.3.3 测试
用postman进行登录token签发的测试,去数据库中找存在的用户信息进行登录:
2.4 获取用户信息鉴权
需求:用户登录成功之后,会发送一个新的请求到服务端,获取用户的详细信息。获取用户信息的过程中必须登录成功,否则不能获取。
前后端约定:前端请求微服务时需要添加头信息Authorization ,内容为:Bearer+空格+token
2.4.1 添加响应实体对象
服务器向客户端返回的用户的详细信息中,应该包含用户的基本信息,以及用户对应的权限信息,因为前端要根据客户的权限信息来进行页面菜单和按钮的显示控制。所以需要构造一个实体类,里面封装了用户信息及其权限信息:
在ihrm_common_model的system.response包下创建ProfileResult类:
package com.zdw.ihrm.domain.system.response;
import com.zdw.ihrm.domain.system.Permission;
import com.zdw.ihrm.domain.system.Role;
import com.zdw.ihrm.domain.system.User;
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
//服务器返回给客户端的用户权限信息实体
@Setter
@Getter
public class ProfileResult {
private String mobile;
private String username;
private String company;
private Map<String,Object> roles = new HashMap<>();
public ProfileResult(User user) {
this.mobile = user.getMobile();
this.username = user.getUsername();
this.company = user.getCompanyName();
Set<Role> roles = user.getRoles();
Set<String> menus = new HashSet<>();
Set<String> points = new HashSet<>();
Set<String> apis = new HashSet<>();
for (Role role : roles) {
Set<Permission> perms = role.getPermissions();
for (Permission perm : perms) {
String code = perm.getCode();//权限编码
if(perm.getType() == 1) {
menus.add(code);
}else if(perm.getType() == 2) {
points.add(code);
}else {
apis.add(code);
}
}
}
this.roles.put("menus",menus);
this.roles.put("points",points);
this.roles.put("apis",apis);
}
}
2.4.2 添加profile方法
注意:因为我们的需求是:SaaS平台管理员可以拥有所有的权限,企业管理员拥有企业相关的所有权限,而普通员工就拥有分配的角色权限。为了区分用户,我们需要在bs_user表中添加一列:level,对应的值有三种:saasAdmin coAdmin user。
ALTER TABLE bs_user ADD COLUMN LEVEL VARCHAR(20) COMMENT '用户级别,saasAdmin:saas管理员,coAdmin:企业管理员,user:普通用户';
同时需要在User实体类种增加下面的属性:
private String level;//用户等级
然后给我们系统里面的三个用户做了level的划分:
在返回结果类:ProfileResult,新增构造方法(根据用户User和权限的集合封装一个返回结果)
//根据用户User和权限的集合封装一个返回结果
public ProfileResult(User user,List<Permission> perms){
this.mobile = user.getMobile();
this.username = user.getUsername();
this.company = user.getCompanyName();
Set<String> menus = new HashSet<>();
Set<String> points = new HashSet<>();
Set<String> apis = new HashSet<>();
for (Permission perm : perms) {
String code = perm.getCode();//得到权限编码
if(perm.getType()==1){
menus.add(code);
}else if(perm.getType()==2){
points.add(code);
}else{
apis.add(code);
}
}
this.roles.put("menus",menus);
this.roles.put("points",points);
this.roles.put("apis",apis);
}
在UserController中添加profile方法:
@Autowired
private PermissionService permissionService;
//获取用户信息的方法
@RequestMapping(value = "/profile",method = RequestMethod.POST)
public Result profile(HttpServletRequest request) throws Exception {
//请求中获取key为Authorization的头信息
String authorization = request.getHeader("Authorization");
if(StringUtils.isEmpty(authorization)){
throw new CommonException(ResultCode.UNAUTHENTICATED);//说明没有登录
}
//前后端约定头信息内容以 Bearer+空格+token 形式组成
String token = authorization.replace("Bearer ", "");//把Bearer 替换掉,得到的就是token
//解析token
Claims claims = jwtUtil.parseJwtToken(token);
if(claims==null){
throw new CommonException(ResultCode.UNAUTHENTICATED);//说明没有登录
}
String userId = claims.getId();//得到用户id
User user = userService.findtById(userId);//会一起查询到用户的roles权限集合
/**
* 根据user的level来判断查询的权限
* saasAdmin:saas管理员:拥有所有的权限
* coAdmin:企业管理员:只有企业相关的所有权限,不能查看saas平台相关的操作,比如企业管理(企业信息的增上改查)
* user:普通用户:只有分配的对应的角色的权限
*/
String level = user.getLevel();
ProfileResult profileResult = null;
if("user".equals(level)){//普通用户
profileResult = new ProfileResult(user);
}else{
Map<String,Object> map = new HashMap<>();
if("coAdmin".equals(level)){
map.put("enVisible","1");//说明可以查询到企业可以看到的所有权限
}
List<Permission> list = permissionService.findAll(map);
profileResult = new ProfileResult(user,list);
}
return new Result(ResultCode.SUCCESS,profileResult);
}
3、前端权限控制
3.1 需求分析
基于前后端分离的开发模式中,权限控制分为前端页面可见性权限与后端API接口可访问行权限。前端的权限控制主要围绕在菜单是否可见,以及菜单中按钮是否可见两方面展开的。
在vue工程中,菜单可以简单的理解为vue中的路由,只需要根据登录用户的权限信息动态的加载路由列表就可以动态的构造出访问菜单。
1. 登录成功后获取用户信息,包含权限列表(菜单权限,按钮权限)
2. 根据用户菜单权限列表,动态构造路由(根据路由名称和权限标识比较)
3. 页面按钮权限通过自定义方法控制可见性
3.2 菜单权限控制
3.2.1 路由钩子函数
vue路由提供的钩子函数(beforeEach)主要用来在加载之前拦截导航,让它完成跳转或取消。可以在路由钩子函数中进行校验是否对某个路由具有访问权限;
文件:src/router/index.js
router.beforeEach((to, from, next) => {
NProgress.start() // start progress bar
if (getToken()) {
// determine if there has token
/* has token */
if (to.path === '/login') {
next({path: '/'})
NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
} else {
if (store.getters.roles.length === 0) {
// 判断当前用户是否已拉取完user_info信息
store
.dispatch('GetUserInfo')
.then(res => {
// 拉取user_info
const roles = res.data.data.roles // note: roles must be a array! such as: ['editor','develop']
store.dispatch('GenerateRoutes', {roles}).then(() => {
// 根据roles权限生成可访问的路由表
router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
next({...to, replace: true}) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
})
})
.catch(() => {
store.dispatch('FedLogOut').then(() => {
Message.error('验证失败, 请重新登录')
next({path: '/login'})
})
})
} else {
next()
}
}
} else {
/* has no token */
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
} else {
next('/login') // 否则全部重定向到登录页
NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
}
}
})
3.2.2 修改api接口的请求路径
src/api/base/frame.js,修改其中登录和获取用户信息的请求路径:
import {createAPI, createFormAPI} from '@/utils/request'
//export const login = data => createAPI('/frame/login', 'post', data) //登录修改成下面的
export const login = data => createAPI('/sys/login', 'post', data)
export const registerStep1 = data => createAPI('/frame/register/step1', 'post', data)
export const registerStep2 = data => createAPI('/frame/register/step2', 'post', data)
export const regCode = data => createAPI('/frame/register/verification_code', 'post', data)
export const logout = data => createAPI('/frame/logout', 'post', data)
export const passwd = data => createAPI('/frame/passwd', 'post', data)
//export const profile = data => createAPI('/frame/profile', 'post', data)//获取信息修改成下面的
export const profile = data => createAPI('/sys/profile', 'post', data)
3.2.3 修改登录和获取信息的js
src/module-dashboard/store/index.js
// 用户名登录
LoginByUsername({ commit }, userInfo) {
const username = userInfo.mobile.trim()
return new Promise((resolve, reject) => {
login({
mobile: username,
password: userInfo.password
}).then(response => {
const data = response.data.data //这里的data拿到的就是用户登录成功,服务器返回的jwt生成的token
//commit('SET_TOKEN', data.token) //所以这里直接改成下面的
commit('SET_TOKEN', data)
//setToken(data.token)
setToken(data) //所以这里直接改成下面的
resolve()
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息
GetUserInfo({ commit, state }) {
return new Promise((resolve, reject) => {
profile().then(response => {
const data = response.data.data
commit('SET_ROLES', data.roles?data.roles:{menus:[],points:[]})
commit('SET_NAME', data.username)
commit('SET_AVATAR', data.avatar)
commit('SET_INTRODUCTION', data.company)
commit('SET_APPROVALS', data.approvals)
resolve(response)
}).catch(error => {
reject(error)
})
})
}
3.2.4 配置菜单权限
在 \src\module-dashboard\store\permission.js 下进行修改,开启路由配置
注意:我们在新增菜单的时候,权限标识要配置成模块名的除去 module- 后剩下的名称:比如SaaS管理模块名称是module-saas-clients,那么权限标识就是:saas-clients
actions: {
GenerateRoutes({ commit }, data) {
return new Promise(resolve => {
const { roles } = data
//动态构造权限列表
let accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
commit('SET_ROUTERS', accessedRouters)
//commit('SET_ROUTERS', asyncRouterMap) // 调试开启全部路由,这是没做权限时候的设置
resolve()
})
}
}
3.2.5 配置验证权限的方法
src\utils\permission.js 配置验证是否具有权限的验证方法
import store from '@/store'
// 检查是否有权限
export function hasPermission(roles, route) {
if (roles.menus && route.name) {
return roles.menus.some(role => {
return route.name.toLowerCase() === role.toLowerCase()
})
} else {
return false
}
}
// 检查是否有权限点
export function hasPermissionPoint(point) {
let points = store.getters.roles.points
if (points) {
return points.some(it => it.toLowerCase() === point.toLowerCase())
} else {
return false
}
}
3.2.6 修改登录和获取信息的请求接口
mock\index.js 中不加载登录(login)以及(profile)的模拟测试
import Mock from 'mockjs'
import TableAPI from './table'
import ProfileAPI from './profile'
import LoginAPI from './login'
import CompanyAPI from './company'
Mock.setup({
//timeout: '1000'
})
Mock.mock(/\/table\/list\.*/, 'get', TableAPI.list)
//注释掉下面两行
//Mock.mock(/\/frame\/profile/, 'post', ProfileAPI.profile)
//Mock.mock(/\/frame\/login/, 'post', LoginAPI.login)
//配置模拟数据接口
//Mock.mock(/\/company\/+/, 'get', CompanyAPI.sassDetail)//根据id查询
//Mock.mock(/\/company/, 'get', CompanyAPI.list) //访问企业列表
3.3 按钮权限控制
这里的按钮权限控制以员工管理菜单列表中,删除按钮做测试。如果当前用户拥有删除员工的权限,那么就显示删除按钮,否则,删除按钮是不可见的。
3.3.1 判断删除按钮是否可见
在module-employees模块的 pages/index.vue中,找到删除按钮,修改成如下的:
<!-- <el-button @click="handleDelete(scope.row)" type="text" size="small">删除</el-button> -->
<el-button v-if="checkPoint('USER_DELETE')" @click="handleDelete(scope.row)" type="text" size="small">删除</el-button>
3.3.2 配置检查按钮权限的方法
在src/utils/permission.js中,添加如下方法:
// 检查是否有权限点
export function hasPermissionPoint(point) {
let points = store.getters.roles.points
if (points) {
return points.some(it => it.toLowerCase() === point.toLowerCase())
} else {
return false
}
}
3.3.3 引入按钮权限方法,完善checkPoint方法
在module-employees模块的 pages/index.vue中,引入刚才的方法:
import { hasPermissionPoint } from '@/utils/permission' //导入按钮权限验证的方法
然后在methods中添加方法:
//校验按钮权限的方法
checkPoint(point){
return hasPermissionPoint(point);
}
3.4 测试
3.4.1 菜单权限测试
我们可以分别以用户的level字段是saasAdmin,coAdmin,user的用户登录系统,查看菜单是否根据权限不同有变化,这个很容易看出来,自行测试即可。
3.4.2 按钮菜单权限测试
我们首先以level字段是coAdmin用户登录系统,分配张三(user)人事经理的角色,然后给人事经理角色分配新增和修改员工的权限,如下所示:
然后,我们用张三的账户登录系统:
可以看到,菜单权限也生效了,只有员工管理的菜单显示,同时,我们发现操作列中,只有查看和分配角色两个按钮,没有删除按钮,这其实也说明按钮权限是生效的,当然我们可以给人事经理角色分配删除用户的权限,而张三拥有人事经理的角色,所以等会再用张三登录的时候,是可以看到删除按钮的:
用level是coAdmin的用户登录,给人事经理分配删除员工权限:
再次用张三登录:
可以看到删除按钮是显示的,所以如果其他的按钮要做权限控制,也应该根据这种方式一个个的去做控制。