Spring Boot集成Security快速入门Demo


1.什么是Security?

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

how it works?

  • 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:rootmysql 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 */@Componentpublic 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 */ @Override public 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 ends if (configAttribute.getAttribute().equals(authority.getAuthority())) { return; } } } throw new AccessDeniedException("no permission"); } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public 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;
@Componentpublic class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { AntPathMatcher antPathMatcher = new AntPathMatcher(); // AntPathMatcher is mainly used to implement ant-style URL matching. @Resource MenuMapper menuMapper;
// Spring Security uses the getAttributes method in the FilterInvocationSecurityMetadataSource interface to determine which roles are required for a request. @Override public 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. */ @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } /** * The supports method returns whether the class object supports verification. */ @Override public 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;
@Configurationpublic class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserService userService;
@Bean RoleHierarchy roleHierarchy() { RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); String hierarchy = "ROLE_dba > ROLE_admin ROLE_admin > ROLE_user"; roleHierarchy.setHierarchy(hierarchy); return roleHierarchy; } @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected 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 @Override protected 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>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O object) { object.setSecurityMetadataSource(cfisms()); object.setAccessDecisionManager(cadm()); return object; } }) .and() .formLogin() .loginProcessingUrl("/login").permitAll() .and() .csrf().disable(); } @Bean CustomFilterInvocationSecurityMetadataSource cfisms() { return new CustomFilterInvocationSecurityMetadataSource(); } @Bean CustomAccessDecisionManager 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: 8088spring:  datasource:    type: com.alibaba.druid.pool.DruidDataSource    url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai    username: root    password: 123456
mybatis: mapper-locations: - classpath:mapper/**/*.xml

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


代码仓库

  • https://github.com/Harries/springboot-demo

4.测试

  • 启动Spring Boot应用

  • 访问地址http://127.0.0.1:8088/hello

  • 此时会要求登陆,输入数据库不同角色的用户,验证不同的权限(密码都是加密的123)

5.引用

  • http://www.liuhaihua.cn/archives/710549.html

  • https://www.baeldung.com/spring-boot-security-autoconfiguration

  • https://springdoc.cn/spring-security/prerequisites.html


相关推荐

  • 扯什么 try-catch 性能问题?
  • Spring Security 如何防止暴力破解?
  • 面试官:BIO、NIO、AIO 的区别是什么?
  • 女同事35岁,五一节后再没露面,听说是被裁拿了10万,今天看到她退了群,但领导又反悔了,让她把10万补偿退回来。
  • [开源]一款多租户Saas快速开发平台,完全免费给个人及企业使用
  • 实操教程|称霸Kaggle的十大深度学习技巧
  • 早逝录:南林教师宋凯博士,因首聘期考核未过自杀身亡,享年38岁!
  • 可以一直做的低成本蓝海项目,适合新人练手
  • 每日 prompt:推荐这个风格画漫画
  • 谷歌也出大招了;字节推豆包大模型全家桶;Ilya Sutskever宣布退出OpenAI
  • OpenAI首席科学家Ilya离职,一个让马斯克与佩奇决裂的男人
  • 谷歌7大模型22项AI大招轰炸:70秒视频生成、Gemini安卓合体、200万tokens上下文
  • 腾讯混元文生图大模型全面开源!Sora同架构,更懂中文,免费商用
  • 字节豆包大模型发布!“比行业价格低99%”,对话火山引擎总裁谭待
  • 华为余承东履新后首次登台,Pura70“重新发布”,十七款重磅新品炸场
  • 打工人效率暴增神器!AI时代硬核办公图鉴,TWS耳机成全能会议助理
  • 全新的分布式锁,几行代码搞定,简单且强大
  • 32 个 Python 实例彻底解析 f-String 格式化浮点数 004【推荐收藏】
  • 京东秒送售后系统退款业务重构心得
  • 【腾讯云 BI 数据分析可视化大赛】有奖征文活动