主页 > 游戏开发  > 

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

安全框架SpringSecurity-1(认证入门数据库授权)
一、Spring Security ①:什么是Spring Security

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

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

②:官方网址

spring.io/projects/spring-security/

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

二、认证入门 ①:安全入门项目 1.新建一个项目01_springsecurity

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

@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 会自动跳转到登录页面

框架生成的用户

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

2.登录成功

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

②:自定义用户名和密码

application.yaml中配置如下

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

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

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

1.创建配置类

/** * 用户详情服务接口 * * @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.启动程序(使用配置类中的用户登录)

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

结论:可以删除配置文件中的用户了 ④:密码处理(加密) 前面的用户并没有真正加密

使用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(); } }

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

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.启动程序 并登录

3.访问刚刚写的第一个controller的第一个接口

⑥:配置用户权限 @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; }

重启服务再次登录查看

标签:

安全框架SpringSecurity-1(认证入门数据库授权)由讯客互联游戏开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“安全框架SpringSecurity-1(认证入门数据库授权)