一. 概述
- 一个系统中用户登陆之后看到的菜单,必须是当前登录人拥有权限的菜单才能展示,没有权限的菜单直接不显示;
- 使用无状态token方案,登录只存储了loginInfo信息,没有登录人相关的权限(菜单、按钮权限);
- 难道我们在登录的时候需要将登录人相关的权限信息也一并存储到redis中码?
答:如果人数多,并发量大,redis就不是一个好的方案:redis是一个内存数据库,内存有局限,数据量越大,内存占用率高,影响读取性能。
二. 无状态的token方案
后端验证登录信息成功之后,会生成一个随机串作为token将用户信息保存在redis,并将token令牌传回给浏览器;
后续浏览器只需要将token携带到服务器,服务器就可以根据浏览器的token令牌获取redis的信息
2.1. 如果获取不到信息,说明token令牌无效
2.2. 获取到信息,就向客户端返回请求的数据
缺点:
三. JWT方案
1. 为什么要用JWT ?
如果将登录信息放在redis - 只存登录信息也还行
如果1.并发量高 2.保存的不只是登录信息,还有菜单和权限 redis保存的数据就非常多。redis内存数据库,影响服务器的性能
jwt:登录成功,把登录信息还有菜单和权限进行加密【jwt - json web token = 加密之后的字符串】
将jwt保存在浏览器的localStorage中
2. 什么是JWT
JSON Web Token【JWT】
是一个非常轻巧的规范。这个规范允许我们使用JWT
在用户和服务器之间传递安全可靠的信息
通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token
,并且这个JWT token
带有签名信息,接收后可以校验是否被篡改。所以可以用于在各方之间安全地将信息作为Json对象传输。
服务器生成JWT token
后,响应给浏览器客户端。客户端保存起来。在后续的请求中,客户端将JWT token
连同请求内容一起发送给服务器,服务器收到请求后通过JWT token
验证用户,如果验证不通过则不返回请求的数据 。验证通过就会向客户端返回请求的数据。
总结:使用JWT生产的Token是安全的,可以理解成就是在无状态的token方案基础上,将token从随机串换成包含登录人信息、权限等内容,且做了加密处理之后的串,实现了数据的安全传输。

