Spring Security 密码验证动态加盐的验证处理方法

网友投稿 364 2022-10-20


Spring Security 密码验证动态加盐的验证处理方法

本文个人博客地址:https://leafage.top/posts/detail/21697I2R

最近几天在改造项目,需要将gateway整合security在一起进行认证和鉴权,之前gateway和auth是两个服务,auth是shiro写的一个,一个filter和一个配置,内容很简单,生成token,验证token,没有其他的安全检查,然后让对项目进行重构。

先是要整合gateway和shiro,然而因为gateway是webflux,而shiro-spring是webmvc,所以没搞成功,如果有做过并成功的,请告诉我如何进行整合,非常感谢。

那整合security呢,因为spring cloud gateway基于webflux,所以网上很多教程是用不了的,webflux的配置会有一些变化,具体看如下代码示例:

import io.leafage.gateway.api.HypervisorApi;

import io.leafage.gateway.handler.ServerFailureHandler;

import io.leafage.gateway.handler.ServerSuccessHandler;

import io.leafage.gateway.service.JdbcReactiveUserDetailsService;

import org.springframework.context.annotation.Bean;

import org.springframework.http.HttpMethod;

import org.springframework.http.HttpStatus;

import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;

import org.springframework.security.config.web.server.ServerHttpSecurity;

import org.springframework.security.core.userdetails.ReactiveUserDetailsService;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import org.springframework.security.crypto.password.PasswordEncoder;

import org.springframework.security.web.server.SecurityWebFilterChain;

import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;

import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;

import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;

import org.springframework.security.web.server.authentication.logout.HttpStatusReturningServerLogoutSuccessHandler;

import org.springframework.security.web.server.csrf.CookieServerCsrfTokenRepository;

/**

* spring security config .

*

* @author liwenqiang 2019/7/12 17:51

*/

@EnableWebFluxSecurity

public class ServerSecurityConfiguration {

// 用于获取远程数据

private final HypervisorApi hypervisorApi;

public ServerSecurityConfiguration(HypervisorApi hypervisorApi) {

this.hypervisorApi = hypervisorApi;

}

/**

* 密码配置,使用BCryptPasswordEncoder

*

* @return BCryptPasswordEncoder 加密方式

*/

@Bean

protecthttp://ed PasswordEncoder passwordEncoder() {

return new BCryptPasswordEncoder();

}

/**

* 用户数据加载

*

* @return JdbcReactiveUserDetailsService 接口

*/

@Bean

public ReactiveUserDetailsService userDetailsService() {

// 自定义的ReactiveUserDetails 实现

return new JdbcReactiveUserDetailsService(hypervisorApi);

}

/**

* 安全配置

*/

@Bean

SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {

http.formLogin(f -> f.authenticationSuccessHandler(authenticationSuccessHandler())

.authenticationFailureHandler(authenticationFailureHandler()))

.logout(l -> l.logoutSuccessHandler(new HttpStatusReturningServerLogoutSuccessHandler()))

.csrf(c -> c.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()))

.authorizeExchange(a -> a.pathMatchers(HttpMethod.OPTIONS).permitAll()

.anyExchange().authenticated())

.exceptionHandling(e -> e.authenticationEntryPoint(new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)));

return http.build();

}

/**

* 登陆成功后执行的处理器

*/

private ServerAuthenticationSuccessHandler authenticationSuccessHandler() {

return new ServerSuccessHandler();

}

/**

* 登陆失败后执行的处理器

*/

private ServerAuthenticatiCXGXjIsNAonFailureHandler authenticationFailureHandler() {

return new ServerFailureHandler();

}

}

上面的示例代码,是我开源项目中的一段,一般的配置就如上面写的,就可以使用了,但是由于我们之前的项目中的是shiro,然后有一个自定义的加密解密的逻辑。

