Spring Security
官方文档:https://docs.spring.io/spring-security/reference/index.html
功能
- 身份认证(authentication)
- 授权(authorization)
- 防御常见攻击(protection against common attacks)
身份认证
官方代码示例:GitHub - spring-projects/spring-security-samples
- 身份认证是验证谁正在访问系统资源,判断用户是否为合法用户。认证用户的常见方式是要求用户输入用户名和密码
授权
- 用户进行身份认证后,系统会控制谁能访问哪些资源,这个过程叫做授权。用户无法访问没有权限的资源
防御常见攻击
- CSRF
- HTTP Headers
- HTTP Requests
默认
- 保护应用程序 URL,要求对应用程序的任何交互进行身份验证
- 程序启动时生成一个默认用户 “user”
- 生成一个默认的随机密码,并将此密码记录在控制台上
- 生成默认的登录表单和注销页面
- 提供基于表单的登录和注销流程
- 对于Web 请求,重定向到登录页面
- 对于服务请求,返回 401 未经授权
- 处理跨站请求伪造(CSRF)攻击
- 处理会话劫持攻击
- 写入 Strict-Transport-Security 以确保 HTTPS
- 写于 X-Content-Type-Options 以处理嗅探攻击
- 写入 Cache Control 头来保护经过身份验证的资源
- 写入 X-Frame-Options 以处理点击劫持攻击
底层原理
官方文档:https://docs.spring.io/spring-security/reference/servlet/architecture.html
Spring Security之所以默认帮助我们做了那么多事情,它的底层原理是传统的 Servlet 过滤器
- Filter
- DelegatingFilterProxy
- FilterChainProxy
- SecurityFilterChain
- Multiple SecurityFilterChain
自定义配置
官方文档:Java自定义配置
基于内存的用户认证
1、创建自定义配置
**UserDetailsService **用来管理用户信息,**InMemoryUserDetailsManager **是 UserDetailsService 的一个实现,用来管理基于内存的用户信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Configuration @EnableWebSecurity public class WebSecurityConfig {
@Bean public UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser( User .withDefaultPasswordEncoder() .username("ren") .password("password") .roles("USER") .build() ); return manager; } }
|
2、基于内存的用户认证流程
- 程序启动时:
- 创建
InMemoryUserDetailsManager
对象
- 创建
User
对象,封装用户名密码
- 使用
InMemoryUserDetailsManager
将 User
存入内存
- 校验用户时:
- SpringSecurity自动使用
InMemoryUserDetailsManager
的 loadUserByUsername
方法从内存中获取 User
对象
- 在
UsernamePasswordAuthenticationFilter
过滤器中的 attemptAuthentication
方法中将用户输入的用户名密码和从内存中获取到的用户信息进行比较,进行用户认证
基于数据库的数据源
1、SQL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| CREATE DATABASE `security-demo`; USE `security-demo`;
CREATE TABLE `user`( `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, `username` VARCHAR(50) DEFAULT NULL , `password` VARCHAR(500) DEFAULT NULL, `enabled` BOOLEAN NOT NULL );
CREATE UNIQUE INDEX `user_username_uindex` ON `user`(`username`);
INSERT INTO `user` (`username`, `password`, `enabled`) VALUES ('admin', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE), ('Helen', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE), ('Tom', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE);
|
2、引入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency>
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.4.1</version> <exclusions> <exclusion> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> </exclusion> </exclusions> </dependency>
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>3.0.3</version> </dependency>
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
|
3、配置数据源
1 2 3 4 5 6 7
| spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/security-demo spring.datasource.username=root spring.datasource.password=123456
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
|
4、实体类
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Data public class User {
@TableId(value = "id", type = IdType.AUTO) private Integer id;
private String username;
private String password;
private Boolean enabled;
}
|
5、Mapper
1 2 3
| @Mapper public interface UserMapper extends BaseMapper<User> { }
|
1 2 3 4 5
| <?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.atguigu.securitydemo.mapper.UserMapper">
</mapper>
|
6、Service
1 2
| public interface UserService extends IService<User> { }
|
1 2 3
| @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { }
|
7、Controller
1 2 3 4 5 6 7 8 9 10 11 12
| @RestController @RequestMapping("/user") public class UserController {
@Resource public UserService userService;
@GetMapping("/list") public List<User> getList(){ return userService.list(); } }
|
基于数据库的用户认证
1、基于数据库的用户认证流程
- 程序启动时:
- 创建
DBUserDetailsManager
类,实现接口 UserDetailsManager
, UserDetailsPasswordService
- 在应用程序中初始化这个类的对象
- 校验用户时:
- SpringSecurity 自动使用
DBUserDetailsManager
的 loadUserByUsername
方法从数据库中获取 User
对象
- 在
UsernamePasswordAuthenticationFilter
过滤器中的 attemptAuthentication
方法中将用户输入的用户名密码和从数据库中获取到的用户信息进行比较,进行用户认证
2、定义 DBUserDetailsManager
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService { @Resource private UserMapper userMapper;
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("username", username); User user = userMapper.selectOne(queryWrapper); if (user == null) { throw new UsernameNotFoundException(username); } else { Collection<GrantedAuthority> authorities = new ArrayList<>(); return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), user.getEnabled(), true, true, true, authorities); } }
@Override public UserDetails updatePassword(UserDetails user, String newPassword) { return null; }
@Override public void createUser(UserDetails user) {
}
@Override public void updateUser(UserDetails user) {
}
@Override public void deleteUser(String username) {
}
@Override public void changePassword(String oldPassword, String newPassword) {
}
@Override public boolean userExists(String username) { return false; } }
|
3、初始化UserDetailsService
1 2 3 4 5
| @Bean public UserDetailsService userDetailsService() { DBUserDetailsManager manager = new DBUserDetailsManager(); return manager; }
|
SpringSecurity的默认配置
1 2 3 4 5 6 7 8 9 10 11 12
| @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeRequests(authorize -> authorize.anyRequest().authenticated()) .formLogin(withDefaults()) .httpBasic(withDefaults());
return http.build(); }
|
添加用户功能
1、Controller
1 2 3 4
| @PostMapping("/add") public void add(@RequestBody User user){ userService.saveUserDetails(user); }
|
2、Service
1
| void saveUserDetails(User user);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Resource private DBUserDetailsManager dbUserDetailsManager;
@Override public void saveUserDetails(User user) {
UserDetails userDetails = org.springframework.security.core.userdetails.User .withDefaultPasswordEncoder() .username(user.getUsername()) .password(user.getPassword()) .build(); dbUserDetailsManager.createUser(userDetails);
}
|
3、修改配置
1 2 3 4 5 6 7 8 9
| @Override public void createUser(UserDetails userDetails) {
User user = new User(); user.setUsername(userDetails.getUsername()); user.setPassword(userDetails.getPassword()); user.setEnabled(true); userMapper.insert(user); }
|
4、使用 Swagger 测试
1 2 3 4 5 6
| <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <version>4.1.0</version> </dependency>
|
5、关闭 CSRF 攻击防御
在 filterChain
方法中添加如下代码,关闭 CSRF 攻击防御
1 2 3 4
| http.csrf((csrf) -> { csrf.disable(); });
|