Spring Boot整合Spring Security(七)基于内存的用户认证

阅前提示

此文章基于Spring Security 6.0

UserDetailsService

在之前的认证流程篇章中已经介绍到,DaoAuthenticationProvider使用了一个UserDetailsService去接收username,password以及一些其他的属性去做认证。然后Spring Security提供了in-memory内存 and JDBC 数据库 implementations of UserDetailsService。本篇章讲讲基于内存的UserDetails管理

InMemoryUserDetailsManager

public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {

	protected final Log logger = LogFactory.getLog(getClass());

	private final Map<String, MutableUserDetails> users = new HashMap<>();

	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
			.getContextHolderStrategy();

	private AuthenticationManager authenticationManager;

	public InMemoryUserDetailsManager() {
	}

	public InMemoryUserDetailsManager(Collection<UserDetails> users) {
		for (UserDetails user : users) {
			createUser(user);
		}
	}

	public InMemoryUserDetailsManager(UserDetails... users) {
		for (UserDetails user : users) {
			createUser(user);
		}
	}

	public InMemoryUserDetailsManager(Properties users) {
		Enumeration<?> names = users.propertyNames();
		UserAttributeEditor editor = new UserAttributeEditor();
		while (names.hasMoreElements()) {
			String name = (String) names.nextElement();
			editor.setAsText(users.getProperty(name));
			UserAttribute attr = (UserAttribute) editor.getValue();
			Assert.notNull(attr,
					() -> "The entry with username '" + name + "' could not be converted to an UserDetails");
			createUser(createUserDetails(name, attr));
		}
	}

	private User createUserDetails(String name, UserAttribute attr) {
		return new User(name, attr.getPassword(), attr.isEnabled(), true, true, true, attr.getAuthorities());
	}

	@Override
	public void createUser(UserDetails user) {
		Assert.isTrue(!userExists(user.getUsername()), "user should not exist");
		this.users.put(user.getUsername().toLowerCase(), new MutableUser(user));
	}

	@Override
	public void deleteUser(String username) {
		this.users.remove(username.toLowerCase());
	}

	@Override
	public void updateUser(UserDetails user) {
		Assert.isTrue(userExists(user.getUsername()), "user should exist");
		this.users.put(user.getUsername().toLowerCase(), new MutableUser(user));
	}

	@Override
	public boolean userExists(String username) {
		return this.users.containsKey(username.toLowerCase());
	}

	@Override
	public void changePassword(String oldPassword, String newPassword) {
		Authentication currentUser = this.securityContextHolderStrategy.getContext().getAuthentication();
		if (currentUser == null) {
			// This would indicate bad coding somewhere
			throw new AccessDeniedException(
					"Can't change password as no Authentication object found in context " + "for current user.");
		}
		String username = currentUser.getName();
		this.logger.debug(LogMessage.format("Changing password for user '%s'", username));
		// If an authentication manager has been set, re-authenticate the user with the
		// supplied password.
		if (this.authenticationManager != null) {
			this.logger.debug(LogMessage.format("Reauthenticating user '%s' for password change request.", username));
			this.authenticationManager
					.authenticate(UsernamePasswordAuthenticationToken.unauthenticated(username, oldPassword));
		}
		else {
			this.logger.debug("No authentication manager set. Password won't be re-checked.");
		}
		MutableUserDetails user = this.users.get(username);
		Assert.state(user != null, "Current user doesn't exist in database.");
		user.setPassword(newPassword);
	}

	@Override
	public UserDetails updatePassword(UserDetails user, String newPassword) {
		String username = user.getUsername();
		MutableUserDetails mutableUser = this.users.get(username.toLowerCase());
		mutableUser.setPassword(newPassword);
		return mutableUser;
	}

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		UserDetails user = this.users.get(username.toLowerCase());
		if (user == null) {
			throw new UsernameNotFoundException(username);
		}
		return new User(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(),
				user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
	}

	/**
	 * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use
	 * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}.
	 *
	 * @since 5.8
	 */
	public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
		Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
		this.securityContextHolderStrategy = securityContextHolderStrategy;
	}

	public void setAuthenticationManager(AuthenticationManager authenticationManager) {
		this.authenticationManager = authenticationManager;
	}

}

这个类很好理解
在初始化的时候,创建了一个HashMap,所有的用户信息以username作为“主键”都放在了HashMap中。其中

  • createUser(UserDetails user)
  • updateUser(UserDetails user)
  • deleteUser(String username)
  • changePassword(String oldPassword, String newPassword)
  • userExists(String username)

这些方法来自UserDetailsManager

  • loadUserByUsername(String username)

这个方法来自UserDetailsService,而UserDetailsManager继承了UserDetailsService类

配置

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(user);
    }
}

废话说的差不多了,开始上配置。在这里,项目启动的时候提供了一个初始用户。也可以直接调用无参构造方法,但是逻辑上不太好弄。然后,Spring Security官方文档中不建议这么创建UserDetails,并且withDefaultPasswordEncoder也是个弃用方法,推荐的创建方法如下

UserDetails user = User.builder()
		.username("user")
		.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
		.roles("USER")
		.build();

上面的密码是password经过Bcrypt加密后的密文
密文可以这么生成

@SpringBootTest
public class ApplicationTest {

    @Test
    void contextLoads(){
        System.out.println(new BCryptPasswordEncoder().encode("password"));
    }
}

用的IDEA社区版创建的maven工程,手动引入SpringBoot的依赖,还得手动引入一下test依赖。我嫌从网页下载初始化项目麻烦,结果是遇到了更大的麻烦。还得是因为没钱,用不起付费版,流下了没钱的泪水

后续的用户可以调用createUser等方法来实现创建、删除、修改密码等操作。创建和调用bean的时候最好命名一下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于基于 RBAC(Role-Based Access Control)的权限控制,可以使用 Spring BootSpring Security 来实现。下面是一个简单的步骤指南: 1. 添加依赖:在你的 Spring Boot 项目的 pom.xml 文件中,添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` 2. 创建用户和角色实体:创建用户(User)和角色(Role)的实体类,可以使用 JPA 或者其他持久化框架来进行数据库操作。 3. 实现 UserDetailsService:创建一个实现了 Spring SecurityUserDetailsService 接口的类,用于加载用户信息。这个类需要重写 loadUserByUsername 方法,根据用户名从数据库中查询用户信息并返回一个 UserDetails 对象。 4. 创建权限访问控制配置类:创建一个配置类,继承自 WebSecurityConfigurerAdapter,并重写 configure 方法。在这个方法中,你可以配置哪些 URL 需要哪些角色或权限才能访问。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasAnyRole("ADMIN", "USER") .anyRequest().authenticated() .and().formLogin().permitAll() .and().logout().permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override @Bean public UserDetailsService userDetailsService() { // 返回自定义的 UserDetailsService 实现类 // 在这个实现类中通过 JPA 或其他方式查询用户信息 return new CustomUserDetailsService(); } } ``` 5. 配置密码加密:在上面的配置类中,我们使用了 BCryptPasswordEncoder 作为密码加密方式。确保你的用户表中保存的密码是经过 BCrypt 加密的。 6. 创建登录页面:创建一个登录页面,可以是一个简单的 HTML 页面或者使用模板引擎进行渲染。 7. 配置登录页面:在 application.properties 或 application.yml 文件中,配置登录页面的路径和其他相关属性。 ```properties spring.security.login-page=/login spring.security.logout-success-url=/login?logout ``` 以上步骤完成后,你的 Spring Boot 应用程序就可以基于 RBAC 实现简单的权限控制了。根据实际需求,你可以进一步扩展和定制 Spring Security 的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值