首先说明一下情况,之前那一套加密(前端MD5,不加盐,然后数据库存储的是加盐后的数据和对应的盐(每个账号一个),要登录比较之前对密码要获取动态的盐,然后加盐进行MD5,再进行对比,但是在配置的时候是没法获取某一用户的盐值)

所以上面的一版配置是没法通过验证的,必须在验证之前,给请求的密码混合该账号对应的盐进行二次加密后在对比,但是这里就有问题了:

security 框架提供的几个加密\解密工具没有MD5的方式;

security 配置加密\解密方式的时候,无法填入动态的账号的加密盐;

对于第一个问题还好处理,解决方式是:自定义加密\解密方式,然后注入到配置类中,示例如下:

import cn.hutool.crypto.SecureUtil;

import com.ichinae.imis.gateway.utils.SaltUtil;

import org.springframework.security.crypto.codec.Utf8;

import org.springframework.security.crypto.password.PasswordEncoder;

import java.security.MessageDigest;

/**

* 自定义加密解密

*/

public class MD5PasswordEncoder implements PasswordEncoder {

@Override

public String encode(CharSequence charSequence) {

String salt = SaltUtil.generateSalt();

return SecureUtil.md5(SecureUtil.md5(charSequence.toString()) + salt);

}

@Override

public boolean matches(CharSequence charSequence, String encodedPassword) {

byte[] expectedBytes = bytesUtf8(charSequence.toString());

byte[] actualBytes = bytesUtf8(charSequence.toString());

return MessageDigest.isEqual(expectedBytes, actualBytes);

}

private static byte[] bytesUtf8(String s) {

// need to check if Utf8.encode() runs in constant time (probably not).

// This may leak length of string.

return (s != null) ? Utf8.encode(s) : null;

}

}

第二个问题的解决办法,找了很多资料,也没有找到,后来查看security的源码发现,可以在UserDetailsService接口的findByUsername()方法中,在返回UserDetails实现的时候,使用默认实现User的UserBuilder内部类来解决这个问题,因为UserBuilder类中有一个属性,passwordEncoder属性,它是Fucntion类型的,默认实现是 password -> password,即对密码不做任何处理,先看下它的源码:

再看下解决问题之前的findByUsername()方法:

@Service

public class UserDetailsServiceImpl implements ReactiveUserDetailsService {

@Resource

private RemoteService remoteService;

@Override

public Mono findByUsername(String username) {

return remoteService.getUser(username).map(userBO -> User.builder()

.username(username)

.password(userBO.getPassword())

.authorities(grantedAuthorities(userBO.getAuthorities()))

.build());

}

private Set grantedAuthorities(Set authorities) {

return authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet());

}

}

那找到了问题的解决方法,就来改代码了,如下所示:

新增一个代码处理方法

private Function passwordEncoder(String salt) {

return rawPassword -> SecureUtil.md5(rawPassword + salt);

}

然后添加builder链

@Service

public class UserDetailsServiceImpl implements ReactiveUserDetailsService {

@Resource

private RemoteService remoteService;

@Override

public Mono findByUsername(String username) {

return remoteService.getUser(username).map(userBO -> User.builder()

.passwordEncoder(passwordEncoder(userBO.getSalt())) //在这里设置动态的盐

.username(username)

.password(userBO.getPassword())

.authorities(grantedAuthorities(userBO.getAuthorities()))

.build());

}

private Set grahttp://ntedAuthorities(Set authorities) {

return authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet());

}

private Function passwordEncoder(String salt) {

return rawPassword -> SecureUtil.md5(rawPassword + salt);

}

}

然后跑一下代码,请求登录接口,就登陆成功了。

以上就是Spring Security 密码验证动态加盐的验证处理的详细内容,更多关于Spring Security密码验证的资料请关注我们其它相关文章!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:数据中心机柜通风循环系统
下一篇:国产布线发展历程回顾
相关文章

 发表评论

暂时没有评论,来抢沙发吧~