万字长文 基于SpringBoot整合SpringSecurity的认证授权(角色+权限) 真案列有数据库有源码

Posted 雾晴

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了万字长文 基于SpringBoot整合SpringSecurity的认证授权(角色+权限) 真案列有数据库有源码相关的知识,希望对你有一定的参考价值。

之前一直是使用的Shiro,最近因为公司使用若依的前后端分离版本中,认证授权模块是使用的SpringSecurity,所以就打算写一遍这个教程了。嗯在这之前一直是使用Shiro做授权和认证的。嗯后面会讲的,在这之前读者需要具有SpringBoot基础、以及能够使用SpringBoot连接数据库进行操作,

源码下载链接在文末

先来说说RBAC模型

什么是RBAC

RBAC(全称:Role-Based Access Control)基于角色的权限访问控制,作为传统访问控制(自主访问,强制访问)的有前景的代替受到广泛的关注。在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。

在一个组织中,角色是为了完成各种工作而创造,用户则依据它的责任和资格来被指派相应的角色,用户可以很容易地从一个角色被指派到另一个角色。角色可依新的需求和系统的合并而赋予新的权限,而权限也可根据需要而从某角色中回收。角色与角色的关系可以建立起来以囊括更广泛的客观
情况。

访问控制是针对越权使用资源的防御措施,目的是为了限制访问主体(如用户等) 对访问客体(如数据库资源等)的访问权限。企业环境中的访问控制策略大部分都采用基于角色的访问控制(RBAC)模型,是目前公认的解决大型企业的统一资源访问控制的有效方法

本案例的RBAC的设计思路

基于角色的访问控制基本原理是在用户和访问权限之间加入角色这一层,实现用户和权限的分离,用户只有通过激活角色才能获得访问权限。通过角色对权限分组,大大简化了用户权限分配表,间接地实现了对用户的分组,提高了权限的分配效率。且加入角色层后,访问控制机制更接近真实世界中的职业分配,便于权限管理。
在这里插入图片描述
下面我们开始

一、数据库建表


SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission`  (
  `pid` int(4) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `permission_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '资源名称',
  `str` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '资源标识符',
  PRIMARY KEY (`pid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES (1, '用户删除', 'sys:user:delete');

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `rid` int(4) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `role_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称',
  PRIMARY KEY (`rid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'ROLE_admin');
INSERT INTO `role` VALUES (2, 'ROLE_root');
INSERT INTO `role` VALUES (3, 'ROLE_test');

-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission`  (
  `id` int(4) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `rid` int(4) NULL DEFAULT NULL COMMENT '角色id',
  `pid` int(4) NULL DEFAULT NULL COMMENT '权限id',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `fk_rid2`(`rid`) USING BTREE,
  INDEX `fk_pid2`(`pid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role_permission
-- ----------------------------
INSERT INTO `role_permission` VALUES (1, 1, 1);

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `uid` int(4) NOT NULL AUTO_INCREMENT COMMENT '账户id',
  `user_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
  `password` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
  `lock` int(1) NULL DEFAULT 0 COMMENT '是否可用 1可用 0不可用',
  PRIMARY KEY (`uid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'zx', '123456', 0);
INSERT INTO `user` VALUES (2, 'te', '123', 0);

-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `uid` int(4) NULL DEFAULT NULL COMMENT '用户id',
  `rid` int(4) NULL DEFAULT NULL COMMENT '角色id',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `fk_uid`(`uid`) USING BTREE,
  INDEX `fk_rid`(`rid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1, 1);
INSERT INTO `user_role` VALUES (2, 1, 2);

SET FOREIGN_KEY_CHECKS = 1;

表结构分析

在这里插入图片描述

二、创建项目工程,并导入依赖

在这里插入图片描述
上面缺少了一个连接池依赖,在pom.xml文件中加入即可,当然不加也没有什么关系,我个人比较喜欢使用

 <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
 </dependency>

创建好了之后,把一些没用的目录和文件删掉,
在这里插入图片描述
配置yml文件
在这里插入图片描述
然后根据数据库编写实体类,编写基本的CRUD方法 最后应该是这样子,因为类太多就不贴代码了,后面会提供源码下载
在这里插入图片描述

创建Controller

创建一个controller的包,在包里面创建一个TestController类,源码如下:

package work.zx.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class TestController {


    @RequestMapping("toLogin")
    public String toLogin(){
        System.out.println("去登录");
        return "login";
    }


    @PostMapping("/toMain")
    public String toMain(){
        System.out.println("去主页");
        return "main";
    }

    @PostMapping("/toError")
    public String toError() {
        System.out.println("去失败的页面");
        return "error.html";
    }
}

当然我们也建立了对应的静态页面。
在这里插入图片描述
login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<form action="/login" method="post">
    用户名:<input type="text" name="username">
    密码:<input type="password" name="password">
    <input type="submit" value="提交">
</form>
</body>
</html>

error.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>失败</title>
</head>
<body>
登录失败
</body>
</html>

main.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录炒年糕</title>
</head>
<body>
登录成功
</body>
</html>

下面来配置springSecurity

创建一个config的包,在里面创建SecurityConfig配置类,这个配置类要继承
WebSecurityConfigurerAdapter类
并且重写里面的void configure(HttpSecurity http) throws Exception方法
如下

package work.zx.Config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //自定义登录页
        http.formLogin()
                .loginPage("/toLogin") //登录页面
                .loginProcessingUrl("/login") // 登录提交的请求 ,必须和登录表单那边的action一致才行
                .successForwardUrl("/toMain") //登录成功后跳转页面 //跳转页面是在Controller中实现的
                .failureForwardUrl("/toError"); //登录失败的页面 //也是post请求

        //认证授权
        http.authorizeRequests()
                //开放登录请求
                .antMatchers("/toLogin").permitAll()
                .antMatchers("/toError").permitAll()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated();

        //关闭csrf 这个是跨站伪造攻击,关了就行
        http.csrf().disable();
    }

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

下面来自定义的登录逻辑

创建一个service包,在其下新建一个LoginUserService 这个类实现UserDetailsService 接口 重写其中的 UserDetails loadUserByUsername(String username)方法
== 注·:因为我考虑不周到,需要修改一下,我们实体类的名字 将User类 修改为 UserVo,因为SpinrgSecurity里面也有一个User类 产生冲突了,==
在这里插入图片描述

package work.zx.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import work.zx.dao.PermissionMapper;
import work.zx.dao.RoleMapper;
import work.zx.dao.UserMapper;
import work.zx.entity.Permission;
import work.zx.entity.Role;
import work.zx.entity.UserVo;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

@Service
public class LoginUserService implements UserDetailsService {

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private PermissionMapper permissionMapper;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private UserMapper userMapper;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        //存放权限的
        Collection<GrantedAuthority> authorities = new ArrayList<>();

        System.out.println("自定义登录");
        //1、根据用户名去数据库查询,如果不存在抛出 UserNameNotFoundExeption异常
        UserVo userVo=userMapper.selectBYUserName(username);

        if(userVo==null){
            System.out.println("用户名不存在");
            throw new UsernameNotFoundException("用户名不存在");
        }
        //因为数据库中目前存放的是明文,一般在注册的时候都会加密,这里我就手动加密了
        String password=passwordEncoder.encode(userVo.getPassword());
       //拿到用户角色
        List<Role> roles=roleMapper.selectByUserId(userVo.getUid());
        for(Role role: roles){
            authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
            //拿到用户具有的权限
            List<Permission> permissionList=permissionMapper.selectByRoleId(role.getRid());
            for (Permission permission:permissionList){
                //添加到
                authorities.add(new SimpleGrantedAuthority(permission.getStr()));
            }
        }

        return new User(username,password,authorities);
    }
}

在这需要主注意一下,数据库中的角色名称 当然也可以称为角色标识符 必须以ROlE_为前缀
是Spring Security规定的,不要乱起哦。
在这里插入图片描述
最后是开启注解授权,也就是在Controller类上或者方法上加上注解即可授权那种
只需要在启动类上加一个注解即可
在这里插入图片描述

接下来我们编写几个接口测试一下角色和权限
如下:
在这里插入图片描述


    @GetMapping("/cs")
    @ResponseBody
    @PreAuthorize("hasAnyAuthority('sys:user:delete')")
    public String cs(){
        System.out.println("说明你具有delete权限");
        return "说明你具有delete权限";
    }

    @GetMapping("/test")
    @ResponseBody
    @PreAuthorize("hasRole('ROLE_admin')")
    public String test(){
        System.out.println("说明你具有ROLE_admin角色");
        return "说明你具有ROLE_admin角色"以上是关于万字长文 基于SpringBoot整合SpringSecurity的认证授权(角色+权限) 真案列有数据库有源码的主要内容,如果未能解决你的问题,请参考以下文章

五万字长文详解SpringBoot 操作 ElasticSearch

小姐姐提灯给你讲讲动态规划(万字长文)

C语言学习笔记-入门整合篇(十万字长文)

C语言学习笔记-入门整合篇(十万字长文)

基于234树手撕TreeMap红黑树源码(3万字长文带你走进红黑深处的细节)

Java--Mybatis万字长文经典面试题王者笔记《收藏版》