SpringBoot和Vue.js实现的前后端分离的用户权限管理系统

Java
247
0
0
2023-04-26
标签   SpringBoot
目录
  • 后端实现
  • 1. 数据库设计
  • 2. 创建 Maven 项目
  • 3. 配置数据库
  • 4. 创建实体类
  • 5. 创建 Repository
  • 6. 创建 Service
  • 7. 创建 Controller
  • 8. 启动应用
  • 9. 完整的SecurityConfig.java:
  • 前端实现
  • 1. 创建 Vue.js 项目
  • 2. 添加依赖
  • 3. 配置 Axios
  • 4. 创建路由
  • 5. 创建页面
  • 6. 添加 Element UI 组件
  • 7. 运行项目
  • 8. 添加路由
  • 9. 添加登录拦截
  • 10. 添加用户服务
  • 11. 添加用户列表页面
  • 12. 完整的AddUser.vue组件代码:

后端实现

1. 数据库设计

我们需要设计两个表:用户表和角色表。

用户表

字段 类型 描述
id bigint(20) 用户 ID
username varchar(50) 用户名
password varchar(255) 密码
email varchar(50) 邮箱
phone varchar(20) 手机号码
create_by varchar(50) 创建人
create_time datetime 创建时间
update_by varchar(50) 最后修改人
update_time datetime 最后修改时间
status tinyint(1) 用户状态

角色表

字段 类型 描述
id bigint(20) 角色 ID
role_name varchar(50) 角色名称
create_by varchar(50) 创建人
create_time datetime 创建时间
update_by varchar(50) 最后修改人
update_time datetime 最后修改时间
status tinyint(1) 角色状态

用户角色表

字段 类型 描述
id bigint(20) ID
user_id bigint(20) 用户 ID
role_id bigint(20) 角色 ID
create_by varchar(50) 创建人
create_time datetime 创建时间
update_by varchar(50) 最后修改人
update_time datetime 最后修改时间
status tinyint(1) 状态

2. 创建 Maven 项目

使用 Maven 创建 Spring Boot 项目,引入 Spring Boot Web、Spring Boot JPA、MySQL 和 Lombok 等依赖。可以使用以下命令创建 Maven 项目:

mvn archetype:generate -DgroupId=com.example -DartifactId=user-management -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

3. 配置数据库

在 application.properties 文件中添加以下配置:

spring.datasource.url=jdbc:mysql://localhost:3306/user_management?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true

4. 创建实体类

创建 User、Role 和 UserRole 实体类,使用 Lombok 注解简化代码。具体代码如下:

User.java

@Data
@Entity
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id
User.java(续)
java
Copy code
    private String username;
    private String password;
    private String email;
    private String phone;
    private String createBy;
    private Date createTime;
    private String updateBy;
    private Date updateTime;
    private Boolean status;
}

Role.java

@Data
@Entity
@Table(name = "role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String roleName;
    private String createBy;
    private Date createTime;
    private String updateBy;
    private Date updateTime;
    private Boolean status;
}

UserRole.java

@Data
@Entity
@Table(name = "user_role")
public class UserRole {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long userId;
    private Long roleId;
    private String createBy;
    private Date createTime;
    private String updateBy;
    private Date updateTime;
    private Boolean status;
}

5. 创建 Repository

创建 UserRepository、RoleRepository 和 UserRoleRepository,用于操作数据库。

UserRepository.java

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

RoleRepository.java

@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
}

UserRoleRepository.java

@Repository
public interface UserRoleRepository extends JpaRepository<UserRole, Long> {
    List<UserRole> findByUserId(Long userId);
}

6. 创建 Service

创建 UserService、RoleService 和 UserRoleService,用于处理业务逻辑。

UserService.java

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private UserRoleRepository userRoleRepository;

    public User findByUsername(String username) {
        return userRepository.findByUsername(username);
    }

    public List<UserRole> findUserRolesByUserId(Long userId) {
        return userRoleRepository.findByUserId(userId);
    }
}

RoleService.java

@Service
public class RoleService {
    @Autowired
    private RoleRepository roleRepository;
}

UserRoleService.java

@Service
public class UserRoleService {
    @Autowired
    private UserRoleRepository userRoleRepository;
}

7. 创建 Controller

创建 UserController、RoleController 和 UserRoleController,用于处理请求。

UserController.java

