一、应用场景
SpringSecurity又称安全框架,拥有基于用户的 [认证] 和 [权限] 授予两大功能;
1.用户认证:验证该用户是否为数据库中存储的数据
2.权限授予:对于不同的用户给予不同访问权限,给出对于项目中不同功能/视图的是否可访问。 (视频教程地址:https://www.bilibili.com/video/BV1mm4y1X7Hc?p=1
)
二、代码实现-用户认证
原生的Security框架在项目启动后,发送任意的请求,都会调用用户认证API(即输入username和password),我们可以根据自己的业务需求来修改这部分API,使Security框架可以更个性化的使用,满足我们自己的业务需求。
1.用户认证的基本逻辑👇:
整理了一份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
scss
复制代码
(1)在登陆时输入校验信息(数据库字段,通常为username和password)并确认 (2)将输入信息和数据库的数据进行校验,对比一致,则根据输入的username和password生成一段基于token (3)将此token存入redis数据库:key为username,value经过mybatis查询保存为username对应的数据库记录 (4)将此token响应给前端;之后每一次请求都必须携带这个token来获取用户信息,来判断他是否有权限获得响应数据
2.使用Security加持的代码实现👇:
通常,我们习惯于将原生的用户认证修改为基于数据库字段的(例如username和password)进行登录校验的业务逻辑,这就需要我们进入Secuirty的认证流程中,修改源代码来解决。
(1)UserDetailsService接口的‘loadUserByUsername’方法:
该方法的携带了输入的username作为参数,执行数据库字段校验的逻辑可以在这里定制。
我们可以在工程中编写一个类来实现这个UserDetailsService接口的,并重写loadUserByUsername方法,制定数据库验证的业务逻辑👇
scss
复制代码
@Service public class MyUserDetailsServiceImpl implements UserDetailsService { @Autowired UserMapper userMapper; @Autowired UserService userService; /** * 对用户的输入进行数据库验证 * loadUserByUsername 获取用户输入的username并返回UserDetails * @param userName 来自用户输入的user在浏览器输入的user_name */ @Override public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper(); queryWrapper.eq(User::getUserName,userName); User user = userMapper.selectOne(queryWrapper); System.out.println("user:"); System.out.println(user); if (Objects.isNull(user)){ throw new RuntimeException("用户名或密码错误"); } LoginUserDetails loginUser = new LoginUserDetails(); loginUser.setUser(user); return loginUser; //不可返回空,定义LoginUser类来返回此类型的值 } }
需要注意的是,该方法返回的不是验证数据库的实体类对象,而是“UserDetails”对象,这是一个包裹了实体类验证信息的接口,我们可以通过新建一个继承UserDetails接口的类,把实体类包裹在里面,进行返回。
typescript
复制代码
/** * 定义一个类来返回 MyUserDetailsServiceImpl 的返回值 */ @Data @NoArgsConstructor @AllArgsConstructor public class LoginUserDetails implements UserDetails { private User user; /* 返回用户的password*/ @Override public String getPassword() { return user.getPassword(); } /* 返回用户的user_name*/ @Override public String getUsername() { return user.getUserName(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } /** * 需要返回的权限信息,与用户认证无关 */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } }
至此,loadUserByUsername方法返回用户数据库数据部分结束。
(2)修改密码校验--PasswordEncode
执行完loadUserByUsername后会返回数据库的用户密码,此信息会经过PasswordEncode类,和用户输入的密码来进行校验,但是原生的PasswordEncode比较糟糕(略),所以我们在配置中修改一下PasswordEncode的校验方式,使用 BCrypt。👇
注意,使用此方法进行密码校验的前提是:数据库的密码必须是使用BCrypt生成的加密字符串,而不是明文。👇
scala
复制代码
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { /** 根据流程,loadUserByUsername携带用户数据库信息来到PasswordEncoder,编写此配置 类的Bean,PasswordEncoder返回值修改为BCryptPasswordEncoder,即可解决loadUserByUsername 方法返回的UserDetails对象不能校验明文密码的问题了哦。 */ @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
PasswordEncoder校验两方密码的原理 (用户在前端输入的明文,和数据库的密文,可以在BCryptPasswordEncoder使用matchs方法来校验:是否为一个同字符串的明和密两种模式) 👇
ini
复制代码
@Test public void BCryptPasswordEncoderTest(){ BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String encode1 = passwordEncoder.encode("111111"); System.out.println(encode1); boolean matches1 = passwordEncoder.matches("111111",encode1); System.out.println(matches1); } ↓ 控制台打印 ↓ $2a$10$8I0lzvZqR4c6/hf8PP.bkuiW//N4cFlE9xuoPAyS9lelELw0WuSGG true