安全框架SpringSecurity-1(认证入门数据库授权)

一、Spring Security

①:什么是Spring Security

Spring Security是一个能够为基于Spring的企业应用系统提供声明式(注解)的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

可以一句话来概括:SpringSecurity 是一个安全框架

②:官方网址

https://spring.io/projects/spring-security/

中文网址:https://springdoc.cn/spring-security/servlet/authorization/authorize-http-requests.html

二、认证入门

①:安全入门项目

1.新建一个项目01_springsecurity

image.png

2.添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</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.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring security-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

3.创建3个controller

image.png

@RestController
@RequestMapping("/admin")
public class AdminController {
    
    @GetMapping("/query")
    public String queryInfo(){
        return "当前登录用户: admin";
    }
}
@RestController
@RequestMapping("/student")
public class StudentController {
    
    @GetMapping("/query")
    public String queryInfo(){
        return "当前登录用户: student";
    }
}
@RestController
@RequestMapping("/teacher")
public class TeacherController {
    
    @GetMapping("/query")
    public String queryInfo(){
        return "当前登录用户: teacher";
    }
}

4.启动程序

1.访问 http://localhost:8080/admin/query 会自动跳转到登录页面

image.png

框架生成的用户

  • 用户名: user
  • 密码: 在启动项目时,生成的临时密码(98d61d12-378d-45ab-97b4-04241651ccd2)

image.png

2.登录成功

image.png

3.登出http://localhost:8080/logout

image.png

②:自定义用户名和密码

application.yaml中配置如下

spring:
  security:
    user:
      name: root
      password: root

使用刚刚自定义的用户名和密码登录

image.png

image.png

③:多用户管理(基于内存)

1.创建配置类

image.png

/**
 * 用户详情服务接口
 *
 * @author: Coke
 * @DateTime: 2023/11/07/20:48
 **/
@Configuration
public class MySecurityUserConfig {
    
    @Bean
    public UserDetailsService userDetailsService () {
        // 创建两个用户
        UserDetails zhangsan = User.builder().username("张三").password("123456").roles("student").build();
        UserDetails lisi = User.builder().username("李四").password("123456").roles("teacher").build();
        // 创建一个内存用户详细信息管理器
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        // 将两个用户放到 内存用户详细信息管理器中
        manager.createUser(zhangsan);
        manager.createUser(lisi);
        return manager;
    }
    
    /*
     * 自定义用户 必须配置密码编辑器
     * NoOpPasswordEncoder(没有加密)
     * @DateTime: 2023/11/7 21:11
     *
     * @return PasswordEncoder
     * @author: Coke
     */
    @Bean
    public PasswordEncoder passwordEncoder () {
        return NoOpPasswordEncoder.getInstance();
    }
}

2.启动程序(使用配置类中的用户登录)

image.png

image.png

3.退出登录使用前面配置文件中的用户登录

image.png

  • 结论:可以删除配置文件中的用户了

④:密码处理(加密)

  • 前面的用户并没有真正加密

使用BCryptPasswordEncoder进行加密 (重新运行程序测试)

@Configuration
public class MySecurityUserConfig {
    
    @Bean
    public UserDetailsService userDetailsService () {
        // 创建两个用户
        UserDetails zhangsan = User.builder().username("张三").password(passwordEncoder().encode("123456")).roles("student").build();
        UserDetails lisi = User.builder().username("李四").password(passwordEncoder().encode("123456")).roles("teacher").build();
        // 创建一个内存用户详细信息管理器
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        // 将两个用户放到 内存用户详细信息管理器中
        manager.createUser(zhangsan);
        manager.createUser(lisi);
        return manager;
    }
    
    /*
     * 自定义用户 必须配置密码编辑器
     * NoOpPasswordEncoder(没有加密)
     * @DateTime: 2023/11/7 21:11
     *
     * @return PasswordEncoder
     * @author: Coke
     */
    @Bean
    public PasswordEncoder passwordEncoder () {
        return new BCryptPasswordEncoder();
    }
}

image.png

⑤:获取当前登录用户信息

1.创建CurrentLoginUserController

@RestController
@RequestMapping("/getLogin")
public class CurrentLoginUserController {
    
    @GetMapping("/user1")
    public Authentication getUser1(Authentication authentication){
        return authentication;
    }
    
    @GetMapping("/user2")
    public Principal getUser2(Principal principal){
        return principal;
    }
    
    @GetMapping("/user3")
    public Principal getUser3(){
        // 通过安全上下文持有器获取安全上下文,再获取认证信息
        return SecurityContextHolder.getContext().getAuthentication();
    }
}

2.启动程序 并登录

image.png

3.访问刚刚写的第一个controller的第一个接口
image.png
在这里插入图片描述

⑥:配置用户权限

    @Bean
    public UserDetailsService userDetailsService(){
        // 创建两个用户
        UserDetails user1 = User.builder()
                .username("张三")
                .password(passwordEncoder().encode("123456"))
                .roles("student")
                .authorities("student_delete", "student_add")
                .build();
        UserDetails user2 = User.builder()
                .username("李四")
                .password(passwordEncoder().encode("123456"))
                .authorities("teacher_delete", "teacher_add")
                .roles("teacher")
                .build();
        // 创建一个内存用户详细信息管理器
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        // 将两个用户放到 内存用户详细信息管理器中
        manager.createUser(user1);
        manager.createUser(user2);
        return manager;
    }

在这里插入图片描述

1.登录 张三 这个用户 然后查询用户信息

在这里插入图片描述

2.在登录李四 这个账户然后 查询用户信息

在这里插入图片描述

  • 结论:权限和角色按照配置的顺序生效 后者覆盖前者
  • 问题:虽然有了权限 但是并没对访问url生效

⑦:针对url进行授权

上面讲的实现了认证功能,但是受保护的资源是默认的,歌认所有认证(登录)用户均可以访问所有资源瓤不能根据实际情况进行角色管理,要实现授权功能,需重写WebSecurityConfigureAdapter中的一个configure方法

1.新建WebSecurityConfig类,重写configure(HttpSecurity http)方法

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests() // 授权请求
                // 匹配路径url的写法有三种
//               .regexMatchers("/student/**")
//               .antMatchers("/student/**")
                .mvcMatchers("/student/**") // 推荐这种, 匹配这个url
                // 判断 权限的五种
//               .hasAuthority( ) // 是否有单个权限
//               .access()
//               .hasRole() // 是否有单个角色
//               .hasAnyRole() // 是否有任意角色
                .hasAnyAuthority("student_add") // 拥有这个权限的用户可以访问 /student/** 这个url
                .mvcMatchers("/teacher/**") // 匹配url
                .hasAuthority("ROLE_teacher") //  拥有这个权限的用户可以访问 /teacher/** 这个url
                .anyRequest() // 任何请求
                .authenticated(); // 都需要登录,注意:没有配置mV℃匹配器的只要登录成功就可以访问
        http.formLogin().permitAll(); // 允许表单登录permit:允许
    }
}

在这里插入图片描述
在这里插入图片描述

⑧:针对方法进行授权

1.拷贝01_spring_security改名为02_spring_security
删除: AdminController和StudentController
新增: TeacherService、TeacherServiceImpl

在这里插入图片描述

1. TeacherController
@RestController
@RequestMapping("/teacher")
public class TeacherController {

    @Autowired
    private TeacherService teacherService;

    @GetMapping("/add")
    public String add(){
        return teacherService.add();
    }


    @GetMapping("/delete")
    public String delete(){
        return teacherService.delete();
    }

    @GetMapping("/update")
    public String update(){
        return teacherService.update();
    }

    @GetMapping("/query")
    public String query(){
        return teacherService.query();
    }
}
2.TeacherService
public interface TeacherService {
    // 添加教师
    String add();
    // 删除教师
    String delete();
    // 修改教师
    String update();
    // 查询教师
    String query();
}
3.TeacherServiceImpl
@Service
@Slf4j
public class TeacherServiceImpl implements TeacherService {

    @Override
    public String add() {
        log.info("添加教师成功!");
        return "添加教师成功!";
    }

    @Override
    public String delete() {
        log.info("删除教师成功!");
        return "删除教师成功!";
    }

    @Override
    public String update() {
        log.info("修改教师成功!");
        return "修改教师成功!";
    }

    @Override
    public String query() {
        log.info("查询教师成功!");
        return "查询教师成功!";
    }
}

2.修改MySecurityUserConfig配置类

@Configuration
public class MySecurityUserConfig {

    @Bean
    public UserDetailsService userDetailsService(){
        // 创建两个用户
        UserDetails user1 = User.builder()
                .username("张三")
                .password(passwordEncoder().encode("123456"))
                .roles("student") // 角色的前面加上 ROLE_ 就成了权限
                .build();
        UserDetails user2 = User.builder()
                .username("李四")
                .password(passwordEncoder().encode("123456"))
                .authorities("teacher:query") // 配置了教师的查询权限
                .build();
        UserDetails user3 = User.builder()
                .username("admin")
                .password(passwordEncoder().encode("123456"))
                .authorities("teacher:add","teacher:delete","teacher:update","teacher:query") // 配置了教师的增删改查权限
                .build();
        // 创建一个内存用户详细信息管理器
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        // 将两个用户放到 内存用户详细信息管理器中
        manager.createUser(user1);
        manager.createUser(user2);
        manager.createUser(user3);
        return manager;
    }

