下面是详细讲解基于散列加密方案实现 Spring Security 自动登录的攻略。
1. 简介
Spring Security 是一个基于 Spring 框架实现的安全框架,它提供了一系列的安全服务,在 Web 安全、认证、授权等方面有着非常好的表现。其中之一就是实现自动登录。
自动登录是指用户在第一次登录之后,下一次再进入系统时,无需再次输入账号和密码,直接进入已登录状态。Spring Security 提供了记住我功能,可以通过 Cookie 或 Token 的方式,实现自动登录。
在 Spring Security 中,记住我功能默认是基于 Token 实现。Token 通常是一个随机生成的字符串,保存在 Cookie 或 Redis 等缓存中,并与用户信息建立映射关系。在用户下一次访问 System 系统时,会携带 Token,以完成自动登录。
本文将详细讲解如何使用 Spring Security 基于散列加密方案实现自动登录。
2. 散列加密方案
在实现自动登录之前,我们需要先讲解散列加密方案。散列是指将任意长度的消息压缩到固定长度的算法,常见的散列算法包括 MD5、SHA1、SHA256 等。这些算法具有不可逆性和抗碰撞性,通常用于密码加密和数据完整性校验等方面。
在 Spring Security 中,它提供了 BCryptPasswordEncoder 和 ShaPasswordEncoder 两种散列加密器实现。
BCryptPasswordEncoder:这是 Spring Security 推荐的一种散列加密器,它支持动态加盐和可自定义强度等特性,函数开销可调节。比较安全,因此比较慢。
ShaPasswordEncoder:这是一种底层使用 SHA 算法的加密器,不支持加盐和强度调节等特性,速度较快,但相对不够安全。
3. 记住我功能实现流程
下面是记住我功能实现的流程:
- 用户输入账号和密码后,勾选记住我选项,并提交表单;
- Spring Security 会自动识别用户是否勾选了记住我选项,如果勾选,会在登录成功后,生成一个 Token,并将 Token 保存在 Cookie 或 Redis 等缓存中,并与用户信息建立映射关系;
- 用户下一次访问时,Spring Security 会自动解析 Cookie 或缓存中的 Token,查询映射关系,如果查询成功,则直接登录成功,否则继续要求用户输入账号和密码。
4. 实现步骤
以下是基于散列加密方案实现自动登录的步骤:
4.1 配置 Spring Security
首先,需要添加 Spring Security 依赖,并在配置中指定加密器和用户相关信息。
- 添加 Spring Security 依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.5.1</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.5.1</version>
</dependency>
- 配置 Spring Security
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/index").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login")
.successHandler(new LoginSuccessHandler())
.failureHandler(new LoginFailureHandler())
.permitAll()
.and()
.rememberMe()
.userDetailsService(userDetailsService)
.rememberMeParameter("remember-me")
.key("spring-security-demo")
.tokenValiditySeconds(60 * 60 * 24 * 7) // 7 天
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(new LogoutSuccessHandler())
.invalidateHttpSession(true)
.clearAuthentication(true)
.permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
@EnableWebSecurity
:启用 Spring Security;configure(HttpSecurity http)
:配置 HTTP 安全策略,包括访问权限和认证方式等;configure(AuthenticationManagerBuilder auth)
:进行身份认证的用户来源和密码处理方式;
在上面的代码中,我们配置了散列加密器为 BCryptPasswordEncoder,并且将 UserDetailsService
实现类和加密器注入到了 SecurityConfig
中。同时,通过配置 permitAll()
,将 /login
、/logout
、/index
等 URL 放开,其他请求都需要进行身份认证才能访问。
4.2 执行自动登录
为了执行自动登录,我们需要在登录成功后,生成 Token 并将 Token 保存在 Cookie 中。代码实现可以在 SecurityConfig
中添加扩展点 LoginSuccessHandler
的方式实现。
public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Autowired
private TokenRepository tokenRepository;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// 生成 Token
Token token = new Token();
token.setUserId(((User)authentication.getPrincipal()).getId());
token.setToken(UUID.randomUUID().toString());
token.setExpireTime(LocalDateTime.now().plusDays(7)); // 7 天有效期
// 保存 Token
token = tokenRepository.save(token);
// 将 Token 保存到 Cookie 中
Cookie cookie = new Cookie("remember-me", token.getToken());
cookie.setMaxAge(60 * 60 * 24 * 7); // 7 天有效期
cookie.setPath("/");
response.addCookie(cookie);
super.onAuthenticationSuccess(request, response, authentication);
}
}
在上述代码中,我们生成一个 Token 对象,并设置 Token 的有效期和用户 ID 等。然后将 Token 保存到数据库中,并将 Token 保存在 Cookie 中,设置 Cookie 的有效期与 Token 的有效期一致,路径为 "/",表示在整个站点内都可以访问这个 Cookie。
4.3 自动登录逻辑
当用户下一次访问站点时,需要进行自动登录。Spring Security 会自动识别 Cookie 中是否包含 remember-me 的值,如果存在,则会根据值查询 Token,并根据 Token 进行自动登录。
具体实现如下:
- 添加 Redis 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 添加 TokenRepository
public interface TokenRepository extends JpaRepository<Token, Long> {
Token findByToken(String token);
}
- Spring Security 中配置 Redis
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// ...
@Bean
public RedisOperationsSessionRepository sessionRepository() {
RedisConnectionFactory connectionFactory = redisConnectionFactory();
return new RedisOperationsSessionRepository(redisTemplate(connectionFactory));
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new RedisConnectionFactoryBuilder()
.redisConfiguration(RedisConfiguration.defaultConfiguration())
.build();
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new JdkSerializationRedisSerializer());
return template;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/index").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login")
.successHandler(new LoginSuccessHandler())
.failureHandler(new LoginFailureHandler())
.permitAll()
.and()
.rememberMe()
.userDetailsService(userDetailsService)
.rememberMeParameter("remember-me")
.key("spring-security-demo")
.tokenValiditySeconds(60 * 60 * 24 * 7) // 7 天
.tokenRepository(tokenRepository())
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(new LogoutSuccessHandler())
.invalidateHttpSession(true)
.clearAuthentication(true)
.permitAll();
}
@Bean
public TokenRepository tokenRepository() {
return new TokenRepository() {
@Override
public Token findByToken(String token) {
HttpSession session = SecurityContextUtils.get()
.getAuthentication().getDetails();
return (Token) session.getAttribute(token);
}
@Override
public <S extends Token> S save(S entity) {
HttpSession session = SecurityContextUtils.get()
.getAuthentication().getDetails();
session.setAttribute(entity.getToken(), entity);
return entity;
}
};
}
}
在上述代码中,我们添加了 Redis 依赖,并且在配置中注册了 RedisTemplate、RedisConnectionFactory 和 RedisOperationsSessionRepository 等 Bean,用于执行 Redis 相关的逻辑。
在 configure(HttpSecurity http)
中,我们增加了 rememberMe().tokenRepository(tokenRepository())
;同时,实现了一个内存 TokenRepository 的版本,如下代码所示:
@Bean
public TokenRepository tokenRepository() {
return new TokenRepository() {
private final Map<String, Token> map = new ConcurrentHashMap<>();
@Override
public Token findByToken(String token) {
return map.get(token);
}
@Override
public <S extends Token> S save(S entity) {
map.put(entity.getToken(), entity);
return entity;
}
};
}
对于 Token 的存储,我们使用了 Redis,甚至可以选择使用数据库,而上边的实现则是在内存中保存。
这样,当用户再次访问时,Spring Security 就可以通过查询 TokenRepository 来验证 Token 的正确性,并获取对应的用户信息完成自动登录的流程。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:SpringSecurity基于散列加密方案实现自动登录 - Python技术站