首先感謝PanJiaChen 的 vue-admin-template 模板,我是一名java程序員,因此前端的技術不怎麼樣。vue.js也只是會用一點,很是感謝PanJiaChen 的模板支持。javascript
已經整合好的模板地址:https://github.com/thousmile/...css
若是你們以爲寫的不錯,就請點亮我GitHub的心心。很是感謝!html
若是你們在搭建過程當中遇到問題,歡迎找我前端
GitHub : https://github.com/thousmilevue
碼雲: https://gitee.com/thousmilejava
QQ: 932560435mysql
大部分的權限管理系統都是5張表結構(一樣咱們這裏也採用這種方式)webpack
這裏咱們主要看權限表(t_sys_permission )ios
最重要的就是resources和type字段,這兩個字段在後面和vue.js整合的時候會用到,git
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.7</version> </dependency> <!-- ————————————— security開始————————————————————— --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> </dependencies>
jwt: header: jwtHeader #jwt的請求頭 secret: eyJleHAiOjE1NDMyMDUyODUsInN1YiI6ImFkbWluIiwiY3Jl #jwt的加密字符串 expiration: 3600000 #jwt token有效時間(毫秒) route: login: /auth/login #登陸地址 refresh: /auth/refresh #刷新token地址 register: /auth/register #註冊的地址
package com.ifsaid.admin.common.jwt; import com.fasterxml.jackson.annotation.JsonIgnore; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; /** * @author: Wang Chen Chen * @Date: 2018/10/29 14:08 * @describe: * @version: 1.0 */ public class JwtUser implements UserDetails { private String username; private String password; private Integer state; private Collection<? extends GrantedAuthority> authorities; public JwtUser() { } public JwtUser(String username, String password, Integer state, Collection<? extends GrantedAuthority> authorities) { this.username = username; this.password = password; this.state = state; this.authorities = authorities; } @Override public String getUsername() { return username; } @JsonIgnore @Override public String getPassword() { return password; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @JsonIgnore @Override public boolean isAccountNonExpired() { return true; } @JsonIgnore @Override public boolean isAccountNonLocked() { return state == 1; } @JsonIgnore @Override public boolean isCredentialsNonExpired() { return true; } @JsonIgnore @Override public boolean isEnabled() { return true; } }
package com.ifsaid.admin.common.jwt; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import java.io.Serializable; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * @author: Wang Chen Chen * @Date: 2018/10/29 14:10 * @describe: * @version: 1.0 */ @Data @ConfigurationProperties(prefix = "jwt") @Component public class JwtTokenUtil implements Serializable { private String secret; private Long expiration; private String header; /** * 從數據聲明生成令牌 * * @param claims 數據聲明 * @return 令牌 */ private String generateToken(Map<String, Object> claims) { Date expirationDate = new Date(System.currentTimeMillis() + expiration); return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact(); } /** * 從令牌中獲取數據聲明 * * @param token 令牌 * @return 數據聲明 */ private Claims getClaimsFromToken(String token) { Claims claims; try { claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } catch (Exception e) { claims = null; } return claims; } /** * 生成令牌 * * @param userDetails 用戶 * @return 令牌 */ public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(2); claims.put("sub", userDetails.getUsername()); claims.put("created", new Date()); return generateToken(claims); } /** * 從令牌中獲取用戶名 * * @param token 令牌 * @return 用戶名 */ public String getUsernameFromToken(String token) { String username; try { Claims claims = getClaimsFromToken(token); username = claims.getSubject(); } catch (Exception e) { username = null; } return username; } /** * 判斷令牌是否過時 * * @param token 令牌 * @return 是否過時 */ public Boolean isTokenExpired(String token) { try { Claims claims = getClaimsFromToken(token); Date expiration = claims.getExpiration(); return expiration.before(new Date()); } catch (Exception e) { return false; } } /** * 刷新令牌 * * @param token 原令牌 * @return 新令牌 */ public String refreshToken(String token) { String refreshedToken; try { Claims claims = getClaimsFromToken(token); claims.put("created", new Date()); refreshedToken = generateToken(claims); } catch (Exception e) { refreshedToken = null; } return refreshedToken; } /** * 驗證令牌 * * @param token 令牌 * @param userDetails 用戶 * @return 是否有效 */ public Boolean validateToken(String token, UserDetails userDetails) { JwtUser user = (JwtUser) userDetails; String username = getUsernameFromToken(token); return (username.equals(user.getUsername()) && !isTokenExpired(token)); } }
package com.ifsaid.admin.common.jwt; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author: Wang Chen Chen * @Date: 2018/10/29 14:29 * @describe: * @version: 1.0 */ @Slf4j @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // 從這裏開始獲取 request 中的 jwt token String authHeader = request.getHeader(jwtTokenUtil.getHeader()); log.info("authHeader:{}", authHeader); // 驗證token是否存在 if (authHeader != null && StringUtils.isNotEmpty(authHeader)) { // 根據token 獲取用戶名 String username = jwtTokenUtil.getUsernameFromToken(authHeader); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { // 經過用戶名 獲取用戶的信息 UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); // 驗證token和用戶是否匹配 if (jwtTokenUtil.validateToken(authHeader, userDetails)) { // 而後把構造UsernamePasswordAuthenticationToken對象 // 最後綁定到當前request中,在後面的請求中就能夠獲取用戶信息 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } } chain.doFilter(request, response); } }
package com.ifsaid.admin.service.impl; import com.ifsaid.admin.common.jwt.JwtUser; import com.ifsaid.admin.entity.SysRole; import com.ifsaid.admin.entity.SysUser; import com.ifsaid.admin.mapper.SysUserMapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.List; import java.util.stream.Collectors; /** * @author: Wang Chen Chen * @Date: 2018/10/29 14:15 * @describe: * @version: 1.0 */ @Slf4j @Service public class JwtUserDetailsServiceImpl implements UserDetailsService { @Autowired private SysUserMapper sysUserMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 根據用戶名獲取數據庫的用戶信息 SysUser sysUser = sysUserMapper.selectByUserName(username); if (sysUser == null || StringUtils.isEmpty(sysUser.getUid())) { throw new UsernameNotFoundException(String.format("'%s'.這個用戶不存在", username)); } else { // 根據數據庫中的用戶信息,構建JwtUser對象 List<SimpleGrantedAuthority> collect = sysUser.getRoles().stream().map(SysRole::getName).map(SimpleGrantedAuthority::new).collect(Collectors.toList()); return new JwtUser(sysUser.getUsername(), sysUser.getPassword(), sysUser.getState(), collect); } } }
package com.ifsaid.admin.config; import com.ifsaid.admin.common.jwt.JwtAuthenticationTokenFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.BeanIds; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsUtils; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; /** * @author: Wang Chen Chen * @Date: 2018/10/29 11:41 * @describe: * @version: 1.0 */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebMvcConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // 這裏記住必定要從新父類的對象,否則在注入 AuthenticationManager時會找不到, // 默認spring security 沒有給咱們注入到容器中 @Bean(name = BeanIds.AUTHENTICATION_MANAGER) @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Autowired public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder()); } /** * @describe spring Security的核心配置 * @date 2018/10/29 * @author Wang Chen Chen */ @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and().authorizeRequests() .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 把不須要認證的接口暴露出去。登陸,刷新token, .antMatchers("/auth/**").permitAll() .anyRequest().authenticated() .and().headers().cacheControl(); // 注入咱們剛纔寫好的 jwt過濾器 httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // 這塊是配置跨域請求的 ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests(); //讓Spring security放行全部preflight request registry.requestMatchers(CorsUtils::isPreFlightRequest).permitAll(); } // 這塊是配置跨域請求的 @Bean public CorsFilter corsFilter() { final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource(); final CorsConfiguration cors = new CorsConfiguration(); cors.setAllowCredentials(true); cors.addAllowedOrigin("*"); cors.addAllowedHeader("*"); cors.addAllowedMethod("*"); urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", cors); return new CorsFilter(urlBasedCorsConfigurationSource); } // 密碼加密 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
登陸的接口
package com.ifsaid.admin.service; import com.ifsaid.admin.common.exception.UserExistsException; import com.ifsaid.admin.common.service.IBaseService; import com.ifsaid.admin.entity.SysUser; import com.ifsaid.admin.vo.SysUserVo; import org.springframework.security.core.AuthenticationException; /** * <p> * [權限管理] 用戶表 服務類 * </p> * * @author wang chen chen * @since 2018-10-23 */ public interface ISysUserService extends IBaseService<SysUser, String> { SysUser findByUsername(String username); /** * 獲取用戶詳細信息 * @param username * @return 操做結果 */ SysUserVo findUserInfo(String username); /** * 用戶登陸 * * @param username 用戶名 * @param password 密碼 * @return 操做結果 */ String login(String username, String password) throws AuthenticationException; /** * 用戶註冊 * * @param user 用戶信息 * @return 操做結果 */ Integer register(SysUser sysUser) throws UserExistsException; /** * 刷新密鑰 * * @param oldToken 原密鑰 * @return 新密鑰 */ String refreshToken(String oldToken); }
package com.ifsaid.admin.service.impl; import com.ifsaid.admin.common.exception.UserExistsException; import com.ifsaid.admin.common.jwt.JwtTokenUtil; import com.ifsaid.admin.common.service.impl.BaseServiceImpl; import com.ifsaid.admin.entity.SysRole; import com.ifsaid.admin.entity.SysUser; import com.ifsaid.admin.mapper.SysUserMapper; import com.ifsaid.admin.service.ISysRoleService; import com.ifsaid.admin.service.ISysUserService; import com.ifsaid.admin.utils.TreeBuilder; import com.ifsaid.admin.vo.ButtonVo; import com.ifsaid.admin.vo.MenuVo; import com.ifsaid.admin.vo.SysUserVo; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; /** * <p> * [權限管理] 用戶表 服務實現類 * </p> * * @author wang chen chen * @since 2018-10-23 */ @Slf4j @Service public class SysUserServiceImpl extends BaseServiceImpl<SysUser, String, SysUserMapper> implements ISysUserService { @Autowired private ISysRoleService sysRoleService; @Override public SysUser findByUsername(String username) throws UsernameNotFoundException { if (StringUtils.isEmpty(username)) { throw new UsernameNotFoundException("用戶名不能夠爲空!"); } SysUser sysUser = baseMapper.selectByUserName(username); if (sysUser == null || StringUtils.isEmpty(sysUser.getUid()) || StringUtils.isEmpty(sysUser.getUsername())) { throw new UsernameNotFoundException("用戶名不存在!"); } log.info("SysUserServiceImpl......... {}", sysUser); return sysUser; } @Override public SysUserVo findUserInfo(String username) { /** * 獲取用戶信息 */ SysUser sysUser = findByUsername(username); /** * 獲取當前用戶的全部角色 */ Set<SysRole> sysRoles = sysRoleService.selectByUserName(username); /** * 在這裏個人想法是,構建一個按鈕權限列表 * 再構建一個菜單權限列表 * 這樣的咱們在前端的寫的時候,就不用解析的很麻煩了 * 由於權限表是一張表,在這裏解析好了之後, * 至關前端少作一點工做,固然這也能夠放到前端去解析權限列表 */ Set<ButtonVo> buttonVos = new HashSet<>(); Set<MenuVo> menuVos = new HashSet<>(); sysRoles.forEach(role -> { log.info("role: {}", role.getDescribe()); role.getPermissions().forEach(permission -> { if (permission.getType().toLowerCase().equals("button")) { /* * 若是權限是按鈕,就添加到按鈕裏面 * */ buttonVos.add(new ButtonVo(permission.getPid(), permission.getResources(), permission.getTitle())); } if (permission.getType().toLowerCase().equals("menu")) { /* * 若是權限是菜單,就添加到菜單裏面 * */ menuVos.add(new MenuVo(permission.getPid(), permission.getFather(), permission.getIcon(), permission.getResources(), permission.getTitle())); } }); }); /** * 注意這個類 TreeBuilder。由於的vue router是以遞歸的形式呈現菜單 * 因此咱們須要把菜單跟vue router 的格式一一對應 而按鈕是不須要的 */ SysUserVo sysUserVo = new SysUserVo(sysUser.getUid(), sysUser.getAvatar(), sysUser.getNickname(), sysUser.getUsername(), sysUser.getMail(), sysUser.getAddTime(), sysUser.getRoles(), buttonVos, TreeBuilder.findRoots(menuVos)); return sysUserVo; } // 若是在WebSecurityConfigurerAdapter中,沒有從新,這裏就會報注入失敗的異常 @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private PasswordEncoder passwordEncoder; @Override public String login(String username, String password) { UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password); Authentication authentication = authenticationManager.authenticate(upToken); SecurityContextHolder.getContext().setAuthentication(authentication); UserDetails userDetails = userDetailsService.loadUserByUsername(username); return jwtTokenUtil.generateToken(userDetails); } @Override public Integer register(SysUser sysUser) throws UserExistsException { String username = sysUser.getUsername(); if (findByUsername(username) != null) { throw new UserExistsException(String.format("'%s' 這個用用戶已經存在了", username)); } String rawPassword = sysUser.getPassword(); sysUser.setPassword(passwordEncoder.encode(rawPassword)); sysUser.setUpTime(new Date()); sysUser.setAddTime(new Date()); return baseMapper.insertSelective(sysUser); } @Override public String refreshToken(String oldToken) { if (!jwtTokenUtil.isTokenExpired(oldToken)) { return jwtTokenUtil.refreshToken(oldToken); } return "error"; } }
package com.ifsaid.admin.controller; import com.ifsaid.admin.service.ISysUserService; import com.ifsaid.admin.vo.Result; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.Map; /** * @author: Wang Chen Chen * @Date: 2018/10/29 10:49 * @describe: * @version: 1.0 */ @RestController public class AuthController { @Autowired private ISysUserService sysUserService; @PostMapping(value = "${jwt.route.login}") public Result<String> login(@RequestBody Map<String, String> map) { String username = map.get("username"); String password = map.get("password"); if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { return Result.error401("用戶或者密碼不能爲空!", null); } return Result.success("登陸成功", sysUserService.login(username, password)); } @PostMapping(value = "${jwt.route.refresh}") public Result<String> refresh(@RequestHeader("${jwt.header}") String token) { return Result.success("刷新token成功!", sysUserService.refreshToken(token)); } }
到這裏咱們已經獲取到 token。
{ "status": 200, "message": "success", "data": { "uid": "3BDDD3B7B3AF4BA2A8FA0EFEB585597B", "avatar": "https://ifsaid-blog.oss-cn-shenzhen.aliyuncs.com/images/2018/9/28/3BDDD3B7B3AF4BA2A8FA0EFEB585597B.jpg", "nickname": "系統管理員", "password": null, "username": "admin", "phone": null, "mail": "thousmile@163.com", "state": null, "addTime": 1540267742000, "upTime": null, "dept": null, "sysDept": null, "roles": [ { "rid": 3, "describe": "超級管理員", "name": "ROLE_ROOT", "state": null, "upTime": null, "addTime": null, "permissions": null }, { "rid": 6, "describe": "測試用戶", "name": "ROLE_USER", "state": null, "upTime": null, "addTime": null, "permissions": null } ], "buttons": [ { "pid": 47, "resources": "dept:update", "title": "修改部門" }, { "pid": 41, "resources": "role:new", "title": "新增角色" }, { "pid": 34, "resources": "perm:delete", "title": "刪除權限" }, { "pid": 38, "resources": "user:delete", "title": "刪除用戶" }, { "pid": 40, "resources": "user:view", "title": "查看用戶" }, { "pid": 44, "resources": "role:view", "title": "查看角色" }, { "pid": 42, "resources": "role:delete", "title": "刪除角色" }, { "pid": 35, "resources": "perm:update", "title": "修改權限" }, { "pid": 48, "resources": "dept:view", "title": "查看部門" }, { "pid": 37, "resources": "user:new", "title": "新增用戶" }, { "pid": 33, "resources": "perm:new", "title": "新增權限" }, { "pid": 43, "resources": "role:update", "title": "修改角色" }, { "pid": 45, "resources": "dept:new", "title": "新增部門" }, { "pid": 39, "resources": "user:update", "title": "修改用戶" }, { "pid": 36, "resources": "perm:view", "title": "查看權限" }, { "pid": 46, "resources": "dept:delete", "title": "刪除部門" } ], "menus": [ { "pid": 2, "father": 0, "icon": "sys_set", "resources": "sys", "title": "系統設置", "children": [ { "pid": 51, "father": 2, "icon": "sys_wechat", "resources": "wechat", "title": "微信設置", "children": null }, { "pid": 52, "father": 2, "icon": "sys_backstage", "resources": "backstage", "title": "後臺設置", "children": null } ] }, { "pid": 4, "father": 0, "icon": "time_task", "resources": "task", "title": "定時任務", "children": null }, { "pid": 1, "father": 0, "icon": "pre_admin", "resources": "pre", "title": "權限設置", "children": [ { "pid": 32, "father": 1, "icon": "dept__admin", "resources": "dept", "title": "部門管理", "children": null }, { "pid": 30, "father": 1, "icon": "user__admin", "resources": "user", "title": "用戶管理", "children": null }, { "pid": 31, "father": 1, "icon": "role__admin", "resources": "role", "title": "角色管理", "children": null }, { "pid": 29, "father": 1, "icon": "prem_admin", "resources": "perm", "title": "權限管理", "children": null } ] }, { "pid": 3, "father": 0, "icon": "sys_control", "resources": "control", "title": "系統監控", "children": [ { "pid": 50, "father": 3, "icon": "control_logs", "resources": "logs", "title": "系統日誌", "children": null }, { "pid": 49, "father": 3, "icon": "control_database", "resources": "database", "title": "數據庫監控", "children": null } ] } ] }, "error": null, "timestamp": 1540901472256 }
能夠劃分爲三個主要部分
1.用戶信息
2.菜單列表(遞歸形式)
3.按鈕列表(List列表形式)
先看一下成果。
從GitHub上面拉去vue-element-admin
# 克隆項目 git clone https://github.com/PanJiaChen/vue-admin-template.git # 建議不要用cnpm 安裝有各類詭異的bug 能夠經過以下操做解決npm速度慢的問題 npm install --registry=https://registry.npm.taobao.org # Serve with hot reload at localhost:9528 npm run dev
1.改登陸的api接口指向咱們spring boot 的登陸 目錄 /src/api/login.js
import request from '@/utils/request' // 登陸 export function login(username, password) { return request({ url: '/auth/login', method: 'post', data: { username, password }}) } // 獲取用戶信息 export function getInfo(token) { return request({ url: 'user/info', method: 'get' }) } // 登出 export function logout() { return request({ url: 'user/logout', method: 'post' }) }
import axios from 'axios' import { Message, MessageBox } from 'element-ui' import store from '../store' import { getToken } from '@/utils/auth' // 建立axios實例 const service = axios.create({ baseURL: process.env.BASE_API, // api 的 base_url timeout: 5000 // 請求超時時間 }) // request攔截器 service.interceptors.request.use( config => { if (getToken() !== '') { config.headers['jwtHeader'] = getToken() // 讓每一個請求攜帶自定義token 請根據實際狀況自行修改 } return config }, error => { // Do something with request error console.log(error) // for debug Promise.reject(error) } ) // response 攔截器 service.interceptors.response.use( response => { /** * code爲非20000是拋錯 可結合本身業務進行修改 */ const res = response.data if (res.status !== 200) { Message({ message: res.message, type: 'error', duration: 5 * 1000 }) // 50008:非法的token; 50012:其餘客戶端登陸了; 50014:Token 過時了; if (res.code === 400 || res.code === 401 || res.code === 402) { MessageBox.confirm('你已被登出,能夠取消繼續留在該頁面,或者從新登陸', '肯定登出', { confirmButtonText: '從新登陸', cancelButtonText: '取消', type: 'warning' } ).then(() => { store.dispatch('FedLogOut').then(() => { location.reload() // 爲了從新實例化vue-router對象 避免bug }) }) } return Promise.reject('error') } else { return response.data } }, error => { console.log('err' + error) // for debug Message({ message: error.message, type: 'error', duration: 5 * 1000 }) return Promise.reject(error) } ) export default service
// dev.env.js 'use strict' const merge = require('webpack-merge') const prodEnv = require('./prod.env') module.exports = merge(prodEnv, { NODE_ENV: '"development"', BASE_API: '"http://localhost:8080"', }) // prod.env.js 'use strict' module.exports = { NODE_ENV: '"production"', BASE_API: '"http://localhost:8080"', }
import { login, logout, getInfo } from '@/api/login' import { getToken, setToken, removeToken } from '@/utils/auth' const user = { state: { token: getToken(), nickname: '', avatar: '', uid: '', user: {}, roles: [], menus: [], // 菜單權限 buttons: [] // 安裝權限 }, mutations: { SET_TOKEN: (state, token) => { state.token = token }, SET_INFO: (state, user) => { state.nickname = user.nickname state.avatar = user.avatar state.uid = user.uid state.user = user }, SET_ROLES: (state, roles) => { state.roles = roles }, SET_MENUS: (state, menus) => { state.menus = menus }, SET_BUTTONS: (state, buttons) => { state.buttons = buttons } }, actions: { // 登陸 Login({ commit }, userInfo) { const username = userInfo.username.trim() return new Promise((resolve, reject) => { login(username, userInfo.password).then(res => { setToken(res.data) commit('SET_TOKEN', res.data) resolve() }).catch(error => { reject(error) }) }) }, // 獲取用戶信息 GetInfo({ commit, state }) { return new Promise((resolve, reject) => { getInfo(state.token).then(res => { const data = res.data if (data.roles && data.roles.length > 0) { // 驗證返回的roles是不是一個非空數組 commit('SET_ROLES', data.roles) } else { reject('getInfo: roles must be a non-null array !') } commit('SET_MENUS', data.menus) commit('SET_BUTTONS', data.buttons) // 設置用戶信息 commit('SET_INFO', data) resolve(res) }).catch(error => { reject(error) }) }) }, // 登出 LogOut({ commit, state }) { return new Promise((resolve, reject) => { logout(state.token).then(() => { commit('SET_INFO', '') commit('SET_TOKEN', '') commit('SET_ROLES', []) removeToken() resolve() }).catch(error => { reject(error) }) }) }, // 前端 登出 FedLogOut({ commit }) { return new Promise(resolve => { commit('SET_TOKEN', '') removeToken() resolve() }) } } } export default user
import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) import Layout from '@/views/layout/Layout' // 默認的路由鏈。全部用戶公共的路由 export const constantRouterMap = [ { path: '/login', name: 'Login', component: () => import('@/views/login/index'), hidden: true }, { path: '/', component: Layout, redirect: '/dashboard', name: 'Dashboard', hidden: true, children: [{ path: 'dashboard', component: () => import('@/views/dashboard/index') }, { path: 'userinfo', name: 'UserInfo', component: () => import('@/views/dashboard/userinfo') }] }, { path: '/error', component: Layout, redirect: '/error/404', hidden: true, children: [{ path: '404', component: () => import('@/views/error/404/index') }, { path: '401', component: () => import('@/views/error/401/index') }] }, { path: '*', redirect: '/error/404', hidden: true } ] export default new Router({ // mode: 'history', //後端支持可開 scrollBehavior: () => ({ y: 0 }), routes: constantRouterMap }) // 異步掛載的路由 // 動態須要根據權限加載的路由表 // 這個路由鏈,根據數據庫中的一一對應,也就是說這是一個最完整的路由鏈, // 根據登陸的用戶權限的不一樣,而後從中提取出對應當前用戶的路由添加到vue router中 // meta:屬性中resources屬性最爲重要,用meta.resources和咱們獲取用戶信息中menus.resources匹配 export const asyncRouterMap = [ { path: '/pre', component: Layout, name: 'pre', meta: { resources: 'pre', title: '權限管理' }, children: [ { path: 'index', component: () => import('@/views/pre/perm/index'), name: 'perm', meta: { resources: 'perm' } }, { path: 'user', component: () => import('@/views/pre/user/index'), name: 'user', meta: { resources: 'user' } }, { path: 'role', component: () => import('@/views/pre/role/index'), name: 'role', meta: { resources: 'role' } }, { path: 'dept', component: () => import('@/views/pre/dept/index'), name: 'dept', meta: { resources: 'dept' } } ] }, { path: '/sys', component: Layout, name: 'sys', meta: { resources: 'sys', title: '系統設置' }, children: [ { path: 'index', component: () => import('@/views/sys/backstage/index'), name: 'backstage', meta: { resources: 'backstage' } }, { path: 'wechat', component: () => import('@/views/sys/wechat/index'), name: 'wechat', meta: { resources: 'wechat' } } ] }, { path: 'external-link', component: Layout, name: 'Link', meta: { resources: 'control', title: '系統監控', icon: 'link' }, children: [{ path: 'https://www.baidu.com/', meta: { resources: 'logs', title: '系統日誌', icon: 'link' } }, { path: 'https://v.qq.com/', meta: { resources: 'database', title: '數據庫監控', icon: 'link' } } ] } ]
// store/permission.js import { asyncRouterMap, constantRouterMap } from '@/router' /** * * @param {Array} userRouter 後臺返回的用戶權限json * @param {Array} allRouter 前端配置好的全部動態路由的集合 * @return {Array} realRoutes 過濾後的路由 */ export function recursionRouter(userRouter = [], allRouter = []) { var realRoutes = [] allRouter.forEach((v, i) => { userRouter.forEach((item, index) => { if (item.resources === v.meta.resources) { if (item.children && item.children.length > 0) { v.children = recursionRouter(item.children, v.children) } v.meta.title = item.title v.meta.icon = item.icon realRoutes.push(v) } }) }) return realRoutes } /** * * @param {Array} routes 用戶過濾後的路由 * * 遞歸爲全部有子路由的路由設置第一個children.path爲默認路由 */ export function setDefaultRoute(routes) { routes.forEach((v, i) => { if (v.children && v.children.length > 0) { v.redirect = { name: v.children[0].name } setDefaultRoute(v.children) } }) } const permission = { state: { routers: constantRouterMap, // 這是默認權限列表 好比404 500等路由 dynamicRouters: [] // 這是經過後臺獲取的權利列表 }, mutations: { SET_ROUTERS: (state, routers) => { state.dynamicRouters = routers state.routers = constantRouterMap.concat(routers) } }, actions: { GenerateRoutes({ commit }, data) { return new Promise(resolve => { // 把傳進來的menus 列表。用recursionRouter方法進行遞歸遍歷出來,存入vuex中 commit('SET_ROUTERS', 。(data, asyncRouterMap)) resolve() }) } } } export default permission
import router from './router' import store from './store' import NProgress from 'nprogress' // Progress 進度條 import 'nprogress/nprogress.css'// Progress 進度條樣式 import { Message } from 'element-ui' import { getToken } from '@/utils/auth' // 驗權 const whiteList = ['/login'] // 不重定向白名單 router.beforeEach((to, from, next) => { NProgress.start() if (getToken()) { 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.menus.length === 0) { // 拉取用戶信息(請確保在 GetInfo 方法中 已經獲取到菜單列表) store.dispatch('GetInfo').then(res => { // 動態設置路由(把上一步獲取到的用戶傳遞給 GenerateRoutes方法 解析) store.dispatch('GenerateRoutes', store.getters.menus).then(r => { // 獲取已經解析好的路由列表,動態添加到router中 router.addRoutes(store.getters.dynamicRouters) // hack方法 確保addRoutes已完成 next({ ...to, replace: true }) }) }).catch((err) => { store.dispatch('FedLogOut').then(() => { Message.error(err || 'Verification failed, please login again') next({ path: '/' }) }) }) } else { next() } } } else { if (whiteList.indexOf(to.path) !== -1) { next() } else { next(`/login?redirect=${to.path}`) // 不然所有重定向到登陸頁 NProgress.done() } } }) router.afterEach(() => { NProgress.done() // 結束Progress })
目錄: /src/views/layout/components/Sidebar/index.vue
<template> <el-scrollbar wrap-class="scrollbar-wrapper"> <logo :is-collapse="isCollapse"/> <el-menu :show-timeout="200" :default-active="$route.path" :collapse="isCollapse" mode="vertical" background-color="#304156" text-color="#bfcbd9" active-text-color="#409EFF" > <!-- 主要就是在這裏位置 menu_routers --> <sidebar-item v-for="route in menu_routers" :key="route.path" :item="route" :base-path="route.path"/> </el-menu> </el-scrollbar> </template> <script> import { mapGetters } from 'vuex' import SidebarItem from './SidebarItem' import logo from './Logo' export default { components: { SidebarItem, logo }, computed: { // 在這裏從vuex 中獲取到菜單列表 ...mapGetters([ 'menu_routers', 'sidebar' ]), isCollapse() { return !this.sidebar.opened } } } </script>
import Vue from 'vue' import store from '@/store' /** 權限指令**/ Vue.directive('has', { bind: function(el, binding) { if (!Vue.prototype.$_has(binding.value)) { el.parentNode.removeChild(el) } } }) // 權限檢查方法 Vue.prototype.$_has = function(value) { // 獲取用戶按鈕權限 let isExist = false const dynamicButtons = store.getters.buttons if (dynamicButtons === undefined || dynamicButtons === null || dynamicButtons.length < 1) { return isExist } dynamicButtons.forEach(button => { if (button.resources === value) { isExist = true return isExist } }) return isExist }
<!-- v-has中的值就是咱們獲取用戶信息時buttons的resources屬性。進行對比, --> <el-button v-has="'perm:new'" class="btns">添加</el-button> <el-button v-has="'perm:haha'" class="btns">哈哈</el-button>
到了這裏已經完成了 spring boot ,security ,jwt,vue-admin-template的整合 一個完整的先後端分類,動態權限,控制到按鈕的 後臺管理系統就完成了
若是你們以爲寫的不錯,就請點亮我GitHub的心心。很是感謝!