菜单权限和按钮权限的实现,以及JWT的使用
一. 概述
- 一个系统中用户登陆之后看到的菜单,必须是当前登录人拥有权限的菜单才能展示,没有权限的菜单直接不显示;
- 使用无状态token方案,登录只存储了loginInfo信息,没有登录人相关的权限(菜单、按钮权限);
- 难道我们在登录的时候需要将登录人相关的权限信息也一并存储到redis中码?
答:如果人数多,并发量大,redis就不是一个好的方案:redis是一个内存数据库,内存有局限,数据量越大,内存占用率高,影响读取性能。
二. 无状态的token方案
-
后端验证登录信息成功之后,会生成一个随机串作为token将用户信息保存在redis,并将token令牌传回给浏览器;
-
后续浏览器只需要将token携带到服务器,服务器就可以根据浏览器的token令牌获取redis的信息
2.1. 如果获取不到信息,说明token令牌无效
2.2. 获取到信息,就向客户端返回请求的数据 -
缺点:
-
每次请求都需要查库【查询redis数据库】,效率低
-
如果redis保存的数据多【用户登录信息,当前用户的权限信息,当前用户的菜单信息】,会影响性能。
三. 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组成
JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了JWT字符串。就像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
第一部分:我们称它为头部(header),用于存放token类型和加密协议,一般都是固定的
第二部分:我们称其为载荷(payload),用户数据就存放在里面
第三部分:是签证(signature),主要用于服务端的验证
-
头部【header】: JSON格式,描述JWT的最基本的信息:
{ 'typ': 'JWT', 'alg': 'HS256' }
jwt 的头部承载两部分信息:
- 声明类型 , 告知这里是 jwt
- 声明加密的算法 通常直接使用 HMAC, SHA256
在使用过程中会对该
JSON
进行BASE64
编码,得到Jwt的第一部分:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
-
载荷【playload】 :JSON格式,用户数据就存放在里面,也需要BASE64编码:
{ "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的第三部分是一个签证信息,通过指定的算法生成哈希,以确保数据不会被篡改,这个签证信息由三部分组成:
head(base64编码后的) playload(base64编码后的) secret(秘钥)
这个部分需要
BASE64
加密后的header
和BASE64
加密后的payload
使用.
连接组成的字符串,然后通过header
中声明的加密方式进行加盐secret
组合加密,然后就构成了jwt
的第三部分:let encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload); let signature = HMACSHA256(encodedString, '密钥');
加密之后,得到signature签名信息,即
Jwt
的第三部分:
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
-
将这三部分用
.
连接成一个完整的字符串,就构成了最终的Jwt:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
四. 加密算法介绍
明文:加密之前的内容,原始内容
暗文:加密之后的内容
公钥:可见的公共的钥匙
私钥:不可见的私有的钥匙
1. 不可逆加密算法
特征:只能加密不能解密
技术:md5
作用:一般对登录密码处理,用于做密码比对
问题:只能加密,不能解密,不能用来对网络中传输的数据进行加密
2. 可逆对称加密算法
特征:
1. 可以加密,也可以解密
2. 加密和解密的密钥是同一个
实现:DES,AES
作用:对数据库密码进行加密
算法:
密文为s,加解密算法为AES,秘钥为k,明文为c
加密:s = AES(k,c)
解密:c = AES(k,s)
问题:
1. 数据可能会被其他人解密
2. 数据可能会被篡改
3. 可逆非对称加密算法
特征:
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. 依赖
<!-- JWT -->
<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
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;
/**
* RSA工具类 负责对RSA密钥的创建、读取功能(公钥和私钥)
*/
public class RsaUtils {
private static final int DEFAULT_KEY_SIZE = 2048; // 生成的大小
/**
* 从文件中读取公钥
*
* @param filename 公钥保存路径,相对于classpath
* @return 公钥对象
* @throws Exception
*/
public static PublicKey getPublicKey(String filename) throws Exception {
byte[] bytes = readFile(filename);
return getPublicKey(bytes);
}
/**
* 从文件中读取密钥
*
* @return 私钥对象
* @throws Exception
*/
public static PrivateKey getPrivateKey(String filename) throws Exception {
byte[] bytes = readFile(filename);
return getPrivateKey(bytes);
}
/**
* 获取公钥
*
* @param bytes 公钥的字节形式
* @return
* @throws Exception
*/
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;
}
}
/**
* 获取密钥
*
* @param bytes 私钥的字节形式
* @return
* @throws Exception
*/
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);
}
/**
* 根据密文,生存rsa公钥和私钥,并写入指定文件
*
* @param publicKeyFilename 公钥文件路径
* @param privateKeyFilename 私钥文件路径
* @param secret 生成密钥的密文
*/
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{
//1 生成秘钥对 xxx_rsa.pub xxxx_rsa
generateKey("E:\\springboot\\pethome\\src\\main\\resources\\auth_rsa.pub",
"E:\\springboot\\pethome\\src\\main\\resources\\auth_rsa.pri","coderyeah",2048);
}
}
3. JwtUtils
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;
/**
* JWT 密钥的解析和加密 工具类
*/
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()));
}
/**
* 私钥加密token
*
* @param userInfo 载荷中的数据
* @param privateKey 私钥
* @param expire 过期时间,单位分钟
* @return JWT
*/
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();
}
/**
* 私钥加密token
*
* @param userInfo 载荷中的数据
* @param privateKey 私钥
* @param expire 过期时间,单位秒
* @return JWT
*/
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();
}
/**
* 公钥解析token
*
* @param token 用户请求中的token
* @param publicKey 公钥
* @return Jws<Claims>
*/
private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
}
/**
* 获取token中的用户信息
*
* @param token 用户请求中的令牌
* @param publicKey 公钥
* @return 用户信息
*/
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;
}
/**
* 获取token中的载荷信息
*
* @param token 用户请求中的令牌
* @param publicKey 公钥
* @return 用户信息
*/
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 {
// 1 获取token
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);
// 2 解析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. 载荷数据
package io.coderyeah.basic.jwt;
import java.util.Date;
public class Payload<T> {
private String id; // jwt的id(token)
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
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 {
//1.登录信息对象Lonininfo对象 - 在前端显示用户数据信息的【
private Logininfo logininfo;
//2.当前登录人的所有权限的sn - 按钮或资源权限【没有访问该资源的按钮直接不显示】
private List<String> permissions;
//3.当前登录人的菜单信息 - 菜单权限【不同的人登录之后菜单是不一样的】
private List<Menu> menus;
}
六. 业务实现
1. 用户登录成功后使用jwt返回客户端数据
// 对登录成功的用户信息进行jwt加密
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;
}
获取登录用户所有菜单
<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. 账号登录(示例)
// 账户登录
@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("该账号被禁用,请联系管理员!!!");
}
// 生成token,并将登录信息保存到redis数据库,设置30有效
final Map<String, Object> map = loginSuccessJwtHandler(loginInfo);
return map;
}
3. 登录拦截器核心代码
//1.获取token
String token = req.getHeader("token");
//3.如果有token,通过token获取redis的登录信息
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) { //jwt过期时抛出的异常
resp.setContentType("application/json;charset=UTF-8");
resp.getWriter().println("{\"success\":false,\"message\":\"timeout\"}");
return false;
}
4.前端后置拦截器
//======================axios的后置拦截器【处理后台登录拦截的结果】====================//
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)
})
//======================axios的后置拦截器【处理后台登录拦截的结果】====================//
5. 登录成功时需要存储信息到浏览器本地
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个)
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: '', //不需显示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页面登录成功之后需要刷新一下本地的路由缓存
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路径下)
//处理页面刷新动态路由失效问题
initIndexRouters();
function initIndexRouters() {
// 判断本地是否有当前用户的菜单权限
if (!localStorage.menus) {
return;
}
//防止重复配置路由:5就是main.js中路由的个数 - 如果你的静态路由是6个这里要写成6
if (router.options.routes.length > 5) {
return;
}
// 获取本地登录用户的菜单
let menus = localStorage.getItem('menus');
// 将保存在本地的json字符串转化为json对象
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并返回给前端
<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指令
语法格式:
Vue.directive('指令名', {
// 当被绑定的元素插入到 DOM 中时……
inserted: (el, binding, vnode) => {
// 需要完成的操作。。。
}
});
3. 定义vue权限指令
-
可在
src/common/js/permission.js
中定义权限指令import Vue from 'vue'; // 注册一个全局自定义指令 `v-perm` Vue.directive('perm', { // 当被绑定的元素插入到 DOM 中时…… inserted: (el, binding, vnode) => { //获取自定义标签v-perm的值 const value = binding.value; //json格式字符串 let permissions = localStorage.getItem('permissions'); if (permissions) { //转成json对象 let auths = JSON.parse(permissions); //将数组中的每一个元素按照,号进行拼接 然后 再检索 if (auths.join(",").indexOf(value) == -1) { //如果不包含权限就移除 el.parentNode.removeChild(el); } } } });
4. 在main.js中引用
import permission from './common/js/permission'
//@ 等价于 /src 这个目录,避免写麻烦又易错的相对路径
import '@/common/js/permission'
5. 使用举例
<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>
//将数组中的每一个元素按照,号进行拼接 然后 再检索
if (auths.join(",").indexOf(value) == -1) {
//如果不包含权限就移除
el.parentNode.removeChild(el);
}
}
}
});
#### 4. 在main.js中引用
```js
import permission from './common/js/permission'
//@ 等价于 /src 这个目录,避免写麻烦又易错的相对路径
import '@/common/js/permission'
5. 使用举例
<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>
更多推荐