Spring Boot集成Security快速入门Demo

server/2024/10/9 6:40:30/

1.什么是Security?

Spring Security是一个Java框架,用于保护应用程序的安全性。它提供了一套全面的安全解决方案,包括身份验证、授权、防止攻击等功能。Spring Security基于过滤器链的概念,可以轻松地集成到任何基于Spring的应用程序中。它支持多种身份验证选项和授权策略,开发人员可以根据需要选择适合的方式。此外,Spring Security还提供了一些附加功能,如集成第三方身份验证提供商和单点登录,以及会话管理和密码编码等。总之,Spring Security是一个强大且易于使用的框架,可以帮助开发人员提高应用程序的安全性和可靠性。

how it works?

22

  • Filter

拦截Http请求,获取用户名和秘密等认证信息 关键方法:

public abstract Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException, IOException, ServletException;
  • AuthenticationManager

从filter中获取认证信息,然后查找合适的AuthenticationProvider来发起认证流程 关键方法:

Authentication authenticate(Authentication authentication) throws AuthenticationException;
  • AuthenticationProvider

调用UserDetailsService来查询已经保存的用户信息并与从http请求中获取的认证信息比对。如果成功则返回,否则则抛出异常。 关键方法:

protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)throws AuthenticationException;
  • UserDetailsService

负责获取用户保存的认证信息,例如查询数据库。 关键方法:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

这些组件都是抽象的,每个都可以有不同的实现,换句话说都是可以定制,特别灵活,所以就特别复杂。具体到我们这个默认的例子中,使用的都是默认实现:

  • Filter: UsernamePasswordAuthenticationFilter
  • AuthenticationManager: ProviderManager
  • AuthenticationProvider: DaoAuthenticationProvider
  • UserDetailsService: InMemoryUserDetailsManager

业务流程是不是很清晰,之所以感觉复杂是因为经过框架的一顿设计,拉长了调用链。虽然设计上复杂了,但是如果理解了这套设计流程,终端用户使用就会简单很多,不理解的话,感觉特别复杂

2.环境搭建

mysql database

 

docker run --name docker-mysql-5.7 -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -d mysql:5.7

init data

