• springboot+springsecurity+jwt+elementui图书管理系统


    ​图书管理系统​

    关注公号:java大师,回复“图书”,获取源码

    一、springboot后台

    1、mybatis-plus整合

    1.1添加pom.xml

    1. <!--mp逆向工程 -->
    2. <dependency>
    3. <groupId>org.projectlombok</groupId>
    4. <artifactId>lombok</artifactId>
    5. </dependency>
    6. <dependency>
    7. <groupId>com.baomidou</groupId>
    8. <artifactId>mybatis-plus-boot-starter</artifactId>
    9. <version>3.4.3.1</version>
    10. </dependency>
    11. <dependency>
    12. <groupId>com.baomidou</groupId>
    13. <artifactId>mybatis-plus-generator</artifactId>
    14. <version>3.1.0</version>
    15. </dependency>
    16. <dependency>
    17. <groupId>org.freemarker</groupId>
    18. <artifactId>freemarker</artifactId>
    19. <version>2.3.31</version>
    20. </dependency>
    21. <dependency>
    22. <groupId>mysql</groupId>
    23. <artifactId>mysql-connector-java</artifactId>
    24. <version>8.0.28</version>
    25. </dependency>
    26. <dependency>
    27. <groupId>org.apache.commons</groupId>
    28. <artifactId>commons-lang3</artifactId>
    29. <version>3.7</version>
    30. </dependency>

    1.2创建CodeGenerator代码生成类

    1. package com.ds.book.mp;
    2. import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
    3. import com.baomidou.mybatisplus.core.toolkit.StringPool;
    4. import com.baomidou.mybatisplus.generator.AutoGenerator;
    5. import com.baomidou.mybatisplus.generator.InjectionConfig;
    6. import com.baomidou.mybatisplus.generator.config.*;
    7. import com.baomidou.mybatisplus.generator.config.po.TableInfo;
    8. import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
    9. import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
    10. import org.apache.commons.lang3.StringUtils;
    11. import java.util.ArrayList;
    12. import java.util.List;
    13. import java.util.Scanner;
    14. public class CodeGenerator {
    15. /**
    16. *

    17. * 读取控制台内容
    18. *

    19. */
    20. public static String scanner(String tip) {
    21. Scanner scanner = new Scanner(System.in);
    22. StringBuilder help = new StringBuilder();
    23. help.append("请输入" + tip + ":");
    24. System.out.println(help.toString());
    25. if (scanner.hasNext()) {
    26. String ipt = scanner.next();
    27. if (StringUtils.isNotBlank(ipt)) {
    28. return ipt;
    29. }
    30. }
    31. throw new MybatisPlusException("请输入正确的" + tip + "!");
    32. }
    33. public static void main(String[] args) {
    34. // 代码生成器
    35. AutoGenerator mpg = new AutoGenerator();
    36. // 全局配置
    37. GlobalConfig gc = new GlobalConfig();
    38. String projectPath = System.getProperty("user.dir");
    39. gc.setOutputDir(projectPath + "/src/main/java");
    40. gc.setAuthor("java大师");
    41. gc.setOpen(false);
    42. // gc.setSwagger2(true); 实体属性 Swagger2 注解
    43. mpg.setGlobalConfig(gc);
    44. // 数据源配置
    45. DataSourceConfig dsc = new DataSourceConfig();
    46. dsc.setUrl("jdbc:mysql://175.24.198.63:3306/book?useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2B8");
    47. // dsc.setSchemaName("public");
    48. dsc.setDriverName("com.mysql.cj.jdbc.Driver");
    49. dsc.setUsername("root");
    50. dsc.setPassword("root@1234!@#");
    51. mpg.setDataSource(dsc);
    52. // 包配置
    53. PackageConfig pc = new PackageConfig();
    54. // pc.setModuleName(scanner("模块名"));
    55. pc.setParent("com.ds.book");
    56. mpg.setPackageInfo(pc);
    57. // 自定义配置
    58. InjectionConfig cfg = new InjectionConfig() {
    59. @Override
    60. public void initMap() {
    61. // to do nothing
    62. }
    63. };
    64. // 如果模板引擎是 freemarker
    65. String templatePath = "/templates/mapper.xml.ftl";
    66. // 如果模板引擎是 velocity
    67. // String templatePath = "/templates/mapper.xml.vm";
    68. // 自定义输出配置
    69. List<FileOutConfig> focList = new ArrayList<>();
    70. // 自定义配置会被优先输出
    71. focList.add(new FileOutConfig(templatePath) {
    72. @Override
    73. public String outputFile(TableInfo tableInfo) {
    74. // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
    75. return projectPath + "/src/main/resources/mapper/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
    76. }
    77. });
    78. /*
    79. cfg.setFileCreate(new IFileCreate() {
    80. @Override
    81. public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
    82. // 判断自定义文件夹是否需要创建
    83. checkDir("调用默认方法创建的目录,自定义目录用");
    84. if (fileType == FileType.MAPPER) {
    85. // 已经生成 mapper 文件判断存在,不想重新生成返回 false
    86. return !new File(filePath).exists();
    87. }
    88. // 允许生成模板文件
    89. return true;
    90. }
    91. });
    92. */
    93. cfg.setFileOutConfigList(focList);
    94. mpg.setCfg(cfg);
    95. // 配置模板
    96. TemplateConfig templateConfig = new TemplateConfig();
    97. // 配置自定义输出模板
    98. //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
    99. // templateConfig.setEntity("templates/entity2.java");
    100. // templateConfig.setService();
    101. // templateConfig.setController();
    102. templateConfig.setXml(null);
    103. mpg.setTemplate(templateConfig);
    104. // 策略配置
    105. StrategyConfig strategy = new StrategyConfig();
    106. strategy.setNaming(NamingStrategy.underline_to_camel);
    107. strategy.setColumnNaming(NamingStrategy.underline_to_camel);
    108. strategy.setTablePrefix("t_");
    109. // strategy.setInclude("t_user");
    110. // strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
    111. strategy.setEntityLombokModel(true);
    112. strategy.setRestControllerStyle(true);
    113. // 公共父类
    114. // strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
    115. // 写于父类中的公共字段
    116. strategy.setSuperEntityColumns("id");
    117. strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
    118. strategy.setControllerMappingHyphenStyle(true);
    119. // strategy.setTablePrefix(pc.getModuleName() + "_");
    120. mpg.setStrategy(strategy);
    121. mpg.setTemplateEngine(new FreemarkerTemplateEngine());
    122. mpg.execute();
    123. }
    124. }

    1.3生成crontroller、service、mapper、entity等业务实体类

    运行CodeGenerator,生成业务实体类

    请输入表名,多个英文逗号分割: t_user,t_menu,t_role,t_user_role,t_role_menu

    2、springsecurity-jwt整合

    2.1整合springsecurity

    1)

    1. <dependency>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-security</artifactId>
    4. </dependency>

    2.2认证授权流程

    认证管理

    流程图解读:

    1、用户提交用户名、密码被SecurityFilterChain中的 UsernamePasswordAuthenticationFilter 过滤器获取到, 封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。

    2、然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证 。

    3、认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息, 身份信息,细节信息,但密码通常会被移除) Authentication 实例。

    4、SecurityContextHolder 安全上下文容器将第3步填充了信息的 Authentication ,通过 SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。 可以看出AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它 的实现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个 List 列表,存放多种认证方式,最终实际的认证工作是由 AuthenticationProvider完成的。咱们知道web表单的对应的AuthenticationProvider实现类为 DaoAuthenticationProvider,它的内部又维护着一个UserDetailsService负责UserDetails的获取。最终 AuthenticationProvider将UserDetails填充至Authentication。

    授权管理

    访问资源(即授权管理),访问url时,会通过FilterSecurityInterceptor拦截器拦截,其中会调用SecurityMetadataSource的方法来获取被拦截url所需的全部权限,再调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的投票策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则决策通过,返回访问资源,请求放行,否则跳转到403页面、自定义页面。

    2.3编写自己的UserDetails和UserDetailService

    2.3.1UserDetails

    1. package com.ds.book.entity;
    2. import com.baomidou.mybatisplus.annotation.TableName;
    3. import java.io.Serializable;
    4. import java.util.Collection;
    5. import lombok.Data;
    6. import lombok.EqualsAndHashCode;
    7. import lombok.experimental.Accessors;
    8. import org.springframework.security.core.GrantedAuthority;
    9. import org.springframework.security.core.userdetails.UserDetails;
    10. /**
    11. *

    12. *
    13. *

    14. *
    15. * @author java大师
    16. * @since 2023-03-17
    17. */
    18. @Data
    19. @EqualsAndHashCode(callSuper = false)
    20. @Accessors(chain = true)
    21. @TableName("t_user")
    22. public class User implements Serializable, UserDetails {
    23. private static final long serialVersionUID = 1L;
    24. private Integer id;
    25. /**
    26. * 登录名
    27. */
    28. private String name;
    29. /**
    30. * 用户名
    31. */
    32. private String username;
    33. /**
    34. * 密码
    35. */
    36. private String password;
    37. /**
    38. * 是否有效:1-有效;0-无效
    39. */
    40. private String status;
    41. @Override
    42. public Collectionextends GrantedAuthority> getAuthorities() {
    43. return roles
    44. .stream()
    45. .map(role -> new SimpleGrantedAuthority(role.getRoleCode()))
    46. .collect(Collectors.toList());
    47. }
    48. @Override
    49. public boolean isAccountNonExpired() {
    50. return true;
    51. }
    52. @Override
    53. public boolean isAccountNonLocked() {
    54. return true;
    55. }
    56. @Override
    57. public boolean isCredentialsNonExpired() {
    58. return true;
    59. }
    60. @Override
    61. public boolean isEnabled() {
    62. return true;
    63. }
    64. }

    2.3.2userDetailService

    登录成功后,将UserDetails的roles设置到用户中

    1. package com.ds.book.service.impl;
    2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    3. import com.ds.book.entity.User;
    4. import com.ds.book.mapper.UserMapper;
    5. import com.ds.book.service.IUserService;
    6. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    7. import org.apache.commons.lang3.StringUtils;
    8. import org.springframework.beans.factory.annotation.Autowired;
    9. import org.springframework.security.core.userdetails.UserDetails;
    10. import org.springframework.security.core.userdetails.UserDetailsService;
    11. import org.springframework.security.core.userdetails.UsernameNotFoundException;
    12. import org.springframework.stereotype.Service;
    13. /**
    14. *

    15. * 服务实现类
    16. *

    17. *
    18. * @author java大师
    19. * @since 2023-03-17
    20. */
    21. @Service
    22. public class UserServiceImpl extends ServiceImpl, User> implements IUserService, UserDetailsService {
    23. @Autowired
    24. private UserMapper userMapper;
    25. @Override
    26. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    27. User loginUser = userMapper.selectOne(new QueryWrapper<User>().eq("username", username));
    28. if (loginUser == null){
    29. throw new UsernameNotFoundException("用户名或密码错误");
    30. }
    31. loginUser.setRoles(userMapper.getRolesByUserId(loginUser.getId()));
    32. return loginUser;
    33. }
    34. }

    2.3.2加载userDetailService

    将我们自己的UserDetailService注入springsecurity

    1. package com.ds.book.config;
    2. import com.ds.book.filter.JwtTokenFilter;
    3. import com.ds.book.service.impl.UserServiceImpl;
    4. import org.springframework.beans.factory.annotation.Autowired;
    5. import org.springframework.context.annotation.Bean;
    6. import org.springframework.context.annotation.Configuration;
    7. import org.springframework.security.config.annotation.ObjectPostProcessor;
    8. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    9. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    10. import org.springframework.security.config.annotation.web.builders.WebSecurity;
    11. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    12. import org.springframework.security.config.http.SessionCreationPolicy;
    13. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    14. import org.springframework.security.crypto.password.PasswordEncoder;
    15. import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
    16. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    17. @Configuration
    18. public class SecurityConfig extends WebSecurityConfigurerAdapter {
    19. @Autowired
    20. private UserServiceImpl userService;
    21. @Bean
    22. public PasswordEncoder passwordEncoder(){
    23. return new BCryptPasswordEncoder();
    24. }
    25. //注入我们自己的UserDetailService
    26. @Override
    27. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    28. auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    29. }
    30. }

    问题:前后端分离项目,通常不会使用springsecurity自带的登录界面,登录界面由前端完成,后台只需要提供响应的服务即可,且目前主流不会采用session去存取用户,后端会返回响应的token,前端访问的时候,会在headers里面带入token.

    2.4JwtToken

    2.4.1 JWT描述

    Jwt token由Header、Payload、Signature三部分组成,这三部分之间以小数点”.”连接,JWT token长这样:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.keH6T3x1z7mmhKL1T3r9sQdAxxdzB6siemGMr_6ZOwU

    token解析后长这样: header部分,有令牌的类型(JWT)和签名算法名称(HS256): { "alg": "HS256", "typ": "JWT" } Payload部分,有效负载,这部分可以放任何你想放的数据:

    { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }

    Signature签名部分,由于这部分是使用header和payload部分计算的,所以还可以以此来验证payload部分有没有被篡改:

    HMACSHA256(

    base64UrlEncode(header) + "." +

    base64UrlEncode(payload),

    123456 //这里是密钥,只要够复杂,一般不会被破解

    )

    2.4.2 pom.xml

    1. <dependency>
    2. <groupId>io.jsonwebtoken</groupId>
    3. <artifactId>jjwt</artifactId>
    4. <version>0.9.0</version>
    5. </dependency>

    2.4.3 JwtToken工具类

    1. package com.ds.book.tool;
    2. import io.jsonwebtoken.Claims;
    3. import io.jsonwebtoken.JwtBuilder;
    4. import io.jsonwebtoken.Jwts;
    5. import io.jsonwebtoken.SignatureAlgorithm;
    6. import javax.crypto.SecretKey;
    7. import javax.crypto.spec.SecretKeySpec;
    8. import java.util.Base64;
    9. import java.util.Date;
    10. import java.util.UUID;
    11. /**
    12. * JWT工具类
    13. */
    14. public class JwtUtil {
    15. //有效期为
    16. public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时
    17. //设置秘钥明文
    18. public static final String JWT_KEY = "dashii";
    19. public static String getUUID(){
    20. String token = UUID.randomUUID().toString().replaceAll("-", "");
    21. return token;
    22. }
    23. /**
    24. * 生成jtw
    25. * @param subject token中要存放的数据(json格式)
    26. * @return
    27. */
    28. public static String createJWT(String subject) {
    29. JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
    30. return builder.compact();
    31. }
    32. /**
    33. * 生成jtw
    34. * @param subject token中要存放的数据(json格式)
    35. * @param ttlMillis token超时时间
    36. * @return
    37. */
    38. public static String createJWT(String subject, Long ttlMillis) {
    39. JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
    40. return builder.compact();
    41. }
    42. private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
    43. SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
    44. SecretKey secretKey = generalKey();
    45. long nowMillis = System.currentTimeMillis();
    46. Date now = new Date(nowMillis);
    47. if(ttlMillis==null){
    48. ttlMillis= JwtUtil.JWT_TTL;
    49. }
    50. long expMillis = nowMillis + ttlMillis;
    51. Date expDate = new Date(expMillis);
    52. return Jwts.builder()
    53. .setId(uuid) //唯一的ID
    54. .setSubject(subject) // 主题 可以是JSON数据
    55. .setIssuer("dashi") // 签发者
    56. .setIssuedAt(now) // 签发时间
    57. .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
    58. .setExpiration(expDate);
    59. }
    60. /**
    61. * 创建token
    62. * @param id
    63. * @param subject
    64. * @param ttlMillis
    65. * @return
    66. */
    67. public static String createJWT(String id, String subject, Long ttlMillis) {
    68. JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
    69. return builder.compact();
    70. }
    71. public static void main(String[] args) throws Exception {
    72. String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
    73. Claims claims = parseJWT(token);
    74. System.out.println(claims);
    75. }
    76. /**
    77. * 生成加密后的秘钥 secretKey
    78. * @return
    79. */
    80. public static SecretKey generalKey() {
    81. byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
    82. SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
    83. return key;
    84. }
    85. /**
    86. * 解析
    87. *
    88. * @param jwt
    89. * @return
    90. * @throws Exception
    91. */
    92. public static Claims parseJWT(String jwt) throws Exception {
    93. SecretKey secretKey = generalKey();
    94. return Jwts.parser()
    95. .setSigningKey(secretKey)
    96. .parseClaimsJws(jwt)
    97. .getBody();
    98. }
    99. }

    2.4.4 JwtTokenFilter

    1. package com.ds.book.filter;
    2. import com.ds.book.entity.User;
    3. import com.ds.book.mapper.UserMapper;
    4. import com.ds.book.service.IMenuService;
    5. import com.ds.book.service.IUserService;
    6. import com.ds.book.tool.JwtUtil;
    7. import io.jsonwebtoken.Claims;
    8. import org.springframework.beans.factory.annotation.Autowired;
    9. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    10. import org.springframework.security.core.context.SecurityContextHolder;
    11. import org.springframework.stereotype.Component;
    12. import org.springframework.util.StringUtils;
    13. import org.springframework.web.filter.OncePerRequestFilter;
    14. import javax.servlet.FilterChain;
    15. import javax.servlet.ServletException;
    16. import javax.servlet.http.HttpServletRequest;
    17. import javax.servlet.http.HttpServletResponse;
    18. import java.io.IOException;
    19. @Component
    20. public class JwtTokenFilter extends OncePerRequestFilter {
    21. @Autowired
    22. private IUserService userService;
    23. @Autowired
    24. private UserMapper userMapper;
    25. @Override
    26. protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
    27. //1、获取token
    28. String token = httpServletRequest.getHeader("token");
    29. if (StringUtils.isEmpty(token)){
    30. filterChain.doFilter(httpServletRequest,httpServletResponse);
    31. return;
    32. }
    33. String userId;
    34. try {
    35. Claims claims = JwtUtil.parseJWT(token);
    36. userId = claims.getSubject();
    37. } catch (Exception exception) {
    38. exception.printStackTrace();
    39. throw new RuntimeException("token非法");
    40. }
    41. User user = userService.getUserById(Integer.parseInt(userId));
    42. user.setRoles(userMapper.getRolesByUserId(Integer.parseInt(userId)));
    43. UsernamePasswordAuthenticationToken authenticationToken =
    44. new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities());
    45. SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    46. filterChain.doFilter(httpServletRequest,httpServletResponse);
    47. }
    48. }

    在springsecurity中,第一个经过的过滤器是UsernamePasswordAuthenticationFilter,所以前后端分离的项目,我们自己定义的过滤器要放在这个过滤器前面,具体配置如下

    1. @Override
    2. protected void configure(HttpSecurity http) throws Exception {
    3. http.csrf().disable()
    4. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    5. .and()
    6. .authorizeRequests()
    7. .antMatchers("/login").permitAll()
    8. .anyRequest().authenticated();
    9. http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
    10. http.cors();
    11. }

    2.4.5授权

    2.4.5.1 开启preAuthorize进行收取(Controller路径匹配)

    1)主启动类上添加EnableGlobalMethodSecurity注解

    1. @EnableGlobalMethodSecurity(prePostEnabled = true)
    2. @SpringBootApplication
    3. @MapperScan("com.ds.book.mapper")
    4. public class BookSysApplication {
    5. public static void main(String[] args) {
    6. SpringApplication.run(BookSysApplication.class,args);
    7. }
    8. }

    2)Controller方法上添加@PreAuthorize注解

    1. @RestController
    2. public class HelloController {
    3. @GetMapping("/hello")
    4. @PreAuthorize("hasRole('ROLE_ADMIN')")
    5. public String hello(){
    6. return "hello";
    7. }
    8. }

    2.4.5.2 增强方式授权(数据库表配置)

    1)创建我们自己的FilterInvocationSecurityMetadataSource,实现getAttributes方法,获取请求url所需要的角色

    1. @Component
    2. public class MySecurtiMetaDataSource implements FilterInvocationSecurityMetadataSource {
    3. @Autowired
    4. private IMenuService menuService;
    5. AntPathMatcher antPathMatcher = new AntPathMatcher();
    6. //获取访问url需要的角色,例如:/sys/user需要ROLE_ADMIN角色,访问sys/user时获取到必须要有ROLE_ADMIN角色。返回 Collection<ConfigAttribute>
    7. @Override
    8. public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
    9. String requestURI = ((FilterInvocation) object).getRequest().getRequestURI();
    10. //获取所有的菜单及角色
    11. List<Menu> menus = menuService.getMenus();
    12. for (Menu menu : menus) {
    13. if (antPathMatcher.match(menu.getUrl(),requestURI)){
    14. String[] roles = menu.getRoles().stream().map(role -> role.getRoleCode()).toArray(String[]::new);
    15. return SecurityConfig.createList(roles);
    16. }
    17. }
    18. return null;
    19. }
    20. @Override
    21. public Collection<ConfigAttribute> getAllConfigAttributes() {
    22. return null;
    23. }
    24. @Override
    25. public boolean supports(Class<?> clazz) {
    26. return false;
    27. }
    28. }

    2)创建我们自己的决策管理器AccessDecisionManager,实现decide方法,判断步骤1)中获取到的角色和我们目前登录的角色是否相同,相同则允许访问,不相同则不允许访问,

    1. @Component
    2. public class MyAccessDecisionManager implements AccessDecisionManager {
    3. //1、认证通过后,会往authentication中填充用户信息
    4. //2、拿authentication中的权限与上一步获取到的角色信息进行比对,比对成功后,允许访问
    5. @Override
    6. public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
    7. Collectionextends GrantedAuthority> authorities = authentication.getAuthorities();
    8. for (ConfigAttribute configAttribute : configAttributes) {
    9. for (GrantedAuthority authority : authorities) {
    10. if (authority.getAuthority().equals(configAttribute.getAttribute())){
    11. return;
    12. }
    13. }
    14. }
    15. throw new AccessDeniedException("权限不足,请联系管理员");
    16. }
    17. @Override
    18. public boolean supports(ConfigAttribute attribute) {
    19. return false;
    20. }
    21. @Override
    22. public boolean supports(Class clazz) {
    23. return false;
    24. }
    25. }

    3)在SecurityConfig中,添加后置处理器(增强器),让springsecurity使用我们自己的datametasource和decisionMananger

    1. @Configuration
    2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
    3. @Autowired
    4. private MySecurtiMetaDataSource mySecurtiMetaDataSource;
    5. @Autowired
    6. private MyAccessDecisionManager myAccessDecisionManager;
    7. @Autowired
    8. private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
    9. @Autowired
    10. private MyAccessDeniedHandler myAccessDeniedHandler;
    11. @Autowired
    12. private UserServiceImpl userService;
    13. @Autowired
    14. private JwtTokenFilter jwtTokenFilter;
    15. @Bean
    16. public PasswordEncoder passwordEncoder(){
    17. return new BCryptPasswordEncoder();
    18. }
    19. @Override
    20. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    21. auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    22. }
    23. @Override
    24. protected void configure(HttpSecurity http) throws Exception {
    25. http.csrf().disable()
    26. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    27. .and()
    28. .authorizeRequests()
    29. .antMatchers("/login").permitAll()
    30. .anyRequest().authenticated()
    31. //后置处理器,使用我们自己的FilterSecurityInterceptor拦截器配置
    32. .withObjectPostProcessor(new ObjectPostProcessor () {
    33. @Override
    34. public O postProcess(O o) {
    35. o.setSecurityMetadataSource(mySecurtiMetaDataSource);
    36. o.setAccessDecisionManager(myAccessDecisionManager);
    37. return o;
    38. }
    39. })
    40. .and()
    41. .headers().cacheControl();
    42. http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
    43. http.cors();
    44. }
    45. }

    2.4.6异常处理

    1)前端渲染工具类

    1. public class WebUtils
    2. {
    3. /**
    4. * 将字符串渲染到客户端
    5. *
    6. * @param response 渲染对象
    7. * @param string 待渲染的字符串
    8. * @return null
    9. */
    10. public static String renderString(HttpServletResponse response, String string) {
    11. try
    12. {
    13. response.setStatus(200);
    14. response.setContentType("application/json");
    15. response.setCharacterEncoding("utf-8");
    16. response.getWriter().print(string);
    17. }
    18. catch (IOException e)
    19. {
    20. e.printStackTrace();
    21. }
    22. return null;
    23. }
    24. }

    2)未登录异常处理,实现commence方法

    1. @Component
    2. public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    3. @Override
    4. public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
    5. Result result = new Result(401,"未登录,请先登录",null);
    6. String json = JSON.toJSONString(result);
    7. WebUtils.renderString(httpServletResponse,json);
    8. }
    9. }

    3)授权失败异常处理,实现Handle方法

    1. @Component
    2. public class MyAccessDeniedHandler implements AccessDeniedHandler {
    3. @Override
    4. public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
    5. Result result = new Result(403,"权限不足请联系管理员",null);
    6. String s = JSON.toJSONString(result);
    7. WebUtils.renderString(httpServletResponse,s);
    8. }
    9. }

    3、整合swagger2

    1)添加pom.xml依赖

    1. <dependency>
    2. <groupId>io.springfox</groupId>
    3. <artifactId>springfox-swagger2</artifactId>
    4. <version>2.7.0</version>
    5. </dependency>
    6. <dependency>
    7. <groupId>io.springfox</groupId>
    8. <artifactId>springfox-swagger-ui</artifactId>
    9. <version>2.7.0</version>
    10. </dependency>
    11. <dependency>
    12. <groupId>com.github.xiaoymin</groupId>
    13. <artifactId>knife4j-spring-boot-starter</artifactId>
    14. <version>2.0.7</version>
    15. </dependency>

    2)创建swagger配置文件

    1. package com.ds.book.config;
    2. import org.springframework.context.annotation.Bean;
    3. import org.springframework.context.annotation.Configuration;
    4. import springfox.documentation.builders.ApiInfoBuilder;
    5. import springfox.documentation.builders.PathSelectors;
    6. import springfox.documentation.builders.RequestHandlerSelectors;
    7. import springfox.documentation.service.*;
    8. import springfox.documentation.spi.DocumentationType;
    9. import springfox.documentation.spi.service.contexts.SecurityContext;
    10. import springfox.documentation.spring.web.plugins.Docket;
    11. import springfox.documentation.swagger2.annotations.EnableSwagger2;
    12. import java.util.ArrayList;
    13. import java.util.List;
    14. @Configuration
    15. @EnableSwagger2
    16. public class Swagger2Config {
    17. @Bean
    18. public Docket createRestApi() {
    19. return new Docket(DocumentationType.SWAGGER_2)
    20. .pathMapping("/")
    21. .apiInfo(apiInfo())
    22. .select()
    23. //swagger要扫描的包路径
    24. .apis(RequestHandlerSelectors.basePackage("com.ds.book.controller"))
    25. .paths(PathSelectors.any())
    26. .build()
    27. .securityContexts(securityContexts())
    28. .securitySchemes(securitySchemes());
    29. }
    30. private ApiInfo apiInfo() {
    31. return new ApiInfoBuilder().title("图书管理系统接口文档")
    32. //作者、路径和邮箱
    33. .contact(new Contact("java大师","http://localhost:8080/doc.html","fry000@qq.com"))
    34. .version("1.0").description("图书管理接口文档").build();
    35. }
    36. private List<SecurityContext> securityContexts() {
    37. //设置需要登录认证的路径
    38. List<SecurityContext> result = new ArrayList<>();
    39. result.add(getContextByPath("/.*"));
    40. return result;
    41. }
    42. //通过pathRegex获取SecurityContext对象
    43. private SecurityContext getContextByPath(String pathRegex) {
    44. return SecurityContext.builder()
    45. .securityReferences(defaultAuth())
    46. .forPaths(PathSelectors.regex(pathRegex))
    47. .build();
    48. }
    49. //默认为全局的SecurityReference对象
    50. private List<SecurityReference> defaultAuth() {
    51. List<SecurityReference> result = new ArrayList<>();
    52. AuthorizationScope authorizationScope = new AuthorizationScope("global",
    53. "accessEverything");
    54. AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
    55. authorizationScopes[0] = authorizationScope;
    56. result.add(new SecurityReference("Authorization", authorizationScopes));
    57. return result;
    58. }
    59. private List<ApiKey> securitySchemes() {
    60. //设置请求头信息
    61. List<ApiKey> result = new ArrayList<>();
    62. //设置header中的token
    63. ApiKey apiKey = new ApiKey("token", "token", "header");
    64. result.add(apiKey);
    65. return result;
    66. }
    67. }

    3)修改SecurityConfig配置类,允许访问swagger的地址

    1. //主要的配置文件,antMatchers匹配的路径,全部忽略,不进行JwtToken的认证
    2. @Override
    3. public void configure(WebSecurity web) throws Exception {
    4. web.ignoring().antMatchers(
    5. "/login",
    6. "/logout",
    7. "/css/**",
    8. "/js/**",
    9. "/index.html",
    10. "favicon.ico",
    11. "/doc.html",
    12. "/webjars/**",
    13. "/swagger-resources/**",
    14. "/v2/api-docs/**"
    15. );
    16. }

    4)编写LoginController接口,通过@Api和@ApiOperation注解使用swagger

    1. package com.ds.book.controller;
    2. import com.ds.book.entity.Result;
    3. import com.ds.book.entity.User;
    4. import com.ds.book.service.IUserService;
    5. import io.swagger.annotations.Api;
    6. import io.swagger.annotations.ApiOperation;
    7. import org.springframework.beans.factory.annotation.Autowired;
    8. import org.springframework.security.access.SecurityConfig;
    9. import org.springframework.security.access.prepost.PreAuthorize;
    10. import org.springframework.web.bind.annotation.GetMapping;
    11. import org.springframework.web.bind.annotation.PostMapping;
    12. import org.springframework.web.bind.annotation.RequestBody;
    13. import org.springframework.web.bind.annotation.RestController;
    14. @RestController
    15. @Api(tags = "登录")
    16. public class LoginController {
    17. @Autowired
    18. private IUserService userService;
    19. @ApiOperation("登录")
    20. @PostMapping("/login")
    21. public Result login(@RequestBody User user){
    22. return userService.login(user);
    23. }
    24. }

    5)输入地址 http://localhost:8080/doc.html,进入swagger

    6)点击登录进入登录接口,点击调试,发送

    测试成功!

    4、业务接口

    4.1 登录接口

    注意:前后端分离项目,退出的时候,由前端清除浏览器请求header中的token和sessionStorage或者LocalStorage,后端只要返回一个退出成功的消息。

    1. package com.ds.book.controller;
    2. import com.ds.book.entity.Result;
    3. import com.ds.book.entity.User;
    4. import com.ds.book.service.IUserService;
    5. import io.swagger.annotations.Api;
    6. import io.swagger.annotations.ApiOperation;
    7. import org.springframework.beans.factory.annotation.Autowired;
    8. import org.springframework.security.access.SecurityConfig;
    9. import org.springframework.security.access.prepost.PreAuthorize;
    10. import org.springframework.security.core.userdetails.UserDetails;
    11. import org.springframework.security.core.userdetails.UserDetailsService;
    12. import org.springframework.web.bind.annotation.GetMapping;
    13. import org.springframework.web.bind.annotation.PostMapping;
    14. import org.springframework.web.bind.annotation.RequestBody;
    15. import org.springframework.web.bind.annotation.RestController;
    16. import java.security.Principal;
    17. @RestController
    18. @Api(tags = "登录")
    19. public class LoginController {
    20. @Autowired
    21. private IUserService userService;
    22. @Autowired
    23. private UserDetailsService userDetailsService;
    24. @ApiOperation("登录")
    25. @PostMapping("/login")
    26. public Result login(@RequestBody User user){
    27. return userService.login(user);
    28. }
    29. @ApiOperation("退出")
    30. @PostMapping("/logout")
    31. public Result logout(){
    32. return Result.success("退出成功");
    33. }
    34. @ApiOperation("获取当前登录用户信息")
    35. @GetMapping("/user/info")
    36. public User user(Principal principal){
    37. if (principal == null){
    38. return null;
    39. }
    40. String username = principal.getName();
    41. User user = (User)userDetailsService.loadUserByUsername(username);
    42. user.setPassword(null);
    43. return user;
    44. }
    45. }

    4.2菜单接口

    1. package com.ds.book.controller;
    2. import com.ds.book.entity.Menu;
    3. import com.ds.book.entity.Result;
    4. import com.ds.book.service.IMenuService;
    5. import io.swagger.annotations.Api;
    6. import io.swagger.annotations.ApiOperation;
    7. import io.swagger.models.auth.In;
    8. import org.springframework.beans.factory.annotation.Autowired;
    9. import org.springframework.security.access.prepost.PreAuthorize;
    10. import org.springframework.web.bind.annotation.*;
    11. import java.util.List;
    12. /**
    13. *

    14. * 前端控制器
    15. *

    16. *
    17. * @author java大师
    18. * @since 2023-03-09
    19. */
    20. @RestController
    21. @Api(tags = "菜单管理")
    22. public class MenuController {
    23. @Autowired
    24. private IMenuService menuService;
    25. @GetMapping("/menus")
    26. @ApiOperation("获取菜单树")
    27. public Result getMenus(){
    28. List allMenus = menuService.getMenuTree();
    29. return Result.success("查询成功",allMenus);
    30. }
    31. @PostMapping("/menu/add")
    32. @ApiOperation("添加菜单")
    33. public Result addMenu(@RequestBody Menu menu){
    34. return menuService.addMenu(menu);
    35. }
    36. @PostMapping("/menu/update")
    37. @ApiOperation("修改菜单")
    38. public Result updateMenu(@RequestBody Menu menu){
    39. return menuService.updateMenu(menu);
    40. }
    41. @PostMapping("/menu/delete/{id}")
    42. @ApiOperation("删除菜单")
    43. public Result deleteMenu(@PathVariable Integer id){
    44. return menuService.deleteMenu(id);
    45. }
    46. }

    4.3用户接口

    1. package com.ds.book.controller;
    2. import com.ds.book.entity.Result;
    3. import com.ds.book.entity.User;
    4. import com.ds.book.service.IUserService;
    5. import io.swagger.annotations.Api;
    6. import io.swagger.annotations.ApiOperation;
    7. import io.swagger.models.auth.In;
    8. import org.springframework.beans.factory.annotation.Autowired;
    9. import org.springframework.security.crypto.password.PasswordEncoder;
    10. import org.springframework.web.bind.annotation.*;
    11. import javax.jws.soap.SOAPBinding;
    12. import java.util.List;
    13. /**
    14. *

    15. * 前端控制器
    16. *

    17. *
    18. * @author java大师
    19. * @since 2023-03-09
    20. */
    21. @RestController
    22. @Api(tags = "用户管理")
    23. public class UserController {
    24. @Autowired
    25. private IUserService userService;
    26. @Autowired
    27. private PasswordEncoder passwordEncoder;
    28. @GetMapping("/users")
    29. @ApiOperation("查询用户列表")
    30. public Result getUsers(){
    31. List list = userService.getUsers();
    32. if (list != null){
    33. return Result.success("查询成功",list);
    34. }
    35. return Result.error("查询失败");
    36. }
    37. @PostMapping("/user/add")
    38. @ApiOperation("添加用户")
    39. public Result addUser(@RequestBody User user){
    40. user.setPassword(passwordEncoder.encode("123456"));
    41. return userService.addUser(user);
    42. }
    43. @PostMapping("/user/update")
    44. @ApiOperation("修改用户")
    45. public Result updateUser(@RequestBody User user){
    46. return userService.updateUser(user);
    47. }
    48. @PostMapping("/user/chooseRole/{userId}/{roleId}")
    49. @ApiOperation("选择角色")
    50. public Result chooseRole(@PathVariable Integer userId,@PathVariable Integer roleId){
    51. return userService.chooseRole(userId,roleId);
    52. }
    53. @PostMapping("/user/delete/{id}")
    54. @ApiOperation("删除用户")
    55. public Result deleteUser(@PathVariable Integer id){
    56. return userService.deleteUser(id);
    57. }
    58. }

    4.4角色接口

    1. package com.ds.book.service.impl;
    2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    3. import com.ds.book.entity.Menu;
    4. import com.ds.book.entity.Result;
    5. import com.ds.book.entity.Role;
    6. import com.ds.book.entity.RoleMenu;
    7. import com.ds.book.mapper.RoleMapper;
    8. import com.ds.book.mapper.RoleMenuMapper;
    9. import com.ds.book.service.IRoleService;
    10. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    11. import org.springframework.beans.factory.annotation.Autowired;
    12. import org.springframework.stereotype.Service;
    13. import java.util.ArrayList;
    14. import java.util.List;
    15. /**
    16. *

    17. * 服务实现类
    18. *

    19. *
    20. * @author java大师
    21. * @since 2023-03-09
    22. */
    23. @Service
    24. public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements IRoleService {
    25. @Autowired
    26. private RoleMapper roleMapper;
    27. @Autowired
    28. private RoleMenuMapper roleMenuMapper;
    29. private List<Menu> buildMenuTree(List<Menu> menus, Integer parentId) {
    30. List<Menu> treeMenus = new ArrayList<>();
    31. for (Menu menu : menus) {
    32. if (parentId==0 ? menu.getParentId()==0 : parentId.equals(menu.getParentId())) {
    33. List<Menu> children = buildMenuTree(menus, menu.getId());
    34. if (!children.isEmpty()) {
    35. menu.setChildren(children);
    36. }
    37. treeMenus.add(menu);
    38. }
    39. }
    40. return treeMenus;
    41. }
    42. @Override
    43. public List getRoles() {
    44. List<Role> roles = roleMapper.getRoles();
    45. for (Role role : roles) {
    46. role.setMenus(buildMenuTree(role.getMenus(),0));
    47. }
    48. return roles;
    49. }
    50. @Override
    51. public Result chooseMenus(Integer roleId, Integer[] menuIds) {
    52. try {
    53. roleMenuMapper.delete(new QueryWrapper<RoleMenu>().eq("role_id",roleId));
    54. for (Integer menuId : menuIds) {
    55. RoleMenu roleMenu = new RoleMenu();
    56. roleMenu.setRoleId(roleId);
    57. roleMenu.setMenuId(menuId);
    58. roleMenuMapper.insert(roleMenu);
    59. }
    60. return Result.success("添加成功");
    61. } catch (Exception exception) {
    62. return Result.error("添加失败");
    63. }
    64. }
    65. }

    二、springboot前端

    1、vue-cli创建vue-book前端项目

    vue create vue-book

    选择Vue2,运行完毕,出现以下画面

    执行绿色的命令,出现下列界面代表脚手架创建项目成功

    2、整合elementui

    1. //命令行安装
    2. npm i element-ui -S
    3. //main.js使用element-ui
    4. import Vue from 'vue';
    5. import ElementUI from 'element-ui';
    6. import 'element-ui/lib/theme-chalk/index.css';
    7. import App from './App.vue';
    8. Vue.use(ElementUI);
    9. new Vue({
    10. el: '#app',
    11. render: h => h(App)
    12. });

    3、安装vue-router

    2.1安装依赖

    npm install vue-router@3

    2.2创建路由文件

    1. import Vue from 'vue'
    2. import VueRouter from "vue-router";
    3. Vue.use(VueRouter)
    4. //配置localhost:8080/跳转为登录页
    5. const routes =[
    6. {
    7. path:'/',
    8. name:'Login',
    9. component:() => import('@/pages/Login.vue')
    10. }
    11. ]
    12. export default new VueRouter({
    13. routes
    14. })

    4、整合json-server

    4.1安装json-server

    npm install -g json-server

    4.2创建mock文件夹,新建db.json

    1. {
    2. "posts": [
    3. {
    4. "id": 1,
    5. "title": "json-server",
    6. "author": "typicode"
    7. }
    8. ],
    9. "users": [
    10. {
    11. "id": 1,
    12. "username": "admin",
    13. "password": "123"
    14. }
    15. ],
    16. "login":
    17. {
    18. "code": 200,
    19. "message":"返回成功",
    20. "data": {
    21. "id": "1237361915165020161",
    22. "username": "admin",
    23. "phone": "111111111111",
    24. "nickName": "javads",
    25. "realName": "javads",
    26. "sex": 1,
    27. "deptId": "1237322421447561216",
    28. "deptName": "测试部门",
    29. "status": 1,
    30. "email": "xxxx@qq.com",
    31. "token":"ASDSADASDSW121DDSA",
    32. "menus": [
    33. {
    34. "id": "1236916745927790564",
    35. "title": "系统管理",
    36. "icon": "el-icon-star-off",
    37. "path": "/sys",
    38. "name": "Sys",
    39. "children": [
    40. {
    41. "id": "1236916745927790578",
    42. "title": "角色管理",
    43. "icon": "el-icon-s-promotion",
    44. "path": "/sys/roles",
    45. "name": "Roles",
    46. "children": []
    47. },
    48. {
    49. "id": "1236916745927790560",
    50. "title": "菜单管理",
    51. "icon": "el-icon-s-tools",
    52. "path": "/sys/menus",
    53. "name": "Menus",
    54. "children": []
    55. },
    56. {
    57. "id": "1236916745927790575",
    58. "title": "用户管理",
    59. "icon": "el-icon-s-custom",
    60. "path": "/sys/users",
    61. "name": "User",
    62. "children": []
    63. }
    64. ],
    65. "spread": true,
    66. "checked": false
    67. },
    68. {
    69. "id": "1236916745927790569",
    70. "title": "账号管理",
    71. "icon": "el-icon-s-data",
    72. "path": "/account",
    73. "name": "Account",
    74. "children": []
    75. }
    76. ],
    77. "permissions": [
    78. "sys:log:delete",
    79. "sys:user:add",
    80. "sys:role:update",
    81. "sys:dept:list"
    82. ]
    83. }
    84. },
    85. "comments": [
    86. {
    87. "id": 1,
    88. "body": "some comment",
    89. "postId": 1
    90. }
    91. ],
    92. "profile": {
    93. "name": "typicode"
    94. }
    95. }

    4.3修改vue.config.js,json-server的默认端口为3000,将代理服务器的的端口改成3000

    1. const { defineConfig } = require('@vue/cli-service')
    2. module.exports = defineConfig({
    3. transpileDependencies: true,
    4. lintOnSave:false,
    5. devServer:{
    6. proxy:{
    7. '/api':{
    8. target:'http://localhost:3000',
    9. pathRewrite:{'^/api':''},
    10. ws:true, //不写为true,websocket
    11. changeOrigin:true //不写为true
    12. }
    13. }
    14. }
    15. })

    4.4修改package.json,在scripts添加以下代码

    "mock": "json-server src/mock/db.json --port 3000 --middlewares src/mock/middlewares.js"

    4.5 运行json-server,出现以下界面代表运行成功

    json-server.cmd --watch db.jso

    5、整合axios

    5.1配置axios请求拦截器,新建utils文件夹,新建api.js,输入以下内容

    1. import router from '../router'
    2. import axios from 'axios'
    3. import {Message} from 'element-ui'
    4. import {Loading} from 'element-ui'
    5. axios.defaults.baseURL = '/api'
    6. //添加遮罩层代码
    7. let loading;
    8. let loadingNum = 0;
    9. //弹出遮罩层
    10. function showLoading(){
    11. if (loadingNum ===0){
    12. loading = Loading.service({
    13. lock:true,
    14. text:'加载中,请稍后...',
    15. background:'rgba(255,255,255,0.5)'
    16. })
    17. }
    18. loadingNum++;
    19. }
    20. //关闭遮罩层
    21. function hiddenLoading(){
    22. loadingNum--;
    23. if (loadingNum <=0){
    24. loading.close();
    25. }
    26. }
    27. /**
    28. * 添加响应拦截器,在浏览器每次发请求之前,token放入http消息头当中
    29. */
    30. axios.interceptors.request.use(config =>{
    31. showLoading();
    32. if(window.sessionStorage.getItem('token')){
    33. config.headers.Authorization =window.sessionStorage.getItem('token')
    34. }
    35. console.log(config)
    36. return config
    37. },error => {
    38. console.log(error)
    39. })
    40. /**
    41. * 添加响应拦截器
    42. */
    43. axios.interceptors.response.use(success => {
    44. hiddenLoading();
    45. if (success.status && success.status == 200){
    46. if (success.data.code == 500 || success.data.code == 401 || success.data.code == 403) {
    47. Message.error({
    48. offset:200,
    49. message:success.data.message
    50. })
    51. router.replace("/")
    52. }
    53. if (success.data.message){
    54. Message.success({
    55. offset:200,
    56. message:success.data.message
    57. })
    58. }
    59. }
    60. return success.data
    61. },error => {
    62. hiddenLoading();
    63. if (error.response.code == 504 || error.response.code == 404) {
    64. Message.error({
    65. message: '服务器跑路了'
    66. });
    67. } else if (error.response.status == 403) {
    68. Message.error({
    69. message: '权限不足,请联系管理员'
    70. });
    71. } else if (error.response.code == 401) {
    72. Message.error({
    73. message: '尚未登录,请先登录'
    74. })
    75. router.replace('/');
    76. } else {
    77. if (error.response.data.message) {
    78. Message.error({
    79. message: error.response.data.message
    80. });
    81. } else {
    82. Message.error({
    83. message: '未知错误'
    84. });
    85. }
    86. }
    87. return;
    88. })
    89. export default axios

    5.2创建请求接口,新建http.js

    1. import axios from './api'
    2. export const login = (param) =>{
    3. return axios.get(`/posts`, {param})
    4. }
    5. export const getUser = () =>{
    6. return axios.get(`/users`, {})
    7. }

    6、业务功能

    6.1登录界面

    1. <template>
    2. <div class="login-container">
    3. <el-form ref="form" :model="form" label-width="100px" class="login-form">
    4. <h1 style="margin-bottom: 20px;text-align: center">欢迎登录</h1>
    5. <el-form-item label="用户名">
    6. <el-input v-model="form.username"></el-input>
    7. </el-form-item>
    8. <el-form-item label="密码">
    9. <el-input type="password" v-model="form.password"></el-input>
    10. </el-form-item>
    11. <el-form-item>
    12. <el-button type="primary" @click="onSubmit">登录</el-button>
    13. <el-button>取消</el-button>
    14. </el-form-item>
    15. </el-form>
    16. </div>
    17. </template>
    18. <script>
    19. import {initRoutes} from "@/utils/routesUtil";
    20. import {login,getUser} from "@/utils/http";
    21. export default {
    22. name:'Login',
    23. data() {
    24. return {
    25. form: {
    26. username: '',
    27. password: '',
    28. }
    29. }
    30. },
    31. methods: {
    32. onSubmit() {
    33. login(this.form).then(res=>{
    34. if(res){
    35. //浏览器中存储token,以后每次调用后端接口,浏览器都会带入这个token
    36. window.sessionStorage.setItem("token",res.data.token)
    37. //初始化路由数据
    38. let myRoutes = initRoutes(res.data.menus)
    39. //将路由进行替换并添加到router中
    40. this.$router.options.routes = [myRoutes]
    41. this.$router.addRoute(myRoutes)
    42. this.$router.replace("/home")
    43. }else{
    44. return false
    45. }
    46. })
    47. },
    48. }
    49. }
    50. </script>
    51. <style scoped>
    52. .login-form {
    53. border: 1px #DCDFE6 solid;
    54. border-radius: 4px;
    55. padding: 40px;
    56. margin: 110px 400px;
    57. box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
    58. width: 400px;
    59. }
    60. .login-container {
    61. /*background: url(../assets/image/login2.jpg) no-repeat;*/
    62. height: 100%;
    63. width: 100%;
    64. overflow: hidden;
    65. background-size: cover;
    66. }
    67. </style>

    6.2处理后台请求返回工具类

    1. export const initTmpRoutes = (menus) => {
    2. let tmpRoutes = []
    3. menus.forEach(menu => {
    4. let {id,title,icon,path,name,children} = menu
    5. if(children instanceof Array){
    6. children = initTmpRoutes(children)
    7. }
    8. let tmpRoute = {
    9. path:path,
    10. meta:{icon:icon,title:title},
    11. name:name,
    12. children:children,
    13. component:children.length?{render(c){return c('router-view')}}:()=>import(`@/pages${path}/${name}.vue`)
    14. }
    15. console.log('tmpRoute',tmpRoute.path)
    16. tmpRoutes.push(tmpRoute)
    17. })
    18. return tmpRoutes
    19. }
    20. export const initRoutes = (menus)=>{
    21. const homeRoute = {
    22. path:'/home',
    23. name:'Home',
    24. meta:{title:'首页',icon: 'el-icon-star-off'},
    25. component:() => import('@/pages/Home.vue'),
    26. }
    27. homeRoute.children = initTmpRoutes(menus);
    28. console.log('homeRoute',homeRoute)
    29. return homeRoute;
    30. }

    6.3首页、导航页和主页

    home.vue

    1. <template>
    2. <div class="box">
    3. <el-container style="height: 100%;" direction="vertial">
    4. <el-aside width="200px">
    5. <Nav/>
    6. </el-aside>
    7. <el-container>
    8. <el-header class="homeHeader">
    9. <el-dropdown class="userInfo" @command="handlecommand">
    10. <span class="el-dropdown-link">
    11. </span>
    12. <el-dropdown-menu slot="dropdown">
    13. <el-dropdown-item command="userInfo">个人中心</el-dropdown-item>
    14. <el-dropdown-item command="setting">设置</el-dropdown-item>
    15. <el-dropdown-item command="logout">退出</el-dropdown-item>
    16. </el-dropdown-menu>
    17. </el-dropdown>
    18. </el-header>
    19. <el-main>
    20. <Main/>
    21. </el-main>
    22. <el-footer>底部</el-footer>
    23. </el-container>
    24. </el-container>
    25. </div>
    26. </template>
    27. <script>
    28. import Nav from "@/components/Nav";
    29. import Main from "@/components/Main";
    30. import RecursiveMenu from "@/components/RecursiveMenu";
    31. export default{
    32. data(){
    33. return {
    34. user:JSON.parse(window.sessionStorage.getItem('user'))
    35. }
    36. },
    37. components:{
    38. Nav,
    39. RecursiveMenu,
    40. Main
    41. },
    42. methods:{
    43. handlecommand(command){
    44. if(command=='logout'){
    45. this.$confirm('确定退出?', '提示', {
    46. confirmButtonText: '确定',
    47. cancelButtonText: '取消',
    48. type: 'warning'
    49. }).then(()=>{
    50. logout();
    51. window.sessionStorage.removeItem('user');
    52. window.sessionStorage.removeItem('token');
    53. this.$store.commit('initRoutes',[]);
    54. this.$router.replace('/');
    55. }).catch(()=>{
    56. })
    57. }
    58. }
    59. },
    60. }
    61. </script>
    62. <style>
    63. #app,
    64. html,
    65. body,
    66. .box,
    67. .el-container{
    68. padding: 0px;
    69. margin: 0px;
    70. height: 100%;
    71. }
    72. .el-header,
    73. .el-footer {
    74. background-color: #B3C0D1;
    75. color: #333;
    76. text-align: right;
    77. line-height: 60px;
    78. }
    79. .el-aside {
    80. background-color: #545C64;
    81. color: #333;
    82. text-align: center;
    83. line-height: 300px;
    84. }
    85. .el-main {
    86. background-color: #E9EEF3;
    87. color: #333;
    88. display: flex;
    89. flex-direction: column;
    90. }
    91. body>.el-container {
    92. margin-bottom: 40px;
    93. }
    94. .homeHeader .userInfo{
    95. cursor: pointer;
    96. }
    97. .el-dropdown-link img{
    98. width: 36px;
    99. height: 36px;
    100. border-radius: 18px;
    101. }
    102. </style>

    Nav.vue

    1. <template>
    2. <el-menu router>
    3. <template v-for="item in routes">
    4. <el-submenu v-if="item.children.length" :index="item.path">
    5. <template slot="title">{{ item.meta.title }}</template>
    6. <recursive-menu :menu="item.children"></recursive-menu>
    7. </el-submenu>
    8. <el-menu-item v-else :index="item.path">{{ item.meta.title }}</el-menu-item>
    9. </template>
    10. </el-menu>
    11. </template>
    12. <script>
    13. import RecursiveMenu from "@/components/RecursiveMenu";
    14. export default {
    15. name: 'Nav',
    16. components:{
    17. RecursiveMenu
    18. },
    19. computed:{
    20. routes(){
    21. console.log('Nav routes:',this.$router.options.routes.length)
    22. // return this.$router.options.routes[1].children;
    23. return this.$router.options.routes;
    24. }
    25. }
    26. }
    27. </script>

    RecursiveMenu.vue

    1. <template>
    2. <div>
    3. <el-menu router>
    4. <template v-for="item in menu">
    5. <el-submenu v-if="item.children.length" :index="item.path">
    6. <template slot="title">{{ item.meta.title }}</template>
    7. <recursive-menu :menu="item.children"></recursive-menu>
    8. </el-submenu>
    9. <el-menu-item v-else :index="item.path">{{ item.meta.title }}</el-menu-item>
    10. </template>
    11. </el-menu>
    12. </div>
    13. </template>
    14. <script>
    15. export default {
    16. name: 'RecursiveMenu',
    17. props: {
    18. menu: {
    19. type: Array,
    20. required: true
    21. },
    22. },
    23. components: {
    24. RecursiveMenu: () => import('./RecursiveMenu.vue')
    25. }
    26. }
    27. </script>

    可以看到左边的菜单和路由已经展示在浏览器中

    注意:这里有一个坑,页面刷新以后,路由中的数据就会丢失,系统菜单会不显示

    原因:页面刷新后,页面会重新实例化路由数据,因为是动态路由,所以页面刷新后会将router置为router/index.js配置的原始路由数据,所以匹配路由地址的时候会报错。

    解决方法

    思路:因为目前login接口返回的时候,直接将菜单数据传回前端,所以我们需要将菜单缓存起来,因为每次页面刷新vuex数据都会重置,所以不适合存储在vuex中,可以将菜单数据存储在sessionStorage中,页面刷新在实例化vue的created生命周期函数之前初始化路由即可

    步骤

    1)安装vuex

    npm install vuex@3

    2)修改登录页Login.vue

    1. <template>
    2. <div class="login-container">
    3. <el-form ref="form" :model="form" label-width="100px" class="login-form">
    4. <h1 style="margin-bottom: 20px;text-align: center">欢迎登录</h1>
    5. <el-form-item label="用户名">
    6. <el-input v-model="form.username"></el-input>
    7. </el-form-item>
    8. <el-form-item label="密码">
    9. <el-input type="password" v-model="form.password"></el-input>
    10. </el-form-item>
    11. <el-form-item>
    12. <el-button type="primary" @click="onSubmit">登录</el-button>
    13. <el-button>取消</el-button>
    14. </el-form-item>
    15. </el-form>
    16. </div>
    17. </template>
    18. <script>
    19. import {initRoutes} from "@/utils/routesUtil";
    20. import {login,getUser} from "@/utils/http";
    21. export default {
    22. name:'Login',
    23. data() {
    24. return {
    25. form: {
    26. username: '',
    27. password: '',
    28. }
    29. }
    30. },
    31. methods: {
    32. onSubmit() {
    33. login(this.form).then(res=>{
    34. if(res){
    35. //将token和menus保存在vuex中
    36. this.$store.dispatch("UPDATETOKEN",res.data.token);
    37. this.$store.dispatch("UPDATEUSERDATA",res.data.menus)
    38. //登录的时候,初始化菜单放在vuex中,不在登录页进行处理
    39. this.$store.commit('INITROUTES',res.data.menus)
    40. // 以下代码为注释
    41. // let myRoutes = initRoutes(res.data.menus)
    42. // this.$router.options.routes = [myRoutes]
    43. // this.$router.addRoute(myRoutes)
    44. this.$router.replace("/home")
    45. }else{
    46. return false
    47. }
    48. })
    49. },
    50. }
    51. }
    52. </script>
    53. <style scoped>
    54. .login-form {
    55. border: 1px #DCDFE6 solid;
    56. border-radius: 4px;
    57. padding: 40px;
    58. margin: 110px 400px;
    59. box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
    60. width: 400px;
    61. }
    62. .login-container {
    63. /*background: url(../assets/image/login2.jpg) no-repeat;*/
    64. height: 100%;
    65. width: 100%;
    66. overflow: hidden;
    67. background-size: cover;
    68. }
    69. </style>

    3)创建store文件夹,创建index.js

    1. import Vuex from 'vuex'
    2. import Vue from "vue";
    3. import {initRoutes} from "@/utils/routesUtil";
    4. import Router from "@/router";
    5. Vue.use(Vuex)
    6. const state = {
    7. token:window.sessionStorage.getItem('token')||'',
    8. userData:window.sessionStorage.getItem('userData')||{},
    9. routes:{}
    10. }
    11. const mutations = {
    12. SETTOKEN(state,token){
    13. window.sessionStorage.setItem('token',token)
    14. state.token = token
    15. },
    16. SETUSERDATA(state,userData){
    17. window.sessionStorage.setItem('userData',JSON.stringify(userData))
    18. state.userData = userData
    19. },
    20. INITROUTES(state,menus){
    21. let myRoutes = initRoutes(menus)
    22. Router.options.routes = [myRoutes]
    23. Router.addRoute(myRoutes);
    24. state.routes = myRoutes
    25. }
    26. }
    27. const actions = {
    28. UPDATETOKEN(context,value){
    29. context.commit('SETTOKEN',value)
    30. },
    31. UPDATEUSERDATA(context,value){
    32. context.commit('SETUSERDATA',value)
    33. }
    34. }
    35. const getters = {
    36. userinfo(state){
    37. return state.userData
    38. },
    39. menus(state){
    40. return state.userData.menus
    41. },
    42. routes(state){
    43. return state.routes.filter(item => {
    44. return item.name==='Home'
    45. })[0].children
    46. }
    47. }
    48. export default new Vuex.Store({
    49. state,
    50. mutations,
    51. actions,
    52. getters
    53. })

    4)main.js修改

    1. import Vue from 'vue'
    2. import App from './App.vue'
    3. import ElementUI from 'element-ui'
    4. import router from './router'
    5. import 'element-ui/lib/theme-chalk/index.css'
    6. import store from "@/store"
    7. Vue.config.productionTip = false
    8. Vue.use(ElementUI)
    9. //生成路由,由于没有获取菜单接口,所以直接从sessionStorage中直接去userData数据,进行路由的初始化
    10. const init = async ()=>{
    11. if (sessionStorage.getItem('token')){
    12. if (store.state.routes){
    13. await store.commit('INITROUTES',JSON.parse(sessionStorage.getItem('userData')))
    14. }
    15. }
    16. }
    17. //此处await不可缺少,需要等待路由数据先生成,才能进行vue实例的创建,否则会报错
    18. async function call(){
    19. await init();
    20. new Vue({
    21. render: h => h(App),
    22. router,
    23. store
    24. }).$mount('#app')
    25. }
    26. call()

    5)如果未登录,则跳转到login页处理,main.js添加如下内容

    1. //路由导航守卫,每次路由地址改变前出发
    2. router.beforeEach((to,from,next)=>{
    3. if (sessionStorage.getItem('token')) {
    4. next();
    5. } else {
    6. //如果是登录页面路径,就直接next()
    7. if (to.path === '/login') {
    8. next();
    9. } else {
    10. if(to.path === '/home'){
    11. next();
    12. }
    13. next('/login');
    14. }
    15. }
    16. })

    安装e-icon-picker选择器

     

  • 相关阅读:
    线性二分类的实现
    2020年全国职业院校技能大赛改革试点赛样卷三
    Linux chmod命令——修改权限信息
    【数据结构】单链表基本操作的实现
    Javascript 基础知识学习
    23个react常见问题
    chrome调试秘籍,让你的开发速度飞起来
    Docker安装入门教程
    抛砖系列之redis监控命令
    mybatispuls 批处理 rewriteBatchedStatements=true
  • 原文地址:https://blog.csdn.net/weixin_44846436/article/details/130910626