SpringSecurity5(2-自定义用户信息)

news/2025/3/12 16:22:32/文章来源:https://www.cnblogs.com/penggx/p/18764939

配置文件自定义用户名和密码

spring:security:user:name: root    #通过配置文件,设置静态用户名password: root    #配置文件,设置静态登录密码

SecurityProperties

@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

@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")@Lazypublic 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 的配置信息,加载用户信息在内存中

基于内存存储认证信息

  1. 在 Spring Security 5.0 版本前,加密的 PasswordEncoder 接口默认实现类为 NoOpPasswordEncoder ,这个是可以不用加密的,直接使用明文密码存储。当前已经标注过时了。
  2. 在 Spring Security 5.0 版本后 ,默认实现类改为了 DelegatingPasswordEncoder,这个实现类要求我们必须对加密后存储,如果不加密处理则会报错。

基本使用

@Configuration
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {/*** 密码编码器,密码不能明文存储*/@Beanpublic PasswordEncoder passwordEncoder() {// 设置默认的加密方式,使用 BCryptPasswordEncoder 密码编码器,// 该编码器会将随机产生的 salt 混入最终生成的密文中return new BCryptPasswordEncoder();}/*** 定制基于 HTTP 请求的用户访问控制*/@Overrideprotected void configure(HttpSecurity http) throws Exception {/*** fromLogin():表单认证* httpBasic():弹出框认证* authorizeRequests():身份认证请求* anyRequest():所有请求* authenticated():身份认证*/http.httpBasic().and().authorizeRequests()// 其它任何请求访问都需要先通过认证.anyRequest().authenticated();}/*** 认证管理器:* 1、认证信息提供方式(用户名、密码、当前用户的资源权限)* 2、可采用内存存储方式,也可能采用数据库方式等*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 用户信息存储在内存中String password = passwordEncoder().encode("1234");log.info("加密之后存储的密码:" + password);auth.inMemoryAuthentication().withUser("admin").password(password).authorities("ADMIN");}/*** 定制一些全局性的安全配置,例如:不拦截静态资源的访问*/@Overridepublic void configure(WebSecurity web) throws Exception {// 静态资源的访问不需要拦截,直接放行web.ignoring().antMatchers("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");}
}

加密处理分析

UserDetailsService 自定义登录请求

在实际开发中,Spring Security 应该动态的从数据库中获取信息进行自定义身份认证,采用数据库方式进行身份认证一般需要实现两个核心接口 UserDetailsService 和 UserDetails

UserDetailService 接口

该接口只有一个方法 loadUserByUsername(),用于定义从数据库中获取指定用户信息的逻辑。如果未获取到用户信息,则需要手动抛出 UsernameNotFoundException 异常;如果获取到用户信息,则将该用户信息封装到 UserDetails 接口的实现类中并返回

public interface UserDetailsService {// 输入参数 username 是前端传入的用户名UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

UserDetails 接口

UserDetails 接口定义了用于描述用户信息的方法

public interface UserDetails extends Serializable {// 返回用户权限集合Collection<? extends GrantedAuthority> getAuthorities();// 返回用户的密码String getPassword();// 返回用户的用户名String getUsername();// 账户是否未过期(true 未过期, false 过期)boolean isAccountNonExpired();// 账户是否未锁定(true 未锁定, false 锁定)// 用户账户可能会被封锁,达到一定要求可恢复boolean isAccountNonLocked();// 密码是否未过期(true 未过期, false 过期)// 一些安全级别高的系统,可能要求 30 天更换一次密码boolean isCredentialsNonExpired();// 账户是否可用(true 可用, false 不可用)// 系统一般不会真正的删除用户信息,而是假删除,通过一个状态码标志用户是否被删除boolean isEnabled();
}

用户登录逻辑处理

@Slf4j
@Component
public class UserDetailServiceImpl implements UserDetailsService {/*** Spring Security 接收 login 请求调用 UserDetailService 这个接口中的 loadUserByUsername* loadUserByUsername 根据传进来的用户名进行校验工作,* 最后将查询到的用户信息封装到 UserDetails 这个接口的实现类中*/@Overridepublic 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 对象

@Component
public class PasswordEncoderImpl implements PasswordEncoder {@Overridepublic String encode(CharSequence charSequence) {return charSequence.toString();}@Overridepublic boolean matches(CharSequence charSequence, String s) {return s.equals(charSequence.toString());}
}
@Configuration
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Resourceprivate PasswordEncoder passwordEncoder;@Resourceprivate UserDetailsService userDetailsService;/*** 定制基于 HTTP 请求的用户访问控制*/@Overrideprotected void configure(HttpSecurity http) throws Exception {/*** fromLogin():表单认证* httpBasic():弹出框认证* authorizeRequests():身份认证请求* anyRequest():所有请求* authenticated():身份认证*/http.httpBasic().and().authorizeRequests()// 其它任何请求访问都需要先通过认证.anyRequest().authenticated();}/*** 认证管理器:* 1、认证信息提供方式(用户名、密码、当前用户的资源权限)* 2、可采用内存存储方式,也可能采用数据库方式等*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 不再使用内存方式存储用户认证信息,而是动态从数据库中获取auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);}/*** 定制一些全局性的安全配置,例如:不拦截静态资源的访问*/@Overridepublic void configure(WebSecurity web) throws Exception {// 静态资源的访问不需要拦截,直接放行web.ignoring().antMatchers("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");}
}

注意

上面自定义的密码解析器密码加密后与原来的一致,如果使用其他的密码解析器密码加密后与原来的不一致时,采用以下方式:

/*** 密码编码器,密码不能明文存储*/
@Bean
public BCryptPasswordEncoder passwordEncoder() {// 使用 BCryptPasswordEncoder 密码编码器,该编码器会将随机产生的 salt 混入最终生成的密文中return new BCryptPasswordEncoder();
}@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
}
@Slf4j
@Component
public class UserDetailServiceImpl implements UserDetailsService {@Resourceprivate PasswordEncoder passwordEncoder;/*** Spring Security 接收 login 请求调用 UserDetailService 这个接口中的 loadUserByUsername* loadUserByUsername 根据传进来的用户名进行校验工作,* 最后将查询到的用户信息封装到 UserDetails 这个接口的实现类中*/@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {log.info("登录用户名:{}",s);//根据用户名查询用户数据return new User(s, passwordEncoder.encode("123456"), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));}
}

自定义返回 UserDetails 信息

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;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authorities;}@Overridepublic String getPassword() {return this.password;}@Overridepublic String getUsername() {return this.username;}@Overridepublic boolean isAccountNonExpired() {return this.accountNonExpired;}@Overridepublic boolean isAccountNonLocked() {return this.accountNonLocked;}@Overridepublic boolean isCredentialsNonExpired() {return this.credentialsNonExpired;}@Overridepublic boolean isEnabled() {return this.enabled;}
}
@Component
public class UserDetailServiceImpl implements UserDetailsService {@Resourceprivate PasswordEncoder passwordEncoder;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {if ("admin".equals(username)) {return new AccountUser(12L, "admin", passwordEncoder.encode("123"), AuthorityUtils.NO_AUTHORITIES);}return null;}
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Resourceprivate UserDetailServiceImpl userDetailService;@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin().and().authorizeRequests().anyRequest().authenticated();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailService);}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/897745.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