    /*
     * 自定义用户 必须配置密码编辑器
     * NoOpPasswordEncoder(没有加密)
     * @DateTime: 2023/11/7 21:11
     *
     * @return PasswordEncoder
     * @author: Coke
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

3.修改WebSecurityConfig配置类

  • 加上启用全局方法安全注解 @EnableGlobalMethodSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启全局方法安全,启用预授权注解和后授权注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated(); // 任何请求 都需要登录,注意:没有配置mV℃匹配器的只要登录成功就可以访问
        http.formLogin().permitAll(); // 放开登录页面
    }
}

4.修改TeacherServiceImpl类 在方法上加上预授权注解

@Service
@Slf4j
public class TeacherServiceImpl implements TeacherService {

    @Override
    @PreAuthorize("hasAuthority('teacher:add')") // 预授权 // hasAuthority('teacher:add') 一个权限可访问
    public String add() {
        log.info("添加教师成功!");
        return "添加教师成功!";
    }

    @Override
    @PreAuthorize("hasAnyAuthority('teacher:delete')") // hasAnyAuthority('teacher:add','teacher:delete'...) 可以有多权限
    public String delete() {
        log.info("删除教师成功!");
        return "删除教师成功!";
    }

    @Override
    @PreAuthorize("hasAnyAuthority('teacher:update')")
    public String update() {
        log.info("修改教师成功!");
        return "修改教师成功!";
    }

    @Override
    @PreAuthorize("hasAnyAuthority('teacher:query')")
    public String query() {
        log.info("查询教师成功!");
        return "查询教师成功!";
    }
}

5.测试

1.登录用户 张三 没有teacher的任何权限
在这里插入图片描述
2.登录用户 李四 有teacher的查询权限
在这里插入图片描述
3.登录用户 admin 有teacher的所有权限
在这里插入图片描述

⑨:处理返回结果及自定义用户信息

1. 处理返回结果

1.拷贝02_spring_security改名为03_spring_security
新增: WebSecurityConfig、Response

在这里插入图片描述

1.WebSecurityConfig
package com.it.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.it.vo.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @Author: CaoYouGen
 * @DateTime: 2023/11/08/13:21
 * @注释: TODO
 **/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启全局方法安全,启用预授权注解和后授权注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private ObjectMapper objectMapper; // 可以进行序列号(json)和反序列化

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated(); // 任何请求 都需要登录,注意:没有配置mV℃匹配器的只要登录成功就可以访问

        http.formLogin()
                // 配置登录成功的处理器
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        String responseJson = objectMapper.writeValueAsString(Response.ok("登录成功!"));
                        response.setCharacterEncoding("utf-8");
                        response.setContentType("application/json;charset=utf-8");
                        PrintWriter writer = response.getWriter();
                        writer.println(responseJson);
                        writer.flush();
                    }
                })
                // 配置登录失败的处理器
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
                        String responseJson = objectMapper.writeValueAsString(Response.error(1,"登录失败!"));
                        response.setCharacterEncoding("utf-8");
                        response.setContentType("application/json;charset=utf-8");
                        PrintWriter writer = response.getWriter();
                        writer.println(responseJson);
                        writer.flush();
                    }
                }).permitAll();

        // 配置退出成功处理器
        http.logout().logoutSuccessHandler(new LogoutSuccessHandler() {
            @Override
            public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                String responseJson = objectMapper.writeValueAsString(Response.ok("退出成功!"));
                response.setCharacterEncoding("utf-8");
                response.setContentType("application/json;charset=utf-8");
                PrintWriter writer = response.getWriter();
                writer.println(responseJson);
                writer.flush();
            }
        });

        // 配置访问拒绝处理器
        http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
                String responseJson = objectMapper.writeValueAsString(Response.error(1,"您没有权限访问该资源!"));
                response.setCharacterEncoding("utf-8");
                response.setContentType("application/json;charset=utf-8");
                PrintWriter writer = response.getWriter();
                writer.println(responseJson);
                writer.flush();
            }
        });
    }
}

2.Response
package com.it.vo;

import lombok.Data;
@Data
public class Response<T> {

    /**
     * 结果
     *
     * @mock true
     */
    private boolean success;

    /**
     * 状态码
     *
     * @mock 200
     */
    private int code;

    /**
     * 消息提示
     *
     * @mock 操作成功
     */

    private String msg;

    /**
     * 结果体
     *
     * @mock null
     */
    private T data;

    public Response() {

    }

    public Response(int code, Object status) {
        super();
        this.code = code;
        this.msg = status.toString();
        if (code == 1) {
            this.success = true;
        } else {
            this.success = false;
        }
    }

    public Response(int code, String status, T result) {
        super();
        this.code = code;
        this.msg = status;
        this.data = result;
        if (code == 1) {
            this.success = true;
        } else {
            this.success = false;
        }
    }

    public static Response<?> ok() {
        return new Response<>(1, "success");
    }

    public static <T> Response<T> ok(T t) {
        return new Response<T>(1, "success", t);
    }

