public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
这个接口,当我们通过配置类@Bean注入时:
- encode:此方法中可以用于对密码进行自定义的加密
- matches:此方法对密码进行解密后比对,返回结果
- upgradeEncoding:此方法可以有多种操作方式,我本人用此方放来判断需要解密的密码长度是否达标后再进行解密,当然各位可以自己定义一些规则在其中
加密后密码的格式:
{id}encodedPassword: id就是制定加密的方式,encodedPassword就是加密后的密码,所以整个部分有这个id(令牌)和encodedPassword(密码串文)组成
简单来说,就是当用户创建密码时,我们此时拥有一个加密的集合工厂,用户只要告诉这个工厂我们需要什么加密方式,工厂就已对应方式告诉对应的员工来进行加密,最后返还给用户的这种方式(上代码就明白了).
下面是官方给的列子↘
//用户想要的加密方式:bcrypt
String idForEncode = "bcrypt";
//encoders:可以看做是加密的集合工厂
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("sha256", new StandardPasswordEncoder());
//此时这个工厂将这个用户需求:idForEncode拿到,去:encoders加密的集合工厂中找到,进行对应加密
PasswordEncoder passwordEncoder =new DelegatingPasswordEncoder(idForEncode, encoders);
从上面代码中可以看出,只需要建立对应加密集合库,用户输入对应的加密指定即可.那么这样做的优势如下了:
- 灵活的加密方式:不同的用户存储到数据中,有着不同的加密方式
- 提高了安全性
- 方便日后数据加密的变更迁移
DelegatingPasswordEncoder类:
常用方法
//这是他的一个构造方法:1:传入对应的编码code(令牌) 2:编码加密的集合(如上面代码中的map)
public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder) {
......
}
//这是他的另一个构造器:1:传入对应的编码code(令牌) 2:编码加密的集合(如上面代码中的map) 3:加密后令牌的前缀 4.加密后令牌的后缀
//列出:{id}encodedPassword
public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder,String idPrefix, String idSuffix){
......
}
//匹配默认的编码器
public void setDefaultPasswordEncoderForMatches(PasswordEncoder defaultPasswordEncoderForMatches) {
if (defaultPasswordEncoderForMatches == null) {
throw new IllegalArgumentException("defaultPasswordEncoderForMatches cannot be null");
}
this.defaultPasswordEncoderForMatches = defaultPasswordEncoderForMatches;
}
setDefaultPasswordEncoderForMatches: 我们加密后的密码是由令牌加密文的格式,就像’{id}adsdsdwwd231’这种组合方式,单么有令牌时,就不知道如何匹配密文,所以用此方法设置当没有令牌时,用那种密码编码器,所以此方法可用于数据库中老密码的迁移更新
其他方法就是实现PasswordEncoder这个接口的方法(不在详解了)
测试实例
@Test
void testDelegatingPasswordEncoder() {
//设置密码令牌idForEncode:为pbkdf2@SpringSecurity_v5_8的加密方式
String idForEncode = "pbkdf2@SpringSecurity_v5_8";
//密码编码库
Map encoders = new HashMap<>();
encoders.put("bcrypt", new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("sha256", new StandardPasswordEncoder());
//创建实例将密码令牌和密码编码库放入其中
DelegatingPasswordEncoder delegatingPasswordEncoder = new DelegatingPasswordEncoder(idForEncode, encoders);
//加密KFC
String encode = delegatingPasswordEncoder.encode("KFC");
//输出encode = {pbkdf2@SpringSecurity_v5_8}8565ed891d8007100599707d04db6efe2bcb69ec802bc47a1511a50cc2976dcde227e6bcb10284cdf4ac31a3049d8cd4
System.out.println("encode = " + encode);
/**
* 创建第二个类似实例,但是放入的令牌是test而不是pbkdf2@SpringSecurity_v5_8,
* 然后再匹配KFC与去掉令牌的密文,
* 因为没有令牌的密文匹配会报错如:IllegalArgumentException,
* 所以通过setDefaultPasswordEncoderForMatches方法设置,当没有令牌时匹配规则为:
* Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()
* 所以结果为:matches = true
*/
Map encodeMap = new HashMap<>();
encodeMap.put("test", new BCryptPasswordEncoder());
DelegatingPasswordEncoder passwordEncoder = new DelegatingPasswordEncoder("test", encodeMap);
passwordEncoder.setDefaultPasswordEncoderForMatches(Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
String oldPassword = "8565ed891d8007100599707d04db6efe2bcb69ec802bc47a1511a50cc2976dcde227e6bcb10284cdf4ac31a3049d8cd4";
boolean matches = passwordEncoder.matches("KFC", oldPassword);
System.out.println("matches = " + matches);
}
直接上代码,谁跟你藏着掖着!(咱就是简单举个例子)
package com.xu.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
/**
* @author xu
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig{
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//配置web授权访问,"/login","/register","/upLogin"这些统统无需权限
http.authorizeHttpRequests().requestMatchers("/login","/register","/upLogin").permitAll()
//成功的链接,也就是后续访问的链接放在这个里,我这里是成功页需要USER权限才能访问
.requestMatchers("/success").hasRole("USER").and()
/**
* 自定义登陆页面是loginSelf.html页(这里看你模板引擎怎么配),表单提交的处理链接是"/upLogin"
* 就是这样,这个"/upLogin"不需要自己处理,就自己决定
* usernameParameter和passwordParameter,就是表单提交所带的参数,
* 这里我是"username"和"password";
*/
.formLogin().loginPage("/loginSelf").loginProcessingUrl("/upLogin")
.usernameParameter("username").passwordParameter("password");
return http.build();
}
}
这个是最新的6.0版本(编写日期:20230306),不再使用继承WebSecurityConfigurerAdapter来配置,而是使用SecurityFilterChain来进行注入,记得的一点就是必须加上@EnableWebSecurity这个注解,不然你小子找不到HttpSecurity!!!
当然里面的方法还有很多,自己点进去看看吧,例如hasRole()还有hasAnyRole()可以传入多个参数.
注意:配置了loginPage不用loginProcessingUrl,默认的提交链接就是loginPage的参数,如果值配置loginProcessingUrl那么登陆页面就是默认滴!!!
上面虽然配置了web的页面访问,那么用户登陆如何处理?怎么判断有没有权限?我又怎么给他权限,怎么存进去?
(炒直接代码)
UserServiceImpl类
package com.xu.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xu.pojo.User;
import com.xu.service.UserService;
import com.xu.mapper.UserMapper;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
/**
* @author xu
* @description 针对表【user】的数据库操作Service实现
* @createDate 2023-03-02 11:30:20
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService, UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
/**
* 从数据库查:User是我自己写的类,getOne()是mybatis_plus的中的方法
* 就通过用户名查出一个用户
*/
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",username);
User user = getOne(queryWrapper);
if (user == null) {
throw new UsernameNotFoundException("没有这个用户啊!你小子!");
}
/**
* 创建一个权限集合,随你放多少
* 最后返回一个org.springframework.security.core.userdetails包下的User
* 授权就给完了
* 我这里授权的是USER;
*/
ArrayList<SimpleGrantedAuthority> arrayList = new ArrayList<>();
arrayList.add(new SimpleGrantedAuthority("ROLE_USER"));
return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPassword(),arrayList);
}
}
配置类:
package com.xu.config;
import com.xu.service.impl.UserServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.SecurityFilterChain;
/**
* @author xu
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig{
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//配置web授权访问,"/login","/register","/upLogin"这些统统无需权限
http.authorizeHttpRequests().requestMatchers("/login","/register","/upLogin").permitAll()
//成功的链接,也就是后续访问的链接放在这个里,我这里是成功页需要USER权限才能访问
.requestMatchers("/success").hasRole("USER").and()
/**
* 自定义登陆页面是loginSelf.html页(这里看你模板引擎怎么配),表单提交的处理链接是"/upLogin"
* 就是这样,这个"/upLogin"不需要自己处理,就自己决定
* usernameParameter和passwordParameter,就是表单提交所带的参数,
* 这里我是"username"和"password";
*/
.formLogin().loginPage("/loginSelf").loginProcessingUrl("/upLogin")
.usernameParameter("username").passwordParameter("password");
return http.build();
}
//注入自定义授权
@Bean
UserDetailsService userDetailsService(){
return new UserServiceImpl();
}
}
看完还是懵逼,我来解释一下:
就是这么个理,有些人说你小子,咋不匹配密码啊?只查用户名?
这个是根据你输入的用户名去查出用户信息,然后创建了这个用户的信息和权限,数据库密码也在其中,然后你输入的密码呢,会再次与数据库的密码进行比对,这里就不用你操心了(关于比对规则看第一小节,也是配置好注入@Bean就行)。
6.0.0的版本有很多区别,想花哨的玩还是得去看官方文档,我就大概摸了个入门,各位同志还须自己努力,当然适合不分离,若前后端分离的话,用不上,有更好的处理方式(哭死,自己的是前后端分离),实在看不懂去看源码,备注也有实列,推荐看HttpSecurity这玩意里面,还有就是你不会自定义配置,你就看他接口的实现类是怎么搞的,照猫画虎,不懂的类直接百度,或者看官方文档,直接给他拿捏