spring security基于数据库的账号密码
文档
- 00 - spring security框架使用
- 01 - spring security自定义登录页面
- 02 - spring security基于配置文件及内存的账号密码
- 03 - spring security自定义登出页面
- 04 - spring security关闭csrf攻击防御
- 05 - spring security权限控制
- 06 - spring security角色和权限设置
基于数据库的账号密码
调整配置
-
pom.xml
文件添加依赖,这里使用mybatis-plus
xml<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>3.5.12</version> </dependency>
-
调整
applicaiton.yml
yamlserver: port: 8080 servlet: context-path: /springsecurity03 spring: security: user: name: admin password: 123456 datasource: url: jdbc:mysql://127.0.0.1:3306/important?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8 username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver jackson: time-zone: GMT+8 mybatis-plus: mapper-locations: classpath:mybatis/mapper/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
-
添加实体类
SpringSecurityUser.class
javapackage xin.yangshuai.springsecurity03.entity; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; @TableName("`spring_security_user`") public class SpringSecurityUser implements Serializable { /** * 主键 */ private Integer id; /** * 用户名 */ private String username; /** * 密码 */ private String password; private static final long serialVersionUID = 1L; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username == null ? null : username.trim(); } public String getPassword() { return password; } public void setPassword(String password) { this.password = password == null ? null : password.trim(); } }
-
创建
mapper
类SpringSecurityUserMapper.java
javapackage xin.yangshuai.springsecurity03.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import xin.yangshuai.springsecurity03.entity.SpringSecurityUser; public interface SpringSecurityUserMapper extends BaseMapper<SpringSecurityUser> { }
-
启动类添加
mybatis-plus
包扫描javapackage xin.yangshuai.springsecurity03; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan(basePackages = {"xin.yangshuai.springsecurity03.mapper"}) public class Springsecurity03Application { public static void main(String[] args) { SpringApplication.run(Springsecurity03Application.class, args); } }
配置基于数据库的账号密码
-
在基于内存的账号密码时,我们定义了一个
UserDetailsService
类型的Bean
java@Configuration // @EnableWebSecurity @EnableMethodSecurity //开启基于方法的授权 public class WebSecurityConfig { @Bean public UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); // 此时配置文件中的用户名和密码将不可用 manager.createUser(User.withDefaultPasswordEncoder().username("user").password("password").authorities("USER_LIST").build()); return manager; } // ... }
-
spring security
框架在认证的时候就会调用该类的loadUserByUsername
方法 -
使用基于数据库的账号密码时,需要要将上面的
Bean
注释掉 -
自定义一个类
DatabaseUserDetailsManager.java
,实现UserDetailsManager
接口,并接受Spring
管理,同样的,spring security
框架在认证的时候就会调用该类的loadUserByUsername
方法package xin.yangshuai.springsecurity03.config; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.provisioning.UserDetailsManager; import org.springframework.stereotype.Component; import xin.yangshuai.springsecurity03.entity.SpringSecurityUser; import xin.yangshuai.springsecurity03.mapper.SpringSecurityUserMapper; @Component public class DatabaseUserDetailsManager implements UserDetailsManager { @Autowired private SpringSecurityUserMapper springSecurityUserMapper; @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; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SpringSecurityUser springSecurityUser1 = springSecurityUserMapper.selectById("123"); System.out.println(springSecurityUser1); LambdaQueryWrapper<SpringSecurityUser> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(SpringSecurityUser::getUsername, username); SpringSecurityUser springSecurityUser = springSecurityUserMapper.selectOne(queryWrapper); if (springSecurityUser == null) { throw new UsernameNotFoundException(username); } else { UserDetails user = User.builder() .username(springSecurityUser.getUsername()) .password(springSecurityUser.getPassword()) // 这里示例,权限手动添加 .authorities("USER_ADD", "USER_UPDATE", "USER_LIST").build(); return user; } } }
添加用户
-
创建
service
类SpringSecurityUserService.java
javapackage xin.yangshuai.springsecurity03.service; import xin.yangshuai.springsecurity03.entity.SpringSecurityUser; import java.util.List; public interface SpringSecurityUserService { List<SpringSecurityUser> list(); int add(SpringSecurityUser springSecurityUser); }
-
创建
service
实现类SpringSecurityUserServiceImpl.java
javapackage xin.yangshuai.springsecurity03.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.DelegatingPasswordEncoder; import org.springframework.security.crypto.password.MessageDigestPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import xin.yangshuai.springsecurity03.entity.SpringSecurityUser; import xin.yangshuai.springsecurity03.mapper.SpringSecurityUserMapper; import xin.yangshuai.springsecurity03.service.SpringSecurityUserService; import java.util.HashMap; import java.util.List; import java.util.Map; @Service public class SpringSecurityUserServiceImpl implements SpringSecurityUserService { @Autowired private SpringSecurityUserMapper springSecurityUserMapper; @Override public List<SpringSecurityUser> list() { return springSecurityUserMapper.selectList(null); } @Override public int add(SpringSecurityUser springSecurityUser) { if (springSecurityUser == null) { return 0; } // org.springframework.security.crypto.factory.PasswordEncoderFactories String encodingId = "bcrypt"; Map<String, PasswordEncoder> encoders = new HashMap(); encoders.put(encodingId, new BCryptPasswordEncoder()); encoders.put("MD5", new MessageDigestPasswordEncoder("MD5")); // ... encodingId = "MD5"; PasswordEncoder encoder = new DelegatingPasswordEncoder(encodingId, encoders); String password = springSecurityUser.getPassword(); String encode = encoder.encode(password); springSecurityUser.setPassword(encode); return springSecurityUserMapper.insert(springSecurityUser); } }
-
修改
SpringSecurityUserController.java
javapackage xin.yangshuai.springsecurity03.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import xin.yangshuai.common01.entity.BaseResult; import xin.yangshuai.springsecurity03.entity.SpringSecurityUser; import xin.yangshuai.springsecurity03.service.SpringSecurityUserService; import java.util.List; @RestController @RequestMapping("user") public class SpringSecurityUserController { @Autowired private SpringSecurityUserService springSecurityUserService; @GetMapping("list") @PreAuthorize("hasAuthority('USER_LIST')") public BaseResult<List<SpringSecurityUser>> list() { List<SpringSecurityUser> list = springSecurityUserService.list(); BaseResult<List<SpringSecurityUser>> result = new BaseResult<>(); result.setCode("200"); result.setData(list); return result; } @PostMapping("add") public BaseResult<Integer> add(@RequestBody SpringSecurityUser springSecurityUser) { int rows = springSecurityUserService.add(springSecurityUser); BaseResult<Integer> result = new BaseResult<>(); result.setCode("200"); result.setData(rows); return result; } }
-
为避免麻烦,关闭csrf攻击防御,对
user/add
接口设置免认证,编辑WebSecurityConfig.java
类javapackage xin.yangshuai.springsecurity03.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.*; import org.springframework.security.web.SecurityFilterChain; @Configuration // @EnableWebSecurity @EnableMethodSecurity //开启基于方法的授权 public class WebSecurityConfig { // @Bean // public UserDetailsService userDetailsService() { // InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); // // 此时配置文件中的用户名和密码将不可用 // manager.createUser(User.withDefaultPasswordEncoder().username("user").password("password").authorities("USER_LIST").build()); // return manager; // } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 开启授权保护 http.authorizeRequests(new Customizer<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry>() { @Override public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry expressionInterceptUrlRegistry) { // 具有USER_LIST权限的用户可以访问/user/list // expressionInterceptUrlRegistry.requestMatchers("/user/list").hasAuthority("USER_LIST"); expressionInterceptUrlRegistry .requestMatchers("/user/add").permitAll() // 对所有请求开启授权保护 .anyRequest() // 已经认证的请求会被自动授权 .authenticated(); } }); // 自定义登录页面 // 自定义登出页面 // 自定义拒绝访问页面 // 关闭csrf攻击防御 http.csrf(new Customizer<CsrfConfigurer<HttpSecurity>>() { @Override public void customize(CsrfConfigurer<HttpSecurity> httpSecurityCsrfConfigurer) { httpSecurityCsrfConfigurer.disable(); } }); return http.build(); } }
-
这里有个细节,
spring security
进行账号认证的时候,系统内的用户信息,也就是UserDetails
对象的密码需要传入经过某种算法加密后的值,比如:bcrypt
算法,具体参考org.springframework.security.crypto.password.DelegatingPasswordEncoder#matches
-
在
org.springframework.security.crypto.factory.PasswordEncoderFactories
类中,列举了加密算法 -
在使用基于内存的账号密码时,构造
UserDetails
对象时,使用的.withDefaultPasswordEncoder()
方法,会将密码进行加密,使用的算法就是bcrypt
-
在使用基于数据库的账号密码时,构造
UserDetails
对象时,并没有使用.withDefaultPasswordEncoder()
方法,使用的是.builder()
方法,这个方法并不会对原始密码加密 -
基于以上,可以将加密后的密码保存在数据库里,这样也能避免数据库中存储用户的原始密码