@RestController
@RequestMapping("/api/user")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/{username}")
    public User findByUsername(@PathVariable String username) {
        return userService.findByUsername(username);
    }

    @GetMapping("/{userId}/roles")
    public List<UserRole> findUserRolesByUserId(@PathVariable Long userId) {
        return userService.findUserRolesByUserId(userId);
    }
}

RoleController.java

@RestController
@RequestMapping("/api/role")
public class RoleController {
    @Autowired
    private RoleService roleService;

    @GetMapping("")
    public List<Role> findAll() {
        return roleService.findAll();
    }
}

UserRoleController.java

@RestController
@RequestMapping("/api/userRole")
public class UserRoleController {
    @Autowired
    private UserRoleService userRoleService;
}

8. 启动应用

使用 Maven 命令 mvn spring-boot:run 启动应用,或者直接运行 UserManagementApplication 类。

9. 完整的SecurityConfig.java:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private JwtAuthenticationEntryPoint unauthorizedHandler;

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }

    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

    @Bean(BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .cors()
                    .and()
                .csrf()
                    .disable()
                .exceptionHandling()
                    .authenticationEntryPoint(unauthorizedHandler)
                    .and()
                .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                .authorizeRequests()
                    .antMatchers("/api/auth/**")
                        .permitAll()
                    .anyRequest()
                        .authenticated();

        http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

其中,JwtAuthenticationFilter是前面提到的JWT认证过滤器,UserDetailsServiceImpl是实现了Spring Security的UserDetailsService接口的用户服务类,JwtAuthenticationEntryPoint是未经授权时抛出的异常处理程序。配置类中的configure方法配置了授权规则,访问“/api/auth/**”路径下的接口不需要认证即可访问,其他所有请求都需要认证后才能访问。同时,我们也添加了JWT认证过滤器。

前端实现

1. 创建 Vue.js 项目

使用 Vue CLI 创建项目:

vue create user-management

2. 添加依赖

在 package.json 中添加以下依赖:

{
  "dependencies": {
    "axios": "^0.21.1",
    "element-plus": "^1.0.2-beta.55",
    "vue": "^2.6.12",
    "vue-router": "^3.5.1"
  }
}

然后执行 npm install 安装依赖。

3. 配置 Axios

在 src/main.js 中添加以下代码:

import axios from 'axios'
axios.defaults.baseURL = 'http://localhost:8080/api'
Vue.prototype.$http = axios

4. 创建路由

在 src/router/index.js 中添加以下代码:

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Login from '../views/Login.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/login',
    name: 'Login',
    component: Login
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

5. 创建页面

在 src/views 目录下创建以下页面:

Home.vue

<template>
  <div>
    <h1>用户管理</h1>
    <table>
      <thead>
        <tr>
          <th>用户名</th>
          <th>邮箱</th>
          <th>手机号码</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="user in users" :key="user.id">
          <td>{{ user.username }}</td>
          <td>{{ user.email }}</td>
          <td>{{ user.phone }}</td>
          <td>
            <button @click="editUser(user)">编辑</button>
            <button @click="deleteUser(user)">删除</button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
export default {
  name: 'Home',
  data() {
    return {
      users: []
    }
  },
  mounted() {
    this.loadUsers()
  },
  methods: {
    loadUsers() {
      this.$http.get('/user')
        .then(response => {
          this.users = response.data
        })
        .catch(error => {
          console.log(error)
        })
    },
    editUser(user) {
      console.log(user)
    },
    deleteUser(user) {
      console.log(user)
    }
  }
}
</script>

Login.vue

<template>
  <div>
    <h1>登录</h1>
    <form @submit.prevent="login">
      <div>
        <label>用户名:</label>
        <input type="text" v-model="username" />
      </div>
      <div>
        <label>密码:</label>
        <input type="password" v-model="password" />
      </div>
      <button type="submit">登录</button>
    </form>
  </div>
</template>

<script>
export default {
  name: 'Login',
  data() {
    return {
      username: '',
      password: ''
    }
  },
  methods: {
    login() {
      const params = {
        username: this.username,
        password: this.password
  }

  this.$http.post('/login', params)
    .then(response => {
      console.log(response)
    })
    .catch(error => {
      console.log(error)
    })
}
}
}
</script>

6. 添加 Element UI 组件

在 src/main.js 中添加以下代码:

import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

Vue.use(ElementPlus)

7. 运行项目

在项目根目录下执行以下命令运行项目:

npm run serve

然后在浏览器中访问 http://localhost:8080 查看效果。

以上代码只是简单地实现了用户列表的显示以及登录功能的实现,还需要进一步完善和优化。同时,还需要在后端实现相应的接口。

8. 添加路由

在 src/router/index.js 中添加以下代码:

import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../views/Login.vue'
import UserList from '../views/UserList.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    redirect: '/login'
  },
  {
    path: '/login',
    name: 'Login',
    component: Login
  },
  {
    path: '/userlist',
    name: 'UserList',
    component: UserList,
    meta: {
      requireAuth: true
    }
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

其中,meta 属性中的 requireAuth 表示该路由需要登录才能访问。

9. 添加登录拦截

在 src/main.js 中添加以下代码:

router.beforeEach((to, from, next) => {
  if (to.meta.requireAuth && !localStorage.getItem('token')) {
    next({
      path: '/login',
      query: { redirect: to.fullPath }
    })
  } else {
    next()
  }
})

这段代码的作用是,在用户访问需要登录才能访问的路由时,检查用户是否已登录。如果用户未登录,则跳转到登录页面,并将目标路由的路径保存到查询参数中,以便用户登录成功后自动跳转到目标路由。

10. 添加用户服务

在 src/services 目录下创建 UserService.js 文件,并添加以下代码:

import axios from 'axios'

const API_URL = '/api/users/'

class UserService {
  getUsers () {
    return axios.get(API_URL)
  }

  getUser (id) {
    return axios.get(API_URL + id)
  }

  createUser (data) {
    return axios.post(API_URL, data)
  }

  updateUser (id, data) {
    return axios.put(API_URL + id, data)
  }

  deleteUser (id) {
    return axios.delete(API_URL + id)
  }
}
export default new UserService()

该文件定义了一个 UserService 类,用于向后端发送请求,获取用户数据。其中,API_URL 表示后端 API 的根路径,getUsers、getUser、createUser、updateUser 和 deleteUser 方法分别对应获取用户列表、获取单个用户、创建用户、更新用户和删除用户。

11. 添加用户列表页面

在 src/views 目录下创建 UserList.vue 文件,并添加以下代码:

<template>
  <div>
    <h1>用户列表</h1>
    <div class="mb-3">
      <router-link to="/user/add" class="btn btn-primary">添加用户</router-link>
    </div>
    <table class="table table-striped">
      <thead>
        <tr>
          <th>用户名</th>
          <th>邮箱</th>
          <th>角色</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="user in userList" :key="user.id">
          <td>{{ user.username }}</td>
          <td>{{ user.email }}</td>
          <td>{{ user.roles.map(role => role.name).join(', ') }}</td>
          <td>
            <router-link :to="'/user/edit/' + user.id" class="btn btn-primary">编辑</router-link>
            <button class="btn btn-danger" @click="deleteUser(user.id)">删除</button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
import axios from "axios";

export default {
  data() {
    return {
      userList: [],
    };
  },
  methods: {
    fetchUserList() {
      axios.get("/api/private/users").then((response) => {
        this.userList = response.data;
      });
    },
    deleteUser(id) {
      if (confirm("确定删除该用户吗?")) {
        axios.delete(`/api/private/users/${id}`).then(() => {
          this.fetchUserList();
        });
      }
    },
  },
  mounted() {
    this.fetchUserList();
  },
};
</script>

这个文件中,我们使用了vue-router和axios库。在fetchUserList方法中,我们使用axios库发起了一个GET请求来获取用户列表。在deleteUser方法中,我们使用axios库发起了一个DELETE请求来删除用户。

接下来我们将完成系统的权限控制部分。在Spring Security中,权限控制通常通过定义安全配置类来实现。

首先,我们需要创建一个实现了WebSecurityConfigurer接口的安全配置类SecurityConfig。在这个类中,我们可以配置用户认证和授权规则。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Autowired
    private UserDetailsService userDetailsService;
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/api/public/**").permitAll()
                .antMatchers("/api/private/**").authenticated()
                .and().formLogin()
                .loginPage("/login")
                .permitAll()
                .defaultSuccessUrl("/")
                .and().logout().logoutUrl("/logout").logoutSuccessUrl("/login").invalidateHttpSession(true);
    }
 
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
 
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

在上述代码中,我们配置了以下规则:

  • 所有以/api/public开头的请求都不需要认证即可访问。
  • 所有以/api/private开头的请求都需要认证才能访问。
  • 登录页面为/login,登录成功后默认跳转到根路径/。
  • 注销路径为/logout,注销成功后跳转到登录页面。
  • 在AuthenticationManagerBuilder中指定了用户认证服务和密码加密方式。

为了实现用户认证,我们需要创建一个实现了UserDetailsService接口的用户认证服务。UserDetailsService是一个Spring Security的核心接口,用于查询用户信息。我们可以通过重写该接口的loadUserByUsername方法来实现自定义的用户认证逻辑。

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
 
    @Autowired
    private UserRepository userRepository;
 
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        List<SimpleGrantedAuthority> authorities = user.getRoles().stream()
                .map(role -> new SimpleGrantedAuthority(role.getName()))
                .collect(Collectors.toList());
        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
                authorities);
    }
}

在上述代码中,我们通过注入UserRepository来查询用户信息。然后,我们将查询到的用户角色信息转换为Spring Security的授权信息,并返回一个UserDetails对象。

至此,我们已经完成了用户认证和权限控制的实现。通过这个系统,我们可以实现用户登录、注销和权限控制等基础功能。

12. 完整的AddUser.vue组件代码:

<template>
  <div>
    <h2>Add User</h2>
    <form>
      <div class="form-group">
        <label for="username">Username</label>
        <input type="text" class="form-control" id="username" v-model="user.username" required>
      </div>
      <div class="form-group">
        <label for="email">Email</label>
        <input type="email" class="form-control" id="email" v-model="user.email" required>
      </div>
      <div class="form-group">
        <label for="password">Password</label>
        <input type="password" class="form-control" id="password" v-model="user.password" required>
      </div>
      <div class="form-group">
        <label for="roles">Roles</label>
        <select multiple class="form-control" id="roles" v-model="user.roles">
          <option v-for="role in roles" :key="role.id" :value="role">{{ role.name }}</option>
        </select>
      </div>
      <button type="button" class="btn btn-primary" @click="addUser">Add User</button>
    </form>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  name: 'AddUser',
  data() {
    return {
      user: {
        username: '',
        email: '',
        password: '',
        roles: []
      },
      roles: []
    }
  },
  created() {
    axios.get('/api/roles')
      .then(response => {
        this.roles = response.data;
      })
      .catch(error => {
        console.log(error);
      });
  },
  methods: {
    addUser() {
      axios.post('/api/users', this.user)
        .then(response => {
          this.$router.push({ name: 'UserList' });
        })
        .catch(error => {
          console.log(error);
        });
    }
  }
}
</script>

<style scoped>
.form-group {
  margin-bottom: 1rem;
}
</style>

在这个组件中,我们使用了Bootstrap的样式来美化表单。我们从后端获取了所有的角色列表,将其渲染到了下拉列表中。同时,我们绑定了一个addUser方法,在点击“Add User”按钮时将用户信息提交到后端创建一个新用户。13. 完整的UserForm.vue组件代码:

<template>
  <div>
    <form>
      <div class="form-group">
        <label for="username">Username</label>
        <input type="text" class="form-control" id="username" v-model="user.username" required>
      </div>
      <div class="form-group">
        <label for="email">Email</label>
        <input type="email" class="form-control" id="email" v-model="user.email" required>
      </div>
      <div class="form-group">
        <label for="password">Password</label>
        <input type="password" class="form-control" id="password" v-model="user.password" required>
      </div>
      <div class="form-group">
        <label for="roles">Roles</label>
        <select multiple class="form-control" id="roles" v-model="user.roles">
          <option v-for="role in roles" :key="role.id" :value="role">{{ role.name }}</option>
        </select>
      </div>
      <button type="button" class="btn btn-primary" @click="submitForm">{{ submitButtonLabel }}</button>
    </form>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  name: 'UserForm',
  props: {
    user: {
      type: Object,
      default: () => {
        return {
          username: '',
          email: '',
          password: '',
          roles: []
        };
      }
    },
    roles: {
      type: Array,
      default: () => {
        return [];
      }
    },
    submitButtonLabel: {
      type: String,
      default: 'Submit'
    },
    onSubmit: {
      type: Function,
      default: () => {
        console.log('Submit function not provided');
      }
    }
  },
  methods: {
    submitForm() {
      this.onSubmit(this.user);
    }
  }
}
</script>

<style scoped>
.form-group {
  margin-bottom: 1rem;
}
</style>

在这个组件中,我们使用了Bootstrap的样式来美化表单。我们从父组件中传递了一个user对象、roles数组和submitButtonLabel属性,分别用于渲染表单元素和提交按钮。同时,我们使用了props属性来声明这些属性。最后,我们绑定了一个submitForm方法,在点击提交按钮时将用户信息传递给父组件的onSubmit方法处理。