    public static Response<?> error(String status) {
        return new Response<>(500, status);
    }

    public static Response<?> error(int code, String status) {
        return new Response<>(code, status);
    }
}

2.自定义用户信息

1.删除: MySecurityUserConfig
新增: SecurityUser、UserServiceImpl

在这里插入图片描述

1.新增SecurityUser
public class SecurityUser implements UserDetails {
    
    // 用户的权限
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities () {
        return null;
    }
    
    @Override
    public String getPassword () {
        return new BCryptPasswordEncoder().encode("123456");
    }
    
    @Override
    public String getUsername () {
        return "张三";
    }
    
    // 判断帐号是否已经过期
    @Override
    public boolean isAccountNonExpired () {
        return true;
    }
    
    // 判断帐号是否已被锁定
    @Override
    public boolean isAccountNonLocked () {
        return true;
    }
    // 判断用户凭证是否已经过期
    @Override
    public boolean isCredentialsNonExpired () {
        return true;
    }
    
    // 是否有效
    @Override
    public boolean isEnabled () {
        return true;
    }
}
2.新增UserServiceImpl
@Service
public class UserServiceImpl implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException {
        // 判断用户名是否为空
        if (!StringUtils.hasText(username)) {
            throw new UsernameNotFoundException("用户名不存在!");
        }
        // 判断用户是否正确
        if (!username.equals("张三")) {
            throw new UsernameNotFoundException("用户名不正确!");
        }
        // 执行到这里 说明用户名不为空 并且 用户名正确
        return new SecurityUser();
    }
}
3.修改WebSecurityConfig(在该类中新增以下方法)
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

2.测试

  • 使用 李四 这个用户登录

在这里插入图片描述

  • 使用 张三 这个用户登录

在这里插入图片描述

  • 登录成功后 访问teacher/query 的资源
    在这里插入图片描述
  • 查看张三的权限
    在这里插入图片描述

三、基于数据库认证

①:创建数据库和表

1.创建数据库(security_study

在这里插入图片描述

2.创建表

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '编号',
  `pid` int NULL DEFAULT NULL COMMENT '父级编号',
  `name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '名称',
  `code` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '权限编码',
  `type` int NULL DEFAULT NULL COMMENT '0代表菜单1权限2 url',
  `delete_flag` tinyint NULL DEFAULT 0 COMMENT '0代表未删除,1 代表已删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO `sys_menu` VALUES (1, 0, '学生管理', '/student/**', 0, 0);
INSERT INTO `sys_menu` VALUES (2, 1, '学生查询', 'student:query', 1, 0);
INSERT INTO `sys_menu` VALUES (3, 1, '学生添加', 'student:add', 1, 0);
INSERT INTO `sys_menu` VALUES (4, 1, '学生修改', 'student:update', 1, 0);
INSERT INTO `sys_menu` VALUES (5, 1, '学生删除', 'student:delete', 1, 0);
INSERT INTO `sys_menu` VALUES (6, 1, '导出学生信息', 'student:export', 1, 0);
INSERT INTO `sys_menu` VALUES (7, 0, '教师管理', '/teacher/**', 0, 0);
INSERT INTO `sys_menu` VALUES (9, 7, '教师查询', 'teacher:query', 1, 0);

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `rolename` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '角色名称,英文名称',
  `remark` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'ROLE_ADMIN', '管理员');
INSERT INTO `sys_role` VALUES (2, 'ROLE_TEACHER', '老师');
INSERT INTO `sys_role` VALUES (3, 'ROLE_STUDENT', '学生');

-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu`  (
  `rid` int NOT NULL COMMENT '角色表的编号',
  `mid` int NOT NULL COMMENT '菜单表的编号',
  PRIMARY KEY (`mid`, `rid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_role_menu
-- ----------------------------
INSERT INTO `sys_role_menu` VALUES (1, 1);
INSERT INTO `sys_role_menu` VALUES (3, 1);
INSERT INTO `sys_role_menu` VALUES (2, 2);
INSERT INTO `sys_role_menu` VALUES (3, 2);
INSERT INTO `sys_role_menu` VALUES (1, 3);
INSERT INTO `sys_role_menu` VALUES (2, 3);
INSERT INTO `sys_role_menu` VALUES (1, 4);
INSERT INTO `sys_role_menu` VALUES (2, 4);
INSERT INTO `sys_role_menu` VALUES (1, 5);
INSERT INTO `sys_role_menu` VALUES (2, 5);
INSERT INTO `sys_role_menu` VALUES (3, 6);
INSERT INTO `sys_role_menu` VALUES (1, 9);
INSERT INTO `sys_role_menu` VALUES (2, 9);
INSERT INTO `sys_role_menu` VALUES (3, 9);
INSERT INTO `sys_role_menu` VALUES (1, 10);
INSERT INTO `sys_role_menu` VALUES (1, 17);

-- ----------------------------
-- Table structure for sys_role_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_user`;
CREATE TABLE `sys_role_user`  (
  `uid` int NOT NULL COMMENT '用户编号',
  `rid` int NOT NULL COMMENT '角色编号',
  PRIMARY KEY (`uid`, `rid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;

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

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `user_id` int NOT NULL AUTO_INCREMENT COMMENT '编号',
  `username` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '登陆名',
  `password` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '密码',
  `sex` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '性别',
  `address` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '地址',
  `enabled` int NULL DEFAULT 1 COMMENT '是否启动账户0禁用 1启用',
  `account_no_expired` int NULL DEFAULT 1 COMMENT '账户是否没有过期0已过期 1 正常',
  `credentials_no_expired` int NULL DEFAULT 1 COMMENT '密码是否没有过期0已过期 1 正常',
  `account_no_locked` int NULL DEFAULT 1 COMMENT '账户是否没有锁定0已锁定 1 正常',
  PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'obama', '$2a$10$KyXAnVcsrLaHMWpd3e2xhe6JmzBi.3AgMhteFq8t8kjxmwL8olEDq', '男', '武汉', 1, 1, 1, 1);
INSERT INTO `sys_user` VALUES (2, 'thomas', '$2a$10$KyXAnVcsrLaHMWpd3e2xhe6JmzBi.3AgMhteFq8t8kjxmwL8olEDq', '男', '北京', 1, 1, 1, 1);
INSERT INTO `sys_user` VALUES (3, 'eric', '$2a$10$KyXAnVcsrLaHMWpd3e2xhe6JmzBi.3AgMhteFq8t8kjxmwL8olEDq', '男', '成都', 1, 1, 1, 1);

SET FOREIGN_KEY_CHECKS = 1;
  • 执行完以上sql后 一共创建了5张表
    在这里插入图片描述

②:创建新的模块

1. 创建、引入依赖、添加配置

1.创建新的模块(04_spring_security

在这里插入图片描述

2.引入依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </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.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--spring security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        <!--支持使用 JDBC 访问数据库 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.1</version>
        </dependency>
        <!-- mybatis-plus-generator -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.3.2</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.16</version>
        </dependency>

3.配置文件(数据库等配置信息)

server:
  port: 8099
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://您的ip地址:3306/security_study?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: 您的密码

mybatis:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true # 数据库中下划线 映射到实体类中大小写
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 输出sql语句

2.创建实体类与DAO

1.创建实体类 SysUser

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class SysUser implements Serializable {
    private Integer userId;
    private String username;
    private String password;
    private String sex;
    private String address;
    private Integer enabled;
    private Integer accountNoExpired;
    private Integer credentialsNoExpired;
    private Integer accountNoLocked;
}

2.创建MapperSysUseMapper

@Mapper
public interface SysUserMapper {
    /*
     * 根据用户名获取用户信息
     * @DateTime: 2023/11/8 21:40
     *
     * @param userName:
     * @return SysUser
     * @author: Coke
     */
    SysUser getUserName (@Param ("userName") String userName);
}

3.创建SysUserMapper.xml
在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.it.mapper.SysUserMapper">

    <!-- 这里定义SQL语句 -->
    <select id="getUserName" resultType="com.it.entity.SysUser">
        select user_id,
               username,
               password,
               sex,
               address,
               enabled,
               account_no_expired,
               credentials_no_expired,
               account_no_locked
        from sys_user
        where username = #{userName}
    </select>
</mapper>

3.实现Service层

1.创建SysUserService

public interface SysUserService {
    /*
     * 根据用户名获取用户信息
     * @DateTime: 2023/11/8 21:40
     *
     * @param userName:
     * @return SysUser
     * @author: Coke
     */
    SysUser getUserName (String userName);
}

2.创建实现了SysUserServiceImpl

@Service
@Slf4j
public class SysUserServiceImpl implements SysUserService {
    @Autowired
    private SysUserMapper sysUserMapper;
    @Override
    public SysUser getUserName (String userName) {
        return sysUserMapper.getUserName(userName);
    }
}

4. 创建安全用户与实现

1.创建SecurityUser
在这里插入图片描述

public class SecurityUser implements UserDetails {
    private final SysUser sysUser;
    
    public SecurityUser (SysUser sysUser) {
        this.sysUser = sysUser;
    }
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities () {
        // todo 还没有配置权限
        return null;
    }
    
    @Override
    public String getPassword () {
        return this.sysUser.getPassword();
    }
    
    @Override
    public String getUsername () {
        return this.sysUser.getUsername();
    }
    
    @Override
    public boolean isAccountNonExpired () {
        return this.sysUser.getAccountNoExpired().equals(1);
    }
    
    @Override
    public boolean isAccountNonLocked () {
        return this.sysUser.getAccountNoLocked().equals(1);
    }
    
    @Override
    public boolean isCredentialsNonExpired () {
        return this.sysUser.getCredentialsNoExpired().equals(1);
    }
    
    @Override
    public boolean isEnabled () {
        return this.sysUser.getEnabled().equals(1);
    }
}

2.创建SecurityUserDetailsServiceImpl

在这里插入图片描述

@Service
public class SecurityUserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private SysUserService sysUserService;
    @Override
    public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException {
        SysUser sysUser = sysUserService.getUserName(username);
        // 判断对象是否为空
        if (ObjectUtils.isEmpty(sysUser)){
            throw new UsernameNotFoundException("该用户不存在!");
        }
        // 判断用户是否可用
        if (!sysUser.getAccountNoExpired().equals(1)) {
            throw new UsernameNotFoundException("该账户已过期!");
        }
        
        return new SecurityUser(sysUser);
    }
}

5. 创建安全配置类与Controller层

1.创建安全配置类``

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    // 对密码进行编码
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    
    
    @Override
    protected void configure (HttpSecurity http) throws Exception {
        // 任何请求 都需要登录,注意:没有配置mV℃匹配器的只要登录成功就可以访问
        http.authorizeRequests().anyRequest().authenticated();
        http.formLogin().permitAll();
    }
}

2.新建三个ControllerStudentController TeacherController CurrentLoginUserController

  • StudentController
@RestController
@Slf4j
@RequestMapping ("/student")
public class StudentController {
    @GetMapping ("/query")
    public String queryInfo(){
        return "query student";
    }
    @GetMapping("/add")
    public String addInfo(){
        return "add  student!";
    }
    @GetMapping("/update")
    public String updateInfo(){
        return "update student";
    }
    
    @GetMapping("/delete")
    public String deleteInfo(){
        return "delete  student!";
    }
    @GetMapping("/export")
    public String exportInfo(){
        return "export  student!";
    }
}
  • TeacherController
@RestController
@Slf4j
@RequestMapping ("/teacher")
public class TeacherController {
    @GetMapping ("/query")
    @PreAuthorize ("hasAuthority('teacher:query')")
    public String queryInfo(){
        return "I am a teacher!";
    }
}
  • CurrentLoginUserController
@RestController
@RequestMapping("/getLogin")
public class CurrentLoginUserController {

    @GetMapping("/user1")
    public Authentication getUser1(Authentication authentication) {
        return authentication;
    }

    @GetMapping("/user2")
    public Principal getUser2(Principal principal){
        return principal;
    }

    @GetMapping("/user3")
    public Principal getUser3(){
        // 通过安全上下文持有器获取安全上下文,再获取认证信息
        return SecurityContextHolder.getContext().getAuthentication();
    }
}

6.启动测试

1.使用thomas和obama分别登录测试,发现student/query等能访问,teacher/query 不能访问

在这里插入图片描述

2.原因:发现用户没有权限,但是/teacher/query 需要访问权限

在这里插入图片描述

四、基于数据库的授权

①:创建实体类、Mapper、service

1.创建菜单(权限)实体类SysMenu

@Data
public class SysMenu implements Serializable {
    private Integer id;
    private Integer pid;
    private Integer type;
    private String name;
    private String code;
}

2.创建mapperSysMenuMapper

public interface SysMenuMapper {
    List<String> queryPermissionsByUserId(@Param("userId") Integer userId);
}

3.创建SysMenuMapper.xml

在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.it.mapper.SysMenuMapper">

    <!-- 这里定义SQL语句 -->
<select id="queryPermissionsByUserId" resultType="string">
    select distinct sm.code
    from sys_role_user sru
             join sys_role_menu srm on sru.rid = srm.rid
             join sys_menu sm on srm.mid = sm.id where sru.rid = #{userId};
</select>
</mapper>

4.创建serviceSysMenuService

public interface SysMenuService {
    List<String> queryPermissionsByUserId(Integer userId);
}

5.创建SysMenuServiceImpl

@Service
@Slf4j
public class SysMenuServiceImpl implements SysMenuService {

    @Resource
    private SysMenuMapper sysMenuMapper;

    @Override
    public List<String> queryPermissionsByUserId(Integer userId) {
        return sysMenuMapper.queryPermissionsByUserId(userId);
    }
}

②: 配置权限

1.修改SecurityUser实体类

  • 加入一个属性
private List<SimpleGrantedAuthority> simpleGrantedAuthorities;

2.修改方法getAuthorities

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities () {
        // todo 配置权限
        return simpleGrantedAuthorities;
    }

3.添加一个set方法

public void setSimpleGrantedAuthorities(List<SimpleGrantedAuthority> simpleGrantedAuthorities) {
        this.simpleGrantedAuthorities = simpleGrantedAuthorities;
    }

在这里插入图片描述

4.修改SecurityUserDetailsServiceImpl

  • 增加设置权限的步骤
@Service
public class SecurityUserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private SysMenuService sysMenuService;

    @Override
    public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException {
        SysUser sysUser = sysUserService.getUserName(username);
        // 判断对象是否为空
        if (ObjectUtils.isEmpty(sysUser)){
            throw new UsernameNotFoundException("该用户不存在!");
        }
        // 判断用户是否可用
        if (!sysUser.getAccountNoExpired().equals(1)) {
            throw new UsernameNotFoundException("该账户已过期!");
        }
        // 通过用户id获取用户的所有权限
        List<String> permissions = sysMenuService.queryPermissionsByUserId(sysUser.getUserId());

        // 使用Stream流将 List<String> permissions 转换为 List<SimpleGrantedAuthority> grantedAuthorities
        List<SimpleGrantedAuthority> grantedAuthorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
        // 创建一个用户权限对象
        SecurityUser securityUser = new SecurityUser(sysUser);
        // 将权限设置到用户对象中
        securityUser.setSimpleGrantedAuthorities(grantedAuthorities);
        // 返回
        return securityUser;
    }
}

③:启动程序测试

  • 使用thomas和obama分别登录测试,发现已经有权限功能了
在这里插入图片描述 在这里插入图片描述

④:手动擦除密码防止传到前端

  • 擦除密码前
    在这里插入图片描述

1. 修改SecurityUser类中的getPassword方法

    @Override
    public String getPassword () {
        String myPassword = this.sysUser.getPassword();
        // 手动擦除密码防止传到前端
        this.sysUser.setPassword(null);
        return myPassword;
    }

在这里插入图片描述

  • 重启服务再次登录查看

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/132595.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】C语言实现单链表万字详解(附完整运行代码)

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 一.了解项目功能 在本次项目中我们的目标是实现一个单链表: 该单链表使用动态内存分配空间,可以用来存储任意数量的同类型数据. 单链表结点(Node)需要包含两个要素:数据域data…

BMVC 23丨多模态CLIP:用于3D场景问答任务的对比视觉语言预训练

来源&#xff1a;投稿 作者&#xff1a;橡皮 编辑&#xff1a;学姐 论文链接&#xff1a;https://arxiv.org/abs/2306.02329 摘要&#xff1a; 训练模型将常识性语言知识和视觉概念从 2D 图像应用到 3D 场景理解是研究人员最近才开始探索的一个有前景的方向。然而&#xff0c…

【线上问题】服务器关机导致docker启动的mysql数据库消失了

目录 一、问题描述二、解决方式 一、问题描述 1. 服务器迁移断电导致docker启动的mysql数据库没有了数据 2. data目录是空的 3. mysql重启数据库消失了 二、解决方式 1. sudo -i切换root账号 2. 查找mysql的容器卷 find /var/lib/docker/volumes/ -name mysql3. 进入各个_dat…

FTP、NFS以及SAMBA服务

一、FTP服务 1、Linux下ftp客户端管理工具 ftp、lftp都是Linux下ftp的客户端管理工具&#xff0c;但是需要独立安装 # yum install ftp lftp -y ☆ ftp工具 # ftp 10.1.1.10 Connected to 10.1.1.10 (10.1.1.10). 220 (vsFTPd 3.0.2) Name (10.1.1.10:root): 输入FTP的账号…

游戏公司数据分析师必备知识(持续补充中...)

1.如何撰写专题报告&#xff1f; ①原则 只有一个主题&#xff1a;即使不讲ppt&#xff0c;业务方也能看得懂行文通俗简单易懂&#xff1a;学习产品经理平常是如何写报告的明确的数据结论和落地项先行&#xff1a;跟业务方多沟通数据结论&#xff0c;让他们给出落地项 ②结构…

CSS特效第一弹:右上角tag标志纯代码前端实现(非图片)

&#x1f60e;效果&#xff1a; &#x1f937;‍♂️思路&#xff1a; 分为2个部分&#xff1a; 1.文字方块右下角折角 文字方块用绝对定位z-index让文字方块悬浮在右上角的位置 2.右下角折角通过before伪元素border属性实现(三角形实现方法&#xff09; &#x1f44d;核心代…

基于springboot乐器视频学习网站设计与实现(源码齐全可用)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

Redis(12)| 过期删除策略和内存淘汰策略

Redis 是可以对 key 设置过期时间的&#xff0c;因此需要有相应的机制将已过期的键值对删除&#xff0c;而做这个工作的就是过期键值删除策略。 如何设置过期时间 先说一下对 key 设置过期时间的命令。 设置 key 过期时间的命令一共有 4 个&#xff1a; expire key n&#x…

js 根据当前时间往前推15天或往后推15天等(例如当前时间往后15天后的日期。并实现今天、明天、后天、周)

本次分享&#xff0c;在项目中开发车票购买功能需要用到日期筛选 思路&#xff1a; 1、首先获取当前时间戳 2、根据当前时间戳拿到15天后的日期 3、根据天匹配星期几 4、将时间戳转换年、月、日并重组 实现代码 // 获取当前日期 const today new Date();// 往前推15天的…

畅通工程之局部最小花费问题 (C++)

目录 题目&#xff1a; 思路&#xff1a; 代码&#xff1a; 结果 题目&#xff1a; 思路&#xff1a; 详细思路都在代码注释里 。 代码&#xff1a; #include<iostream>//无向图邻接矩阵 #include<map> #include<algorithm> #define mvnum 1005 using …

“辛巴猫舍”内网渗透、提权、撞库学习笔记

前言&#xff1a; 在拿到靶机时&#xff0c;我们最先需要做的是信息收集&#xff0c;包括不限于&#xff1a;C段扫描&#xff0c;端口探测&#xff0c;指纹识别&#xff0c;版本探测等。其次就是 漏洞挖掘、漏洞利用、提权、维持权限、日志清理、留下后门。 以上就是渗透的基本…

时序预测 | MATLAB实现WOA-CNN-BiGRU-Attention时间序列预测(SE注意力机制)

时序预测 | MATLAB实现WOA-CNN-BiGRU-Attention时间序列预测&#xff08;SE注意力机制&#xff09; 目录 时序预测 | MATLAB实现WOA-CNN-BiGRU-Attention时间序列预测&#xff08;SE注意力机制&#xff09;预测效果基本描述模型描述程序设计参考资料 预测效果 基本描述 1.MATLA…

ElasticSearch知识点

什么是ElasticSearch ElasticSearch: 智能搜索&#xff0c;分布式的搜索引擎&#xff0c;是ELK的一个非常完善的产品&#xff0c;ELK代表的是: E就是ElasticSearch&#xff0c;L就是Logstach&#xff0c;K就是kibana Elasticsearch是一个建立在全文搜索引擎 Apache Lucene基础…

Docker配置Nginx反向代理

文章目录 1.部署微程序到docker中1.1 dockerfile文件1.2 依据自定义的dockerfile文件创建docker镜像1.3 创建容器1.4 测试 2.在docker中安装Nginx2.1 安装Nginx镜像2.2 获取Nginx配置文件并将其同步到宿主电脑指定位置中安装nginx容器删除nginx容器 2.3 安装Nginx容器并数据挂载…

【技术支持】DevTools中重写覆盖源js文件

sources面板下&#xff0c;左侧overrides标签下添加一个文件夹&#xff0c;并同意。 勾选Enable Local overrides 然后在page标签下&#xff0c;修改文件后ctrls保存 直接就保存在overrides的文件夹下了 或者文件上右键Override content

中低收入群体能在“双十一”购物狂欢吗?

今天这个“双十一”购物狂欢节&#xff0c;在各大网站的报道的确蜂拥而上&#xff0c;显得很有点儿“狂欢”的景象&#xff0c;可读罢内容却听到哀鸿遍野。 笔者仅只接力“腾迅新闻”和“今日头条”几小时前分别发表的《 双11十五年&#xff0c;价格战还能打多久&#xff1f;》…

面向萌新的技术博客入门指南

Python之禅 在Python的解释器中隐藏一个彩蛋&#xff0c;输入import this就会返回19条Python之禅&#xff0c;具体如下&#xff1a; import this The Zen of Python, by Tim Peters Python之禅 &#xff0c;by Tim Peters Beautiful is better than ugly. 优美好于丑陋&…

FTP、NFS、SAMBA系统服务一

一、rsync托管xinetd 1、为什么要进行服务托管 独立服务&#xff1a;独立启动脚本 ssh ftp nfs dns ... 依赖服务: 没有独立的启动脚本 rsync telnet 依赖xinetd服务&#xff08;独立服务&#xff09; 2、如何将rsync托管给xinetd服务去管理&#xff1f; 第一步&#xff1…

飞机社交软件开发:重新定义社交媒体的空中交互体验

【导语】 随着互联网技术的快速发展&#xff0c;社交媒体平台的界限也逐渐模糊。飞机社交软件应运而生&#xff0c;打破传统的地面社交模式&#xff0c;为空中旅行的旅客提供全新的交流平台。本文将从市场需求、技术实现、用户体验和未来发展等方面&#xff0c;深入探讨飞机社交…

推荐几个宝藏app

立冬后&#xff0c;真尼玛冷&#xff0c;哎&#xff01;记得多穿点衣服呀&#xff0c;老铁们&#xff01;&#xff01; GKD 去广告神器 下载网址&#xff1a;https://github.com/gkd-kit/gkd 特性&#xff1a; 它不仅支持跳过开屏广告&#xff0c;还支持跳过弹窗广告等&#xf…