SpringSecurity5(2-自定义用户信息)
|总字数:2.4k|阅读时长:10分钟|浏览量:|
配置文件自定义用户名和密码
1 2 3 4 5
| spring: security: user: name: root password: root
|
SecurityProperties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| @ConfigurationProperties(prefix = "spring.security") public class SecurityProperties {
public static final int BASIC_AUTH_ORDER = Ordered.LOWEST_PRECEDENCE - 5;
public static final int IGNORED_ORDER = Ordered.HIGHEST_PRECEDENCE;
public static final int DEFAULT_FILTER_ORDER = OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER - 100;
private final Filter filter = new Filter();
private User user = new User();
public User getUser() { return this.user; }
public Filter getFilter() { return this.filter; }
public static class Filter {
private int order = DEFAULT_FILTER_ORDER;
private Set<DispatcherType> dispatcherTypes = new HashSet<>( Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));
public int getOrder() { return this.order; }
public void setOrder(int order) { this.order = order; }
public Set<DispatcherType> getDispatcherTypes() { return this.dispatcherTypes; }
public void setDispatcherTypes(Set<DispatcherType> dispatcherTypes) { this.dispatcherTypes = dispatcherTypes; } }
public static class User {
private String name = "user";
private String password = UUID.randomUUID().toString();
private List<String> roles = new ArrayList<>();
private boolean passwordGenerated = true;
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
public String getPassword() { return this.password; }
public void setPassword(String password) { if (!StringUtils.hasLength(password)) { return; } this.passwordGenerated = false; this.password = password; }
public List<String> getRoles() { return this.roles; }
public void setRoles(List<String> roles) { this.roles = new ArrayList<>(roles); }
public boolean isPasswordGenerated() { return this.passwordGenerated; } } }
|
SecuityProperties 会获取配置文件中的信息,UserDetailsServiceAutoConfiguration 在自动装配时获取 SecuityProperties 的属性信息
UserDetailsServiceAutoConfiguration
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| @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; } }
|
当容器中没有 AuthenticationManager、AuthenticationProvider、UserDetailsService 对应的实例类,且没有 org.springframework.security.oauth2.client.registration.ClientRegistrationRepository 时,会实例化 InMemoryUserDetailsManager 从而获取 SecurityProperties 的配置信息,加载用户信息在内存中
基于内存存储认证信息
- 在 Spring Security 5.0 版本前,加密的 PasswordEncoder 接口默认实现类为 NoOpPasswordEncoder ,这个是可以不用加密的,直接使用明文密码存储。当前已经标注过时了。
- 在 Spring Security 5.0 版本后 ,默认实现类改为了 DelegatingPasswordEncoder,这个实现类要求我们必须对加密后存储,如果不加密处理则会报错。
基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| @Configuration @Slf4j public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
@Override protected void configure(HttpSecurity http) throws Exception {
http.httpBasic() .and() .authorizeRequests() .anyRequest() .authenticated(); }
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { String password = passwordEncoder().encode("1234"); log.info("加密之后存储的密码:" + password); auth.inMemoryAuthentication() .withUser("admin") .password(password) .authorities("ADMIN"); }
@Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg"); } }
|
加密处理分析




UserDetailsService 自定义登录请求
在实际开发中,Spring Security 应该动态的从数据库中获取信息进行自定义身份认证,采用数据库方式进行身份认证一般需要实现两个核心接口 UserDetailsService 和 UserDetails
UserDetailService 接口
该接口只有一个方法 loadUserByUsername(),用于定义从数据库中获取指定用户信息的逻辑。如果未获取到用户信息,则需要手动抛出 UsernameNotFoundException 异常;如果获取到用户信息,则将该用户信息封装到 UserDetails 接口的实现类中并返回
1 2 3 4
| public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }
|
UserDetails 接口
UserDetails 接口定义了用于描述用户信息的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled(); }
|
用户登录逻辑处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Slf4j @Component public class UserDetailServiceImpl implements UserDetailsService {
@Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { log.info("登录用户名:{}",s); return new User(s,"123456", AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); } }
|
在使用了 Spring Security5.x 版本,需要手动提供一个 PasswordEncoder 实现类,进行密码校验,PasswordEncoder 是 SpringSecurity 的密码解析器,用户密码校验、加密,自定义登录逻辑时要求必须给容器注入 PasswordEncoder 的 bean 对象
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Component public class PasswordEncoderImpl implements PasswordEncoder {
@Override public String encode(CharSequence charSequence) { return charSequence.toString(); }
@Override public boolean matches(CharSequence charSequence, String s) { return s.equals(charSequence.toString()); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| @Configuration @Slf4j public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource private PasswordEncoder passwordEncoder; @Resource private UserDetailsService userDetailsService;
@Override protected void configure(HttpSecurity http) throws Exception {
http.httpBasic() .and() .authorizeRequests() .anyRequest() .authenticated(); }
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); }
@Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg"); } }
|
注意
上面自定义的密码解析器密码加密后与原来的一致,如果使用其他的密码解析器密码加密后与原来的不一致时,采用以下方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
@Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder()); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Slf4j @Component public class UserDetailServiceImpl implements UserDetailsService {
@Resource private PasswordEncoder passwordEncoder;
@Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { log.info("登录用户名:{}",s); return new User(s, passwordEncoder.encode("123456"), AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); } }
|
自定义返回 UserDetails 信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| public class AccountUser implements UserDetails {
private Long userId;
private static final long serialVersionUID = 540L; private static final Log logger = LogFactory.getLog(User.class); private String password; private final String username; private final Collection<? extends GrantedAuthority> authorities; private final boolean accountNonExpired; private final boolean accountNonLocked; private final boolean credentialsNonExpired; private final boolean enabled;
public AccountUser(Long userId, String username, String password, Collection<? extends GrantedAuthority> authorities) { this(userId, username, password, true, true, true, true, authorities); }
public AccountUser(Long userId, String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) { Assert.isTrue(username != null && !"".equals(username) && password != null, "Cannot pass null or empty values to constructor"); this.userId = userId; this.username = username; this.password = password; this.enabled = enabled; this.accountNonExpired = accountNonExpired; this.credentialsNonExpired = credentialsNonExpired; this.accountNonLocked = accountNonLocked; this.authorities = authorities; }
@Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; }
@Override public String getPassword() { return this.password; }
@Override public String getUsername() { return this.username; }
@Override public boolean isAccountNonExpired() { return this.accountNonExpired; }
@Override public boolean isAccountNonLocked() { return this.accountNonLocked; }
@Override public boolean isCredentialsNonExpired() { return this.credentialsNonExpired; }
@Override public boolean isEnabled() { return this.enabled; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Component public class UserDetailServiceImpl implements UserDetailsService {
@Resource private PasswordEncoder passwordEncoder;
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if ("admin".equals(username)) { return new AccountUser(12L, "admin", passwordEncoder.encode("123"), AuthorityUtils.NO_AUTHORITIES); } return null; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource private UserDetailServiceImpl userDetailService;
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .and() .authorizeRequests() .anyRequest() .authenticated(); }
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailService); }
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
|