No.60 Vue---事件处理

一、事件处理 1.1 监听事件(添加事件)我们可以使用 v-on 指令 (通常缩写为 @ 符号)来监听 DOM 事件,并在触发事件时执行一些JavaScript。用法为v-on:click="methodName”或使用快捷方式 @click="methodName”.<template><div><button v-on:click=&…

基于TPM的远程认证之一(TCG文档-验证篇)

PCR值验证 在PCR值足够静态或者验证者有证明者的PCR标准值的情况下,可以使用PCR值验证的方式验证平台的可信度。在PCR复合验证中,这种方法要求验证者拥有一个固定的断言PCR复合值列表,这样证明者就只发送报价(带有PCR值的哈希值),而不必发送完整的PCR值或事件日志。或者,…

day:19 html实战2

一、链接标签 1、定义: 从一个网页指向另一个网页的目的地,这个目标可以是一个网页,也可以图片,一个文件,一个网站等 2、链接的类型 a、生成信廉基覆盖源链接 b、新开一个窗口 c、图片链接 d、死链接 3、链接操作代码:链接标签百度(覆盖原有窗口) 京东(另开一个窗口)死…

数据采集仪 传感器采集读数仪 兼容多类型振弦、电压、电流传感器 分组存储与自动导出

数据采集仪 传感器采集读数仪 兼容多类型振弦、电压、电流传感器 分组存储与自动导出VH501TC是一款多功能手持式数据采集仪,专为单弦式振弦传感器设计,同时兼容电压、电流传感器的数据采集。该设备集成了先进的LoRA无线通信技术,可与本公司NLM系列产品无缝对接,实现远程无线…

