引入 spring-security jar 包,不做任何额外配置
启动服务自动生成的随机密码
Using generated security password: 52713582-35cf-4b0c-832a-1342faece243
默认登录页
image.png实际业务,会存在多用户的的情况,spring-security 也提供在内存及数据库管理用户的方法。为了方便,我们可以先尝试内存操作
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.defaultSuccessUrl("/user") //登录成功后跳转地址
.and().httpBasic();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(customPasswordEncoder())
.withUser("cxc").password(customPasswordEncoder().encode("cxcpwd")).roles("USER");
}
@Bean
PasswordEncoder customPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
写一个 reset 请求进行验证
@RestController
public class IndexController {
@GetMapping("/hello")
String hello() {
return "hello world";
}
@GetMapping("/user")
String user() {
return SecurityContextHolder.getContext().getAuthentication().getName();
}
}
登录 & 验证效果
image.png
稍微高级一点的用法
定义一个 authenticationProvider,然后在 configure 将这个 provider 作为参数传入到 auth.authenticationProvider(authenticationProvider());
@Bean
public DaoAuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(customPasswordEncoder());
// 这里直接还是用默认的 InMemoryUserDetailsManager,就不再实现 UserDetailsService 接口
provider.setUserDetailsService(new InMemoryUserDetailsManager());
return provider;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(customPasswordEncoder())
.withUser("chenxiaochi").password(customPasswordEncoder().encode("pwd")).roles("USER");
auth.authenticationProvider(authenticationProvider());
}
项目用到的 pom 文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cxc</groupId>
<artifactId>security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>security</name>
<description>security</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
重点讲解部分
PasswordEncoder,UserDetailsService 的初始化
如果没注入自定义的 provider , InitializeUserDetailsManagerConfigurer 会接管初始化配置,此时 UserDetailsService,PasswordEncoder 将会被注入系统默认的 provider
InitializeUserDetailsManagerConfigurer
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.apply(new InitializeUserDetailsBeanManagerConfigurer.InitializeUserDetailsManagerConfigurer());
}
class InitializeUserDetailsManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
InitializeUserDetailsManagerConfigurer() {
}
public void configure(AuthenticationManagerBuilder auth) throws Exception {
if (!auth.isConfigured()) {
UserDetailsService userDetailsService = (UserDetailsService)this.getBeanOrNull(UserDetailsService.class);
if (userDetailsService != null) {
PasswordEncoder passwordEncoder = (PasswordEncoder)this.getBeanOrNull(PasswordEncoder.class);
UserDetailsPasswordService passwordManager = (UserDetailsPasswordService)this.getBeanOrNull(UserDetailsPasswordService.class);
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
if (passwordEncoder != null) {
provider.setPasswordEncoder(passwordEncoder);
}
if (passwordManager != null) {
provider.setUserDetailsPasswordService(passwordManager);
}
provider.afterPropertiesSet();
auth.authenticationProvider(provider);
}
}
}
private <T> T getBeanOrNull(Class<T> type) {
String[] beanNames = InitializeUserDetailsBeanManagerConfigurer.this.context.getBeanNamesForType(type);
return beanNames.length != 1 ? null : InitializeUserDetailsBeanManagerConfigurer.this.context.getBean(beanNames[0], type);
}
}
AuthenticationManagerBuilder
public boolean isConfigured() {
return !this.authenticationProviders.isEmpty() || this.parentAuthenticationManager != null;
}
所以尽量在注入PasswordEncoder的时候,尽量隐藏密码类型细节,返回类型为接口类型即可。
@Bean
PasswordEncoder customPasswordEncoder() {
return new BCryptPasswordEncoder();
}
总结
这里不涉及太多源代码研究,更多的是演示怎样用,一个简单的例子感受 spring-security。
参考
网上好的例子: https://juejin.cn/post/6844903896687575047
网上好的例子2: https://progressivecoder.com/implementing-spring-boot-security-using-userdetailsservice/
验证流程分析文章 https://juejin.cn/post/6844903806921244679
说点其他的
公司项目因业务原因,没有过多依赖于 spring-security 的用户名及密码校验,主要用了认证成功的登录态设置,权限。是通过过滤器的方式处理的。 所以也会写一篇 spring-security 过滤器的。
网友评论