In this paper, a personal blog address: https://www.leafage.top/posts/detail/21697I2R

Gateway and auth were two services before, and auth was one written by Shiro, a filter and a configuration. The contents were very simple, such as generating tokens and verifying tokens. No additional security checks, and then let the project refactor.

First, we need to integrate Gateway and Shiro. However, since Gateway is WebFlux and Shiro Spring is WebMVC, we failed. If you have done it and succeeded, please tell me how to integrate it.

Since Spring Cloud Gateway is based on WebFlux, many tutorials on the Internet cannot be used. WebFlux configuration will be changed. For details, see the following code example:

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 {/ / is used to obtain the remote data private final HypervisorApi HypervisorApi; public ServerSecurityConfiguration(HypervisorApi hypervisorApi) { this.hypervisorApi = hypervisorApi; } /** * password configuration, BcryptPasswordenCoder * * @return bcryptPasswordenCoder Encryption */ @bean protected PasswordenCoder PasswordenCoder () { return new BCryptPasswordEncoder(); } / * * @ * * * the user data load return JdbcReactiveUserDetailsService interface * / @ Bean public ReactiveUserDetailsService UserDetailsService () {/ / custom ReactiveUserDetails return new JdbcReactiveUserDetailsService (hypervisorApi); } / security configuration * / * * * @ 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(); } / * * * * log in successfully executed after processor/private ServerAuthenticationSuccessHandler authenticationSuccessHandler () {return new ServerSuccessHandler(); } / logon failure after the execution of the processor * * * * / private ServerAuthenticationFailureHandler authenticationFailureHandler () {return new ServerFailureHandler(); }}

The above example code is a section of my open source project. The general configuration is as written above, so it can be used. However, since it is Shiro in our previous project, there is a custom encryption and decryption logic.

First explain the situation, before that a set of encryption (front MD5, don’t add salt, then stored in the database is the data after salt and the corresponding salt (one per account), wants to compare the password before login for dynamic salt, then add salt to the MD5, compare again, but at the time of configuration is couldn’t get a user’s salt value)

So the above version of the configuration is unable to pass the verification, must be before verification, to the request of the password mixed with the account corresponding to the salt for two encryption after comparison, but there is a problem here:

  1. Several encryption/decryption tools provided by the Security framework do not have MD5 methods;
  2. [Fixed] [Fixed] [Fixed] [Fixed] [Fixed] [Fixed] [Fixed] [Fixed] [Fixed] [Fixed] [Fixed] [Fixed] [Fixed]

The first problem is easy to handle. The solution is to customize the encryption/decryption method and inject it into the configuration class, as shown in the following example:

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; }}

You can return the UserDetails implementation by using the findByUserName () method of the UserDetailsService interface. Use the UserBuilder inner class that implements User by default to solve this problem, because there is one property in the UserBuilder class, the PasswordenCoder property, which is of type Fucntion<String, String>, The default implementation is password-> password, which does nothing to the password.

Again, look at the findByUsername() method before solving the problem:

@Service public class UserDetailsServiceImpl implements ReactiveUserDetailsService { @Resource private RemoteService remoteService; @Override public Mono<UserDetails> findByUsername(String username) { return remoteService.getUser(username).map(userBO -> User.builder() .username(username) .password(userBO.getPassword()) .authorities(grantedAuthorities(userBO.getAuthorities())) .build()); } private Set<GrantedAuthority> grantedAuthorities(Set<String> authorities) { return authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet()); }}

Then find the solution to the problem, to change the code, as follows:

Add a new code handling method

private Function<String, String> passwordEncoder(String salt) {
    return rawPassword -> SecureUtil.md5(rawPassword + salt);
}

Then add the Builder chain

@Service public class UserDetailsServiceImpl implements ReactiveUserDetailsService { @Resource private RemoteService remoteService; @Override public Mono<UserDetails> findByUsername(String username) { return remoteService.getUser(username).map(userBO -> User.Builder (). PasswordenCoder (PasswordenCoder (userbo.getSalt ())) // Set the dynamic salt here. .password(userBO.getPassword()) .authorities(grantedAuthorities(userBO.getAuthorities())) .build()); } private Set<GrantedAuthority> grantedAuthorities(Set<String> authorities) { return authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet()); } private Function<String, String> passwordEncoder(String salt) { return rawPassword -> SecureUtil.md5(rawPassword + salt); }}

Then run the code, request the login interface, the landing was successful.