07 - spring security基于数据库的账号密码
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
<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
server:port: 8080servlet:context-path: /springsecurity03spring:security:user:name: adminpassword: 123456datasource:url: jdbc:mysql://127.0.0.1:3306/important?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8username: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driverjackson:time-zone: GMT+8mybatis-plus:mapper-locations: classpath:mybatis/mapper/*.xmlconfiguration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
-
添加实体类
SpringSecurityUser.class
package 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
package 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
包扫描package 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
@Configuration // @EnableWebSecurity @EnableMethodSecurity //开启基于方法的授权 public class WebSecurityConfig {@Beanpublic 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 {@Autowiredprivate SpringSecurityUserMapper springSecurityUserMapper;@Overridepublic void createUser(UserDetails user) {}@Overridepublic void updateUser(UserDetails user) {}@Overridepublic void deleteUser(String username) {}@Overridepublic void changePassword(String oldPassword, String newPassword) {}@Overridepublic boolean userExists(String username) {return false;}@Overridepublic 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
package 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
package 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 {@Autowiredprivate SpringSecurityUserMapper springSecurityUserMapper;@Overridepublic List<SpringSecurityUser> list() {return springSecurityUserMapper.selectList(null);}@Overridepublic int add(SpringSecurityUser springSecurityUser) {if (springSecurityUser == null) {return 0;}// org.springframework.security.crypto.factory.PasswordEncoderFactoriesString 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
package 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 {@Autowiredprivate 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
类package 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; // }@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {// 开启授权保护http.authorizeRequests(new Customizer<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry>() {@Overridepublic 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>>() {@Overridepublic 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()
方法,这个方法并不会对原始密码加密 -
基于以上,可以将加密后的密码保存在数据库里,这样也能避免数据库中存储用户的原始密码