3. JWT特点
- 基于JSON,方便解析,因为JSON的通用性,所以JWT可以跨语言支持
- 可以在令牌中定义内容,方便扩展。他不是一个随机token串,而是可以携带自定义内容的加密token串
- 使用非对称加密算法中提供数字签名,JWT防篡改
- 后端服务使用JWT可以不依赖redis即可完成权限校验
4. JWT组成
1 2 3 4 5
| JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了JWT字符串。就像这样: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ 第一部分:我们称它为头部(header),用于存放token类型和加密协议,一般都是固定的 第二部分:我们称其为载荷(payload),用户数据就存放在里面 第三部分:是签证(signature),主要用于服务端的验证
|
1 2 3 4
| { 'typ': 'JWT', 'alg': 'HS256' }
|
jwt 的头部承载两部分信息:
- 声明类型 , 告知这里是 jwt
- 声明加密的算法 通常直接使用 HMAC, SHA256
在使用过程中会对该JSON
进行BASE64
编码,得到Jwt的第一部分:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
载荷【playload】 :JSON格式,用户数据就存放在里面,也需要BASE64编码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| { "sub": "1234567890", "name": "John Doe", "admin": true } 然后将其进行BASE64加密,得到Jwt的第二部分: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
载荷playload也包含三部分: 1. 标准中注册的声明(建议但不强制使用) iss: jwt签发者 sub: jwt所面向的用户zs aud: 接收jwt的一方 exp: jwt的过期时间,这个过期时间必须要大于签发时间 nbf: 定义在什么时间之前,该jwt都是不可用的 iat: jwt的签发时间 jti: jwt的唯一身份标识,主要用来作为一次性token 2. 公共的声明:公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密 3. 私有的声明:私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息
|
签名 【signature】: jwt的第三部分是一个签证信息,通过指定的算法生成哈希,以确保数据不会被篡改,这个签证信息由三部分组成:
1 2 3
| head(base64编码后的) playload(base64编码后的) secret(秘钥)
|
这个部分需要BASE64
加密后的header
和BASE64
加密后的payload
使用.
连接组成的字符串,然后通过header
中声明的加密方式进行加盐secret
组合加密,然后就构成了jwt
的第三部分:
1 2
| let encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload); let signature = HMACSHA256(encodedString, '密钥');
|
加密之后,得到signature签名信息,即Jwt
的第三部分:
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
将这三部分用.
连接成一个完整的字符串,就构成了最终的Jwt:
1
| eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
|
四. 加密算法介绍
1 2 3 4
| 明文:加密之前的内容,原始内容 暗文:加密之后的内容 公钥:可见的公共的钥匙 私钥:不可见的私有的钥匙
|
1. 不可逆加密算法
特征:只能加密不能解密
技术:md5
作用:一般对登录密码处理,用于做密码比对
问题:只能加密,不能解密,不能用来对网络中传输的数据进行加密
2. 可逆对称加密算法
1 2 3 4 5 6 7 8 9 10 11 12
| 特征: 1. 可以加密,也可以解密 2. 加密和解密的密钥是同一个 实现:DES,AES 作用:对数据库密码进行加密 算法: 密文为s,加解密算法为AES,秘钥为k,明文为c 加密:s = AES(k,c) 解密:c = AES(k,s) 问题: 1. 数据可能会被其他人解密 2. 数据可能会被篡改
|
3. 可逆非对称加密算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 特征: 1. 可以加密,也可以解密 2. 加密和解密的密钥不是同一个。但是是成对出现的。一个私钥就对应一个公钥。如果使用私钥加密, 只能使用与之对应公钥来解决。反之如果使用公钥加密,只能使用与之对应私钥解密 实现:RSA,RSA2 作用:网络传输中对数据进行加解密 算法: 密文为s,加解密算法为RSA私钥为k1,公钥为k2,明文为c 第一组:私钥加密公钥解密 加密:s = rsa(k1,c) 解密:c = rsa(k2,s) 第二组:公钥加密私钥解决 加密:s = rsa(k2,c) 解密:c = rsa(k1,s) 注意:加密一次不安全,要加密两次,解密两次。第一次加密和解密并不是真正的数据,而是数字签名和签名认证/确认身份
|
4. .网络加密技术有哪些?
1.不可逆【只能加密不能解密】的加密技术:md5
用来对比密码,不能用来传输数据
2.可逆【可以加密也能解密】对称【加密和解密使用的是同一个秘钥】加密算法:AES,DES
风险:截取数据
篡改数据
3.可逆非对称【加密和解密使用的不是同一个秘钥,使用公钥和私钥】
前提:交换公钥
加密:篡改数据
加密2次,解密2次:
先用对方的公钥加密,然后再用自己的私钥加密
先用对方的公钥解密,然后再用自己的私钥解密
五. 常用工具类
生成JWT,需要先获取公钥,私钥
1. 依赖
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
<dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.10.11</version> </dependency>
|
2. RsaUtils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| package io.coderyeah.basic.jwt;
import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.security.*; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64;
public class RsaUtils {
private static final int DEFAULT_KEY_SIZE = 2048;
public static PublicKey getPublicKey(String filename) throws Exception { byte[] bytes = readFile(filename); return getPublicKey(bytes); }
public static PrivateKey getPrivateKey(String filename) throws Exception { byte[] bytes = readFile(filename); return getPrivateKey(bytes); }
public static PublicKey getPublicKey(byte[] bytes) { try{ bytes = Base64.getDecoder().decode(bytes); X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePublic(spec); }catch (Exception e){ e.printStackTrace(); return null; } }
public static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException { bytes = Base64.getDecoder().decode(bytes); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePrivate(spec); }
public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret, int keySize) throws Exception { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); SecureRandom secureRandom = new SecureRandom(secret.getBytes()); keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom); KeyPair keyPair = keyPairGenerator.genKeyPair(); byte[] publicKeyBytes = keyPair.getPublic().getEncoded(); publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes); writeFile(publicKeyFilename, publicKeyBytes); byte[] privateKeyBytes = keyPair.getPrivate().getEncoded(); privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes); writeFile(privateKeyFilename, privateKeyBytes); }
private static byte[] readFile(String fileName) throws Exception { return Files.readAllBytes(new File(fileName).toPath()); }
private static void writeFile(String destPath, byte[] bytes) throws IOException { File dest = new File(destPath); if (!dest.exists()) { dest.createNewFile(); } Files.write(dest.toPath(), bytes); }
public static void main(String[] args) throws Exception{ generateKey("E:\\springboot\\pethome\\src\\main\\resources\\auth_rsa.pub", "E:\\springboot\\pethome\\src\\main\\resources\\auth_rsa.pri","coderyeah",2048); } }
|
3. JwtUtils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
| package io.coderyeah.basic.jwt;
import com.alibaba.fastjson.JSONObject; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.joda.time.DateTime;
import java.security.PrivateKey; import java.security.PublicKey; import java.util.Base64; import java.util.UUID;
public class JwtUtils {
private static final String JWT_PAYLOAD_USER_KEY = "user";
private static String createJTI() { return new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes())); }
public static String generateTokenExpireInMinutes(Object userInfo, PrivateKey privateKey, int expire) { return Jwts.builder() .claim(JWT_PAYLOAD_USER_KEY, JSONObject.toJSONString(userInfo)) .setId(createJTI()) .setExpiration(DateTime.now().plusMinutes(expire).toDate()) .signWith(SignatureAlgorithm.RS256,privateKey) .compact();
}
public static String generateTokenExpireInSeconds(Object userInfo, PrivateKey privateKey, int expire) { return Jwts.builder() .claim(JWT_PAYLOAD_USER_KEY, JSONObject.toJSONString(userInfo)) .setId(createJTI()) .setExpiration(DateTime.now().plusSeconds(expire).toDate()) .signWith(SignatureAlgorithm.RS256,privateKey) .compact(); }
private static Jws<Claims> parserToken(String token, PublicKey publicKey) { return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token); }
public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey, Class<T> userType) { Jws<Claims> claimsJws = parserToken(token, publicKey); Claims body = claimsJws.getBody(); Payload<T> claims = new Payload<>(); claims.setId(body.getId()); T t = JSONObject.parseObject(body.get(JWT_PAYLOAD_USER_KEY).toString(),userType); claims.setLoginData(t); claims.setExpiration(body.getExpiration()); return claims; }
public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey) { Jws<Claims> claimsJws = parserToken(token, publicKey); Claims body = claimsJws.getBody(); Payload<T> claims = new Payload<>(); claims.setId(body.getId()); claims.setExpiration(body.getExpiration()); return claims; }
public static void main(String[] args) throws Exception { PrivateKey privateKey = RsaUtils.getPrivateKey(JwtUtils.class.getClassLoader().getResource("auth_rsa.pri").getFile()); System.out.println(privateKey); String token = generateTokenExpireInSeconds(new User(1L, "zs"), privateKey, 10); System.out.println(token);
PublicKey publicKey = RsaUtils.getPublicKey(JwtUtils.class.getClassLoader().getResource("auth_rsa.pub").getFile()); Payload<User> payload = getInfoFromToken(token, publicKey, User.class); System.out.println(payload); Thread.sleep(11000);
} }
class User{ private Long id; private String name;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + '}'; }
public User() { }
public User(Long id, String name) { this.id = id; this.name = name; } }
|
4. 载荷数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package io.coderyeah.basic.jwt;
import java.util.Date;
public class Payload<T> {
private String id; private T loginData; private Date expiration;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public T getLoginData() { return loginData; }
public void setLoginData(T loginData) { this.loginData = loginData; }
public Date getExpiration() { return expiration; }
public void setExpiration(Date expiration) { this.expiration = expiration; }
@Override public String toString() { return "Payload{" + "id='" + id + '\'' + ", loginData=" + loginData + ", expiration=" + expiration + '}'; } }
|
5. 需要保存到前端的数据
LoginData
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package io.coderyeah.basic.jwt;
import io.coderyeah.system.domain.Menu; import io.coderyeah.user.domain.LoginInfo; import io.coderyeah.user.domain.User; import lombok.Data;
import java.util.List;
@Data public class LoginData { private Logininfo logininfo; private List<String> permissions; private List<Menu> menus; }
|
六. 业务实现
1. 用户登录成功后使用jwt返回客户端数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| private Map<String, Object> loginSuccessJwtHandler(LoginInfo loginInfo) { final HashMap<String, Object> map = new HashMap<>(); final LoginData loginData = new LoginData(); loginInfo.setSalt(null); loginInfo.setPassword(null); map.put("loginInfo", loginInfo); loginData.setLoginInfo(loginInfo); if (loginInfo.getType() == 0) { final List<String> permissions = employeeMapper.getPermissionSnByLoginInfoId(loginInfo.getId()); map.put("permissions", permissions); loginData.setPermissions(permissions); List<Menu> menus = employeeMapper.getMenus(loginInfo.getId()); map.put("menus", menus); loginData.setMenus(menus); } try { final PrivateKey privateKey = RsaUtils.getPrivateKey(LoginInfoServiceImpl.class.getClassLoader().getResource("auth_rsa.pri").getFile()); final String token = JwtUtils.generateTokenExpireInMinutes(loginData, privateKey, 30); map.put("token", token); } catch (Exception e) { e.printStackTrace(); } return map; }
|
获取登录用户所有菜单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <resultMap id="menuMap" type="io.coderyeah.system.domain.Menu"> <id property="id" column="mid"/> <result property="name" column="mname"/> <result property="icon" column="micon"/> <collection property="children" ofType="io.coderyeah.system.domain.Menu"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="component" column="component"/> <result property="url" column="url"/> <result property="icon" column="icon"/> <result property="index" column="index"/> <result property="parentId" column="parent_id"/> <result property="intro" column="intro"/> <result property="state" column="state"/> </collection> </resultMap> <select id="getMenus" resultMap="menuMap"> select tm1.id mid, tm1.name mname, tm1.icon micon, tm2.* from t_menu tm1 join ( select tm.* from t_employee te join t_employee_role ter on te.id = ter.employee_id join t_role tr on tr.id = ter.role_id join t_role_menu trm on tr.id = trm.role_id join t_menu tm on trm.menu_id = tm.id where te.logininfo_id = #{id} ) tm2 on tm1.id = tm2.parent_id </select>
|
2. 账号登录(示例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Override public Map<String, Object> accountLogin(LoginDto loginDto) { if (StrUtil.isBlank(loginDto.getAccount()) || StrUtil.isBlank(loginDto.getCheckPass())) { throw new BusinessException("信息不能为空!!!"); } LoginInfo loginInfo = checkLogin(loginDto); if (!DigestUtil.md5Hex(loginInfo.getSalt() + loginDto.getCheckPass()).equals(loginInfo.getPassword())) { throw new BusinessException("账号或密码错误!!!"); } if (!loginInfo.getDisable()) { throw new BusinessException("该账号被禁用,请联系管理员!!!"); }
final Map<String, Object> map = loginSuccessJwtHandler(loginInfo); return map; }
|
3. 登录拦截器核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| String token = req.getHeader("token");
if (token != null) { LoginInfo info=null; try { final PublicKey publicKey = RsaUtils.getPublicKey(LoginInterceptor.class.getClassLoader().getResource("auth_rsa.pub").getFile()); final Payload<LoginData> payload = JwtUtils.getInfoFromToken(token, publicKey, LoginData.class); info = payload.getLoginData().getLoginInfo(); } catch (ExpiredJwtException e) { resp.setContentType("application/json;charset=UTF-8"); resp.getWriter().println("{\"success\":false,\"message\":\"timeout\"}"); return false; }
|
4.前端后置拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| axios.interceptors.response.use(res => { if (false === res.data.success && "noLogin" === res.data.message) { localStorage.removeItem("token"); localStorage.removeItem("loginInfo"); localStorage.removeItem("menus"); localStorage.removeItem("permissions"); router.push({path: '/login'}); } if (false === res.data.success && "noPermission" === res.data.message) { Message.warning('您没有访问权限') } if (false === res.data.success && "timeout" === res.data.message) { localStorage.removeItem("token"); localStorage.removeItem("loginInfo"); localStorage.removeItem("menus"); localStorage.removeItem("permissions"); Message.error('超时啦') } return res; }, error => { Promise.reject(error) })
|
5. 登录成功时需要存储信息到浏览器本地
1 2 3 4 5 6 7 8 9
| this.$message({ message: "登录成功", type: 'success' }); let {token, loginInfo, menus, permissions} = res.data.data localStorage.setItem("token", token) localStorage.setItem("loginInfo", JSON.stringify(loginInfo)) localStorage.setItem("menus", JSON.stringify(menus)) localStorage.setItem("permissions", JSON.stringify(permissions))
|
七. 动态菜单
1. router.js中的需要动态展示的路由配置需要去掉 (保留5个)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import Login from './views/Login.vue' import NotFound from './views/404.vue' import Home from './views/Home.vue' import echarts from './views/charts/echarts.vue' const ShopRegister = () => import('./views/ShopRegister')
let routes = [ { path: '/register', component: ShopRegister, name: '', hidden: true }, { path: '/login', component: Login, name: '', hidden: true }, { path: '/404', component: NotFound, name: '', hidden: true },
{ path: '/', component: Home, name: '图形化数据展示', iconCls: 'el-icon-s-data', children: [ {path: '/echarts', component: echarts, name: 'echarts'} ] }, { path: '*', hidden: true, redirect: {path: '/404'} } ];
export default routes;
|
2.login.vue页面登录成功之后需要刷新一下本地的路由缓存
1 2 3 4 5 6 7 8 9 10
| let {token, loginInfo, menus, permissions} = res.data.data localStorage.setItem("token", token) localStorage.setItem("loginInfo", JSON.stringify(loginInfo)) localStorage.setItem("menus", JSON.stringify(menus)) localStorage.setItem("permissions", JSON.stringify(permissions)) console.log(res.data);
this.$router.push({path: '/echarts'});
location.reload()
|
3. main.js配置动态路由(vue中的@符号表示在src路径下)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| initIndexRouters();
function initIndexRouters() { if (!localStorage.menus) { return; } if (router.options.routes.length > 5) { return; } let menus = localStorage.getItem('menus'); menus = JSON.parse(menus); let tempRouters = []; menus.forEach(menu => { let indexRouter = { path: '/', iconCls: menu.icon, name: menu.name, component: resolve => require(['@/views/Home'], resolve), children: [] } menu.children.forEach(cMenu => { let cr = { path: cMenu.url, name: cMenu.name, iconCls: cMenu.icon, component: resolve => require(['@/views/' + cMenu.component], resolve) } indexRouter.children.push(cr) }) tempRouters.push(indexRouter) router.options.routes.push(indexRouter) }) router.addRoutes(tempRouters); }
|
八. 按钮权限的实现
1. 获取当前登录用户的所有权限
前面登录成功会将登录人的权限数据封装permissions并返回给前端
1 2 3 4 5 6 7 8 9
| <select id="getPermissionSnByLoginInfoId" resultType="java.lang.String"> select tp.sn from t_employee te join t_employee_role ter on te.id = ter.employee_id join t_role tr on ter.role_id = tr.id join t_role_permission trp on tr.id = trp.role_id join t_permission tp on tp.id = trp.permission_id where te.logininfo_id = #{id} </select>
|
2. 自定义vue指令
语法格式:
1 2 3 4 5 6
| Vue.directive('指令名', { inserted: (el, binding, vnode) => { } });
|
3. 定义vue权限指令
可在src/common/js/permission.js
中定义权限指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import Vue from 'vue';
Vue.directive('perm', { inserted: (el, binding, vnode) => { const value = binding.value; let permissions = localStorage.getItem('permissions'); if (permissions) { let auths = JSON.parse(permissions); if (auths.join(",").indexOf(value) == -1) { el.parentNode.removeChild(el); } } } });
|
4. 在main.js中引用
1 2 3
| import permission from './common/js/permission'
import '@/common/js/permission'
|
5. 使用举例
1 2 3 4 5 6 7 8 9 10 11 12 13
| <el-form-item> <el-button type="primary" v-on:click="keywordQuery" v-perm="'department:list'" > 关键字查询 </el-button> </el-form-item> <el-form-item> <el-button type="primary" v-perm="'department:save'" @click="handleAdd"> 新增 </el-button> </el-form-item>
<el-button size="small" v-perm="'department:update'" @click="handleEdit(scope.$index, scope.row)">编辑</el-button> <el-button type="danger" size="small" v-perm="'department:delete'" @click="handleDel(scope.$index, scope.row)">删除</el-button>
|