create database demo;DROP TABLE IF EXISTS `jwt_user`; 
CREATE TABLE `jwt_user`(`id` varchar(32) CHARACTER SET utf8 NOT NULL COMMENT '用户ID',`username` varchar(100) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '登录账号',`password` varchar(255) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '密码'
)ENGINE = InnoDB CHARACTER SET = utf8 COMMENT = '用户表' ROW_FORMAT = Compact;INSERT INTO jwt_user VALUES('1','admin','123');-- ----------------------------
-- Table structure for menu
-- ----------------------------
CREATE TABLE `menu` (`id` int(11) NOT NULL AUTO_INCREMENT,`pattern` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;-- ----------------------------
-- Records of menu
-- ----------------------------
INSERT INTO `menu` VALUES ('1', '/db/**');
INSERT INTO `menu` VALUES ('2', '/admin/**');
INSERT INTO `menu` VALUES ('3', '/user/**');-- ----------------------------
-- Table structure for menu_role
-- ----------------------------
CREATE TABLE `menu_role` (`id` int(11) NOT NULL AUTO_INCREMENT,`mid` int(11) DEFAULT NULL,`rid` int(11) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;-- ----------------------------
-- Records of menu_role
-- ----------------------------
INSERT INTO `menu_role` VALUES ('1', '1', '1');
INSERT INTO `menu_role` VALUES ('2', '2', '2');
INSERT INTO `menu_role` VALUES ('3', '3', '3');-- ----------------------------
-- Table structure for role
-- ----------------------------
CREATE TABLE `role` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(32) DEFAULT NULL,`nameZh` varchar(32) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('1', 'dba', '数据库管理员');
INSERT INTO `role` VALUES ('2', 'admin', '系统管理员');
INSERT INTO `role` VALUES ('3', 'user', '用户');-- ----------------------------
-- Table structure for user
-- ----------------------------
CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(32) DEFAULT NULL,`password` varchar(255) DEFAULT NULL,`enabled` tinyint(1) DEFAULT NULL,`locked` tinyint(1) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of user
-- password is 123
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'root', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', '1', '0');
INSERT INTO `user` VALUES ('2', 'admin', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', '1', '0');
INSERT INTO `user` VALUES ('3', 'sang', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', '1', '0');-- ----------------------------
-- Table structure for user_role
-- ----------------------------
CREATE TABLE `user_role` (`id` int(11) NOT NULL AUTO_INCREMENT,`uid` int(11) DEFAULT NULL,`rid` int(11) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES ('1', '1', '1');
INSERT INTO `user_role` VALUES ('2', '1', '2');
INSERT INTO `user_role` VALUES ('3', '2', '2');
INSERT INTO `user_role` VALUES ('4', '3', '3');

remark

msyql account:root
mysql password:123456

3.代码工程

实验目的:实现基于mysql来控制权限

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>springboot-demo</artifactId><groupId>com.et</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>security</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId></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-security</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.2</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.10</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><!-- <build><plugins><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.4.0</version><dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version></dependency></dependencies><executions><execution><id>Generate MyBatis Artifacts</id><phase>package</phase><goals><goal>generate</goal></goals></execution></executions><configuration><verbose>true</verbose><overwrite>true</overwrite><configurationFile>src/main/resources/mybatis-generator.xml</configurationFile></configuration></plugin></plugins></build>-->
</project>

config

package com.et.security.config;import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;import java.util.Collection;/*** Comparing role information in the AccessDecisionManager class*/
@Component
public class CustomAccessDecisionManager implements AccessDecisionManager {/*** Override the decide method, in which it is judged whether the currently logged in user has the role information required for the current request URL. If not, an AccessDeniedException is thrown, otherwise nothing is done.** @param auth   Information about the currently logged in user* @param object FilterInvocationObject, you can get the current request object* @param ca     FilterInvocationSecurityMetadataSource The return value of the getAttributes method in is the role required by the current request URL*/@Overridepublic void decide(Authentication auth, Object object, Collection<ConfigAttribute> ca) {Collection<? extends GrantedAuthority> auths = auth.getAuthorities();for (ConfigAttribute configAttribute : ca) {/** If the required role is ROLE_LOGIN, it means that the currently requested URL can be accessed after the user logs in.* If auth is an instance of UsernamePasswordAuthenticationToken, it means that the current user has logged in and the method ends here, otherwise it enters the normal judgment process.*/if ("ROLE_LOGIN".equals(configAttribute.getAttribute()) && auth instanceof UsernamePasswordAuthenticationToken) {return;}for (GrantedAuthority authority : auths) {// If the current user has the role required by the current request, the method endsif (configAttribute.getAttribute().equals(authority.getAuthority())) {return;}}}throw new AccessDeniedException("no permission");}@Overridepublic boolean supports(ConfigAttribute attribute) {return true;}@Overridepublic boolean supports(Class<?> clazz) {return true;}
}
package com.et.security.config;import com.et.security.entity.Menu;
import com.et.security.entity.Role;
import com.et.security.mapper.MenuMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;@Component
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {AntPathMatcher antPathMatcher = new AntPathMatcher(); // AntPathMatcher is mainly used to implement ant-style URL matching.@ResourceMenuMapper menuMapper;// Spring Security uses the getAttributes method in the FilterInvocationSecurityMetadataSource interface to determine which roles are required for a request.    @Overridepublic Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {/** The parameter of this method is a FilterInvocation. Developers can extract the currently requested URL from the FilterInvocation.* The return value is Collection<ConfigAttribute>, indicating the role required by the current request URL.*/String requestUrl = ((FilterInvocation) object).getRequestUrl();/** Obtain all resource information from the database, that is, the menu table in this case and the role corresponding to the menu,* In a real project environment, developers can cache resource information in Redis or other cache databases.*/List<Menu> allMenus = menuMapper.getAllMenus();// Traverse the resource information. During the traversal process, obtain the role information required for the currently requested URL and return it.for (Menu menu : allMenus) {if (antPathMatcher.match(menu.getPattern(), requestUrl)) {List<Role> roles = menu.getRoles();String[] roleArr = new String[roles.size()];for (int i = 0; i < roleArr.length; i++) {roleArr[i] = roles.get(i).getName();}return SecurityConfig.createList(roleArr);}}// If the currently requested URL does not have a corresponding pattern in the resource table, it is assumed that the request can be accessed after logging in, that is, ROLE_LOGIN is returned directly.return SecurityConfig.createList("ROLE_LOGIN");}/*** The getAllConfigAttributes method is used to return all defined permission resources. SpringSecurity will verify whether the relevant configuration is correct when starting.* If verification is not required, this method can directly return null.*/@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() {return null;}/*** The supports method returns whether the class object supports verification.*/@Overridepublic boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);}
}
package com.et.security.config;import com.et.security.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredUserService userService;@BeanRoleHierarchy roleHierarchy() {RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();String hierarchy = "ROLE_dba > ROLE_admin ROLE_admin > ROLE_user";roleHierarchy.setHierarchy(hierarchy);return roleHierarchy;}@BeanPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// The memory user is not configured, but the UserService just created is configured into the AuthenticationManagerBuilder.auth.userDetailsService(userService);}@Order@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()/*.antMatchers("/admin/**").hasRole("ADMIN")//Indicates that the user accessing the URL in the "/admin/**" mode must have the role of ADMIN.antMatchers("/user/**").access("hasAnyRole('ADMIN','USER')").antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')").anyRequest().authenticated()//Indicates that in addition to the URL pattern defined previously, users must access other URLs after authentication (access after logging in) */.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O object) {object.setSecurityMetadataSource(cfisms());object.setAccessDecisionManager(cadm());return object;}}).and().formLogin().loginProcessingUrl("/login").permitAll().and().csrf().disable();}@BeanCustomFilterInvocationSecurityMetadataSource cfisms() {return new CustomFilterInvocationSecurityMetadataSource();}@BeanCustomAccessDecisionManager cadm() {return new CustomAccessDecisionManager();}
}

代码生成

配置文件在resource目录下,mybatis-generator.xml

如何生成代码?

可以参见这篇文章?Spring Boot集成Druid快速入门Demo

DemoApplication.java

package com.et.security;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan(value = "com.et.security.mapper")
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}

application.yaml

server:port: 8088
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourceurl: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456mybatis:mapper-locations:- classpath:mapper/**/*.xml

以上只是一些关键代码,所有代码请参见下面代码仓库

代码仓库

  • GitHub - Harries/springboot-demo: a simple springboot demo with some components for example: redis,solr,rockmq and so on.

4.测试

  • 启动Spring Boot应用
  • 访问地址http://127.0.0.1:8088/hello
  • 此时会要求登陆,输入数据库不同角色的用户,验证不同的权限(密码都是加密的123)

5.引用

  • Spring Boot集成Security快速入门Demo | Harries Blog™
  • Spring Boot Security Auto-Configuration | Baeldung
  • 先决条件 :: Spring Security Reference

http://www.ppmy.cn/server/42543.html

相关文章

docker redis 持久化

1、拉取redis镜像 docker pull redis:latest 2、 mkdir /data/redis 3、填充redis.conf文件及根据需求修改相应的配置 •通过官网地址找到对应版本的配置文件 •将配置信息复制到redis.conf中 •常见的修改配置 https://redis.io/docs/latest/operate/oss_and_stack/managem…

hudi相关疑问

标题 1、flink流式写入hudi表时&#xff0c;Changelog模式和Append模式区别Changelog 模式Append 模式配置示例配置 Append 模式配置 Changelog 模式 总结 2、flink流式写入hudi表时&#xff0c;设置了Changelog模式&#xff0c;还需要设置write.operation参数吗Changelog 模式…

vue打包部署到springboot中,看这篇就够了

如果不清楚springboot中的static和templates目录可以看这篇 static和templates目录 1、问题 vue打包后部署到springboot中访问&#xff0c;毕竟前后端分离部署的时候要分开&#xff0c;多了一个服务&#xff0c;可以将vue打包后放在springboot中的static目录下&#xff0c;网…

蓝桥杯-合并数列

小明发现有很多方案可以把一个很大的正整数拆成若干正整数的和。他采取了其中两种方案&#xff0c;分别将它们列为两个数组 {a1, a2, …, an} 和 {b1, b2, …, bm}。两个数组的和相同。 定义一次合并操作可以将某数组内相邻的两个数合并为一个新数&#xff0c;新数的值是原来两…

【vue/ucharts】ucharts 自定义格式化 y 轴数据显示(横向柱状图常用)

使用 ucharts 的柱状图时&#xff0c;尤其是横向柱状图会更常见&#xff0c;会有自定义 y 轴数据的情况&#xff0c;就像使用过滤器时对数据进行格式化以达到自己想要的效果一样&#xff1b; 比如我想要这样的效果&#xff1a; 官网里的栗子如图所示&#xff1a; 但是如果此…

Linux——进程信号(一)

1.信号入门 1.1生活中的信号 什么是信号? 结合实际红绿灯、闹钟、游戏中的"&#xff01;"等等这些都是信号。 以红绿灯为例子&#xff1a; 一看到红绿灯我们就知道&#xff1a;红灯停、绿灯行&#xff1b;我们不仅知道它是一个红绿灯而且知道当其出现不同的状况…

c++面试题记录

面向对象的程序设计思想是什么&#xff1f; 答&#xff1a;把数据结构和对数据结构进行操作的方法封装形成一个个的对象。 在头文件中进行类的声明&#xff0c;在对应的实现文件中进行类的定义有什么意义&#xff1f; 答&#xff1a;这样可以提高编译效率&#xff0c;因为分开的…

信息泄露--注意点点

目录 明确目标: 信息泄露: 版本软件 敏感文件 配置错误 url基于文件: url基于路由: 状态码: http头信息泄露 报错信息泄露 页面信息泄露 robots.txt敏感信息泄露 .get文件泄露 --判断: 搜索引擎收录泄露 BP: 爆破: 明确目标: 失能 读取 写入 执行 信息泄…