gitlab+jenkins+harbor+k8s安装操作流程之jenkins gitlab harbor页面配置操作截图

现在文档阶段,实现的操作,开发人员下载或者上传新的代码,提交后jenkins自动构建到jenkinsworkspace目录中,构建过程中,利用build-shell的方式,把workspace中的代码目录制作成镜像,打包,并发送到harbor中,注意:在每个项目目录内,必须有dockerfile文件,才可以发布 1.…

20250227 大作业——HTML6标签设计工作日志

HTML6标签创新设计工作日志 2025.02.27——2025.02.28 高级程序设计课布置了大作业,要求如图 于是去询问 AI ,有哪些方面可以做,决定花一两天时间去了解咨询一下。得到了很多消息,就是现在HTML5所具有的一些功能可以参考这个网站,因为设计者的原因,很多功能可能我们平常不…

效率炸雷!Ethernetip转Profinet网关H+E流量计用EipScan连接预连

本期给大家带来H+E流量计与EipScan模拟软件连通案例。由于很多客户现场任务时间紧任务重的特点,在不确定所购买的稳联技术Profinet转Ethernetip网关(WL-PNS-EIPM)能否与H+E流量计正常通讯的情况下,用EipScan模拟软件先测通。下面就介绍下如何用EipScan模拟软件连通且不通过…

穿越“协议迷雾”:Modbus转Profinet与60LB伺服的传奇相遇

本研究案例深入剖析了稳联技术Modbus转Profinet网关(WL-ABC3010)在60LB系列通用伺服驱动器与PLC互联场景中的具体应用。此次应用所涉及的关键设备涵盖了西门子S7-1200PLC、Modbus转Profinet网关以及60LB系列通用伺服驱动器。借助网关对通信协议的转换功能,使得遵循MODBUS协议…

Python 中 Windows 和 macOS 的路径格式不一致问题

Python 中 Windows 和 macOS 的路径格式不一致问题Python 中 Windows 和 macOS 的路径格式不一致问题 在 Python 中,Windows 和 macOS 的文件路径字符串格式不一致主要体现在路径分隔符上:Windows 使用反斜杠 \(如 C:\Users\file.txt),而 macOS 使用正斜杠 /(如 /Users/f…

Zabbix agent编译安装详细教程

文章出处:乐维社区背景: 公司之前一直是用的预编译好的zabbix agent包在redhat、centos等进行安装部署。最近内部部署了一套新的操作系统(TencentOS Server),用之前的agent包发现并不能正常启动agent,具体报错是关于bash变量的: /bin/bash: 没有那个文件或目录 最开始以…

那智机器人维修30编码器异常报警代码处理

那智机器人维修报警代码的核心功能,在于为技术人员提供机器人在运作流程中遭遇故障或异常时的详尽信息,以便他们能够迅速且准确地锁定问题所在,并采取有效的修复措施。这些报警代码被精心设计为多个类别,每一个类别都精准对应着一种特定的故障或异常情形,使得问题的诊断与…

html的基本理论

一、html介绍 1、html是一个超文本标记语言,也是一种标识性语言。(不是编程语句) 2、标记:记号(绰号) 3、超文本:就是页面内容包含图片、链接、音乐、视频等素材 4、为什么学习html? a、测试页面功能,需要了解页面元素(页面是html语言编写的) b、方便我们进行ui自动…