Спасибо PanJiaChen за шаблон vue-admin-template. Я являюсь Java-разработчиком, поэтому мои навыки в фронтенде не очень сильны. Vue.js я использую только на базовом уровне, поэтому очень благодарен PanJiaChen за предоставленный шаблон.
Интегрированный шаблон доступен по адресу: https://github.com/thousmile/spring-admin-vue
Если вам понравится этот проект, пожалуйста, оставьте звезды на моем GitHub. Большое спасибо!
Если у вас возникнут вопросы при сборке, пожалуйста, обращайтесь ко мне.
GitHub: https://github.com/thousmile
Gitee: https://gitee.com/thousmile
QQ: 932560435
Большинство систем управления правами имеют структуру из пяти таблиц (также мы используем этот подход здесь).
В данном случае мы будем рассматривать таблицу с правами (t_sys_permission).
Самыми важными являются поля resources и type, которые будут использоваться при интеграции с vue.js.
<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>
<!-- ————————————— безопасность начало————————————————————— -->
<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 в файл application.yml
```
jwt:
header: jwtHeader #заголовок JWT
secret: eyJleHAiOjE1NDMyMDUyODUsInN1YiI6ImFkbWluIiwiY3Jl #строка шифрования JWT
expiration: 3600000 #время жизни JWT токена (миллисекунды)
маршрут:
вход: /auth/login #адрес входа
обновление: /auth/refresh #адрес обновления токена
регистрация: /auth/register #адрес регистрации
```
### **Класс JwtUser реализует интерфейс UserDetails, определенный в spring security**
```
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;
```
```java
public class JwtUser {
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;
}
}
```
### **Класс JwtTokenUtil для работы с токенами**```java
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: tworocznia/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;
}```markdown
### Создание拦截器 JwtAuthenticationTokenFilter для аутентификации JWT
```java
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;
```
### Создание фильтра JwtAuthenticationTokenFilter для аутентификации JWT
```java
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
* @description:
* @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 {
// Здесь начинается получение jwt токена из запроса
String authHeader = request.getHeader(jwtTokenUtil.getHeader());
log.info("authHeader:{}", authHeader);
// Проверка наличия токена
if (authHeader != null && StringUtils.isNotEmpty(authHeader)) {
// Получение имени пользователя из токена
String username = jwtTokenUtil.getUsernameFromToken(authHeader);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// Получение информации о пользователе по имени
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
// Проверка соответствия токена и пользователя
if (jwtTokenUtil.validateToken(authHeader, userDetails)) {
// Создание объекта UsernamePasswordAuthenticationToken
// Затем привязка к текущему запросу, чтобы в последующих запросах можно было получить информацию о пользователе
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
}
}
```
### **Реализация интерфейса UserDetailsService из Spring Security**```java
package com.ifsaid.admin.service.impl;
``````markdown
Импорты:
```java
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;
``````java
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);
}
}
}
```
### **Настройка Spring Security**
```java
package com.ifsaid.admin.config;
``````java
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;
```
```java
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;
```
```java
/**
* @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()
// Открыть для доступа без аутентификации определенные маршруты. Например, маршруты для входа, обновления токена.
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
.and().headers().cacheControl();
// Внедрить jwt-фильтр, который мы создали ранее
httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов с��域请求的配置
@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);
}
```
```markdown
// Это конфигурация для поддержки запросов сПримечание: В данном контексте, текст был переведен на русский язык, сохраняя структуру и смысл оригинала. // Шифрование пароля
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
```
### **Интерфейс для входа**
```java
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);
}
```
### **Класс реализации для входа**
```java
package com.ifsaid.admin.service.impl;
``````java
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;
``````java
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.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("Токен успешно обновлен!", sysUserService.refreshToken(token));
}
}
```

**На этом этапе мы уже получили токен.**
### **Затем используем токен для получения информации о пользователе**
### **Формат JSON**
```
{
"status": 200,
"message": "успех",
"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": "Удалить роль"
}
]
}
}
``````json
{
"pid": 35,
"resources": "perm:update",
"title": "Изменить разрешение"
},
{
"pid": 48,
"resources": "dept:view",
"title": "Просмотреть отдел"
},
{
"pid": 37,
"resources": "dept:delete",
"title": "Удалить отдел"
}
]
}
}
``````markdown
"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": "Настройки WeChat",
"children": null
},
{
"pid": 52,
"father": 2,
"icon": "sys_backstage",
"resources": "backstage",
"title": "Настройки后台",
"children": null
}
]
}
]
``````markdown
{
"code": 200,
"data": {
"pid": 1,
"father": 0,
"icon": "sys_admin",
"resources": "dept",
"title": "Управление отделами",
"children": [
{
"pid": 28,
"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": "setting__backend",
"resources": "setting",
"title": "Настройки административной панели",
"children": null
}
]
}
}
``````json
[
{
"pid": 1,
"father": 0,
"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. Список кнопок (форма списка)**
## Затем начинаем интеграцию [**vue-element-admin**](https://github.com/PanJiaChen/vue-element-admin/blob/master/README.zh-CN.md)
Сначала посмотрим на результат.


Скачиваем [**vue-element-admin**](https://github.com/PanJiaChen/vue-element-admin/blob/master/README.zh-CN.md) с GitHub.

```npm
# Клонируем проект
git clone https://github.com/PanJiaChen/vue-admin-template.git
# Рекомендуется не использовать cnpm, так как установка с ним имеет множество странных проблем. Можно решить проблему медленной установки npm следующим образом
npm install --registry=https://registry.npm.taobao.org
# Запуск с горячей перезагрузкой на localhost:9528
npm run dev
```
**1. Изменяем API для входа на указанный в Spring Boot** Путь /src/api/login.js
```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' })
}
```
### **2. Изменяем конфигурацию axios** Путь /src/utils/request.js
```js
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 базовый URL
timeout: 5000 // время ожидания запроса
})
``````markdown
// request-интерцептор
service.interceptors.request.use(
config => {
if (getToken() !== '') {
config.headers['jwtHeader'] = getToken() // Добавляем токен в каждый запрос, пожалуйста, измените по необходимости
}
return config
},
error => {
// Обрабатываем ошибку запроса
console.log(error) // для отладки
return 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: Недействительный токен; 50012: Другой клиент вошел в систему; 50014: Токен истек;
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, чтобы избежать ошибок
})
})
}
return Promise.reject('ошибка')
} else {
return response.data
}
},
error => {
console.log('ошибка ' + error) // для отладки
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
```
### **3. Измените /config/dev.env.js и /config/prod.env.js**
```javascript
// 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"',
}
```
### **4. Измените vuex для установки токена при входе и получения информации о пользователе**
**Директория /src/store/modules/user.js**
```javascript
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: [] // разрешения на кнопки
},
``````markdown
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
}
},
```javascript
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)
})
})
},
}```javascript
// Получение информации о пользователе
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)
})
})
},
``````markdown
export default new Router({
// mode: 'history', // поддержка с серверной стороны
scrollBehavior: () => ({ y: 0 }),
routes: constantRouterMap
})
```
```javascript
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
}
]
```
```javascript
// Выход
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
```
### **5. Изменение vue-router** Путь /src/router/index.js
```javascript
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
}
]
```
```javascript
export default new Router({
// mode: 'history', // поддержка с серверной стороны
scrollBehavior: () => ({ y: 0 }),
routes: constantRouterMap
})
```// Асинхронно монтируемые маршруты
// Динамические маршруты, которые необходимо загружать в зависимости от прав доступа
// Этот маршрут является наиболее полным, соответствует записям в базе данных
// В зависимости от прав доступа пользователя, из этого маршрута извлекаются маршруты, соответствующие текущему пользователю, и добавляются в vue-router
// meta: наиболее важным атрибутом является resources, который используется для сравнения с meta.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'
}
}
]
}
]
```
### **6. Добавьте управление правами и добавьте в vuex /src/store/modules/permission.js
```javascript
// store/permission.js
import { asyncRouterMap, constantRouterMap } from '@/router'
/**
*
* @param {Array} userRouter права пользователя, полученные с сервера
* @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
}
``````javascript
/**
*
* @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 используется для рекурсивного построения маршрутов и сохранения их в vuex
commit('SET_ROUTERS', recursionRouter(data, asyncRouterMap))
resolve()
})
}
}
}
export default permission
```
### **7. Измените файл /src/permission.js, чтобы изменить условия для настройки маршрутов**```javascript
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' // авторизация
``````markdown
const whiteList = ['/login'] // Список для исключения из перенаправления
router.beforeEach((to, from, next) => {
NProgress.start()
if (getToken()) {
if (to.path === '/login') {
next({ path: '/' })
NProgress.done() // Если текущая страница — это дашборд, то afterEach-хук не будет запущен, поэтому обрабатываем это вручную
} 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)
// Хак для обеспечения завершения addRoutes
next({ ...to, replace: true })
})
}).catch((err) => {
store.dispatch('FedLogOut').then(() => {
Message.error(err || 'Проверка не пройдена, пожалуйста, войдите снова')
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
next(`/login?redirect=${to.path}`) // В противном случае перенаправляем на страницу входа
NProgress.done()
}
}
})```javascript
router.afterEach(() => {
NProgress.done() // Завершение прогресса
})
```
### **8. Изменение способа отображения меню. По умолчанию, маршруты для отображения берутся из vue-router, но сейчас они хранятся в vuex.**
Директория: /src/views/layout/components/Sidebar/index.vue
```javascript
<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'
</script>
```
```javascript
export default {
components: {
SidebarItem,
logo
},
computed: {
// Здесь получаем список меню из vuex
...mapGetters([
'menu_routers',
'sidebar'
]),
isCollapse() {
return !this.sidebar.opened
}
}
}
</script>
```
### **До этого момента удалось сделать динамическое меню, осталось разобраться с правами доступа к кнопкам, что довольно просто, достаточно добавить одно Vue-направление**
```javascript
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 — это свойство 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. Большое спасибо!
Если у вас возникли проблемы при сборке проекта, пожалуйста, обращайтесь ко мне.
GitHub: https://github.com/thousmile
Gitee: https://gitee.com/thousmile
QQ: 932560435
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )