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 ,会跳转到
需要登录,默认用户名:user,密码可以查看控制台日志获取
登录之后跳转到:
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(全类名),之后便会去访问这个类,并加载对应的自动配置。
我们来分析一下这个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();
}
}
本人知识浅薄,如果有哪里解析有误,欢迎指教