浅谈 SpringSecurity使用方式——自动配置原理(一)

1、用户身份认证

快速开始

基于Spring Boot实现

引入依赖

<!-- 实现对 Spring MVC 的自动化配置 --> 
<dependency>
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-web</artifactId> 
</dependency> 
<!-- 实现对 Spring Security 的自动化配置 -->
<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-security</artifactId> 
</dependency>

编写Controller

@RestController 
@RequestMapping("/admin") 
public class AdminController { 
    @GetMapping("/demo") 
    public String demo() { 
        return "spring security demo"; 
    } 
} 

引入Spring Security之后 ,访问 API 接口时,需要首先进行登录,才能进行访问。

测试 http://localhost:8080/admin/demo ,会跳转到

浅谈 SpringSecurity使用方式——自动配置原理(一)

需要登录,默认用户名:user,密码可以查看控制台日志获取

浅谈 SpringSecurity使用方式——自动配置原理(一)

登录之后跳转到:

浅谈 SpringSecurity使用方式——自动配置原理(一)

2.2、验证登录方式

2.2.1、使用配置文件定义用户名密码

设置用户名密码

基于application.yaml

可以在application.yaml中自定义用户名密码:

spring: # Spring Security 配置项,对应 SecurityProperties 配置类 
  security: # 配置默认的 InMemoryUserDetailsManager 的用户账号与密码。 
    user: 
      name: fox  # 账号 
      password: 123456   # 密码 
      roles: ADMIN  # 拥有角色

为什么可以这样用?

在导入spring-boot-starter-security之后,EnableAutoConfiguration会自动去导入组件,并通过autoConfigure下的META-INF/spring.factories会引入UserDetialServiceAutoConfiguration(全类名),之后便会去访问这个类,并加载对应的自动配置。

浅谈 SpringSecurity使用方式——自动配置原理(一)

我们来分析一下这个UserDeatilServiceAutoConfiguration前面的注解,比较重要的是**@ConditionalOnMissingBean**

@ConditionalOnMissingBean(
  value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class },
  type = { "org.springframework.security.oauth2.jwt.JwtDecoder",
    "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector" })

如果我们想使用数据库或者通过编码的方式去验证登录信息,就需要去重写UserDeatilService.class , 一旦我们IOC容器中存在了UserDeatilService.class则这个UserDeatilServiceAutoConfiguration类下的方法则全部失效,即在application.properties/yml下的spring.security设置的前缀无效

通过源码可以知道自动配置类下创建了两个方法

inMemoryUserDetailsManager (SecurityProperties properties,ObjectProvider passwordEncoder)

  • SecurityProperties properties

    • @ConfigurationProperties(prefix = "spring.security")   
      public class SecurityProperties {
      

      @ConfigurationProperties(prefix = “spring.security”) 注意这里的注解暂时还不会生效,因为SecurityProperties还未加入到IOC容器中;如果想加到IOC容器中则应该添加Component,但是源码并没有这么做,这里我们先埋下一个伏笔,在2.2.4的章节中我们会讲到

      可以看到,SecurityProperties会去绑定我们写在application.properties/yml 对应的前缀,也就是说明我们自定义的user(name,password,roles)都会被自动加载并创建InMemoryUserDetailsManager并保存在内存中

      如果我们在application.properties/yml 没有定义值,在默认创建用户名“user”,密码通过UUID生成,如果有自定义的值则会覆盖

  • ObjectProvider passwordEncoder

    • 这是对应你自己设置的加密方式,我们后面会讲到

总之inMemoryUserDetailsManager 就是帮你在不想用数据库、编码验证登录时,可以在application.properties/yml中定义登录信息

getOrDeducePassword主要做密码加密的工作

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(
  value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class },
  type = { "org.springframework.security.oauth2.jwt.JwtDecoder",
    "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector" })
public class UserDetailsServiceAutoConfiguration {

 private static final String NOOP_PASSWORD_PREFIX = "{noop}";

 private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\\{.+}.*$");

 private static final Log logger = LogFactory.getLog(UserDetailsServiceAutoConfiguration.class);

 @Bean
 @ConditionalOnMissingBean(
   type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")
 @Lazy
 public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
   ObjectProvider<PasswordEncoder> passwordEncoder) {
  SecurityProperties.User user = properties.getUser();
  List<String> roles = user.getRoles();
  return new InMemoryUserDetailsManager(
    User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
      .roles(StringUtils.toStringArray(roles)).build());
 }

 private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {
  String password = user.getPassword();
  if (user.isPasswordGenerated()) {
   logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
  }
  if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
   return password;
  }
  return NOOP_PASSWORD_PREFIX + password;
 }

}
2.2.2、使用编码的方式进行验证登录

上一节我们提到过在UserDetialsServiceAutoConfiguration类中,有这么一个注解@ConitionalOnMissingBean(value={UserDetailsService.class });如果在当前IOC容器中没有UserDeatilsService.class类,在自动配置类生效,就会去扫描application.properties/yml查看是否有对应的user跟userPass,如果没有则默认用户名为user,密码用UUID随机生成,并且会通过日志输出到控制台;

所以我们想通过编码的方式,去进行验证登录,就需要去实现UserDeatilService,让原来的自动配置类失效

编码的方式自定义用户名密码进行验证

@Service 
public class UserDetailsServiceImpl implements UserDetailsService { 
    @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 
        String hashpw = BCrypt.hashpw("123456", BCrypt.gensalt()); 
        UserDetails userDetails = User.withUsername("fox") .password("{noop}123456").authorities("admin").build(); 			return userDetails; 
    }
}

编码的方式实现查询数据库进行验证

注意:我们这里是用来lambda表达式的写法,进入UserDeatilsService可以知道这是一个函数式接口(一个接口只有一个抽象方法)

public interface UserDetailsService {
 UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
@Bean
@Override
public UserDetailsService userDetailsService() {
    return username -> {
        Admin admin = adminService.getAdminByUserName(username);
        if (admin != null) {
            admin.setRoles(adminService.getRoles(admin.getId()));
            return admin;
        }
        // 这是由springsecurity提供的异常处理器
        throw new UsernameNotFoundException("用户名或密码不正确!");
    };
}

2.3、Spring security加密方式

spring security官方推荐的加密方式BCrypt

1、方式一创建一个带有@Configuration的类,在使用@Bean将密码编码器加入到IOC容器

@Configuration 
public class WebSecurityConfig {
    @Bean public PasswordEncoder passwordEncoder(){ 
        return NoOpPasswordEncoder.getInstance(); 
    }
}

2、方式二在继承WebSecurityConfigurerAdapter的类中定义

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // 让springsecurity走我们自定义的UserDetailsService,定义的密码解析
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return username -> {
            Admin admin = adminService.getAdminByUserName(username);
            if (admin != null) {
                admin.setRoles(adminService.getRoles(admin.getId()));
                return admin;
            }
            // 这是由springsecurity提供的异常处理器
            throw new UsernameNotFoundException("用户名或密码不正确!");
        };
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

本人知识浅薄,如果有哪里解析有误,欢迎指教

上一篇:springSecurity


下一篇:springsecurity 记住我