introduce

Spring-Cloud-Gatewway

Spring Cloud Gateway is built based on Spring Boot 2.x, Spring WebFlux and Project Reactor. As a result, many of the synchronization libraries and schemas that you are familiar with (for example, Spring Data and Spring Security) may not apply when you use the Spring Cloud Gateway. If you are not familiar with these projects, it is recommended that you read their documentation to familiarize yourself with some of the new concepts before using the Spring Cloud Gateway.

Spring-Security

Spring Security is a framework that provides authentication, authorization, and protection against common attacks. With first-class support for imperative and responsive applications, it is the de facto standard for securing Spring-based applications.

Spring-Webflux

The original Web framework included in the Spring Framework, Spring Web MVC, was built specifically for the Servlet API and Servlet container. The reactive stack Web framework Spring WebFlux was added later in version 5.0. It is completely non-blocking, supports Reactive Streams back pressure, and runs on servers such as Netty, Undertow, and Servlet 3.1+ containers.

Both Web frameworks reflect the names of their source modules (Spring-WebMVC and Spring-WebFlux) and coexist within the Spring Framework. Each module is optional. An application can use one module, two modules, or in some cases, two modules, such as the Spring MVC controller WebClient with React.

Pay attention to

WebFlux used in Gateway projects cannot be mixed with Spring-Web due to the difference in Web containers.

The difference between Spring MVC and WebFlux:


Project Environment Version

  1. Spring – Cloud: 2020.0.1
  2. Spring – the Boot: 2.4.3

Gradle rely on

dependencies {
 implementation(
            'org.springframework.cloud:spring-cloud-starter-gateway',
            'org.springframework.boot:spring-boot-starter-security'
 )
}

Spring ws-security configuration

Spring Security Settings should be configured in a responsive manner, which is based on WebFilter in WebFlux. Security of Spring MVC is realized through Filter of Servlet, which is also a filtering chain composed of a series of Filters. Reactor corresponds to a traditional MVC configuration:

webflux mvc role
@EnableWebFluxSecurity @EnableWebSecurity Enable Security Configuration
ServerAuthenticationSuccessHandler AuthenticationSuccessHandler Log in successfully Handler
ServerAuthenticationFailureHandler AuthenticationFailureHandler Login failure Handler
ReactiveAuthorizationManager<AuthorizationContext> AuthorizationManager Authentication management
ServerSecurityContextRepository SecurityContextHolder Authentication information storage management
ReactiveUserDetailsService UserDetailsService The user login
ReactiveAuthorizationManager AccessDecisionManager Authentication management
ServerAuthenticationEntryPoint AuthenticationEntryPoint No authentication Handler
ServerAccessDeniedHandler AccessDeniedHandler Failed authentication Handler
  1. Security Core Configuration
package com.pluto.gateway.security; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.server.SecurityWebFilterChain; import reactor.core.publisher.Mono; import javax.annotation.Resource; import java.util.LinkedList; /** * @author ShiLei * @version 1.0.0 * @date 2021/3/11 10:56 * @description WebFlux Security Core Configuration Class */ @EnableWebFluxSecurity public class WebfluxSecurityConfig { @Resource private DefaultAuthorizationManager defaultAuthorizationManager; @Resource private UserDetailsServiceImpl userDetailsServiceImpl; @Resource private DefaultAuthenticationSuccessHandler defaultAuthenticationSuccessHandler; @Resource private DefaultAuthenticationFailureHandler defaultAuthenticationFailureHandler; @Resource private TokenAuthenticationManager tokenAuthenticationManager; @Resource private DefaultSecurityContextRepository defaultSecurityContextRepository; @Resource private DefaultAuthenticationEntryPoint defaultAuthenticationEntryPoint; @Resource private DefaultAccessDeniedHandler defaultAccessDeniedHandler; /** * @value ("${security.nofilter}") private String noFilter; @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity httpSecurity) .authenticationManager(reactiveAuthenticationManager()) .securityContextRepository(defaultSecurityContextRepository) // AuthorizeExchange (Exchange-> Exchange.PathMatchers (noFilter).PermitAll () PathMatchers (HttpMethod. The OPTIONS). PermitAll () anyExchange (). The access (defaultAuthorizationManager)) formLogin () / / custom processing  .authenticationSuccessHandler(defaultAuthenticationSuccessHandler) .authenticationFailureHandler(defaultAuthenticationFailureHandler) .and() .exceptionHandling() .authenticationEntryPoint(defaultAuthenticationEntryPoint) .and() .exceptionHandling() .accessDeniedHandler(defaultAccessDeniedHandler) .and() .csrf().disable() ; return httpSecurity.build(); } /** * BCrypt password encoding */ @bean (" PasswordenCoder ") public PasswordenCoder PasswordenCoder () {return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } /** * Register user information verification manager, According to demand to add multiple execute sequentially * / @ Bean ReactiveAuthenticationManager ReactiveAuthenticationManager () { LinkedList<ReactiveAuthenticationManager> managers = new LinkedList<>(); Managers. Add (AUTHENTICATION-> {// Other sign-on methods can be set here to not throw an exception or mono.error return mono.empty (); }); // Must be last otherwise username and password validation will take precedence but the authenticationManager will call mono.error if the username and password is incorrect causing the authenticationManager to fail to take effect from Managers.add(new) UserDetailsRepositoryReactiveAuthenticationManager(userDetailsServiceImpl)); managers.add(tokenAuthenticationManager); return new DelegatingReactiveAuthenticationManager(managers); }}

2. User authentication

package com.pluto.gateway.security; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import java.io.Serializable; import java.util.Collection; /** * @author ShiLei * @version 1.0.0 * @date 2021/3/10 13:15 * @description */ public class SecurityUserDetails  extends User implements Serializable { private Long userId; public SecurityUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities, Long userId) { super(username, password, authorities); this.userId = userId; } public SecurityUserDetails(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities, Long userId) { super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); this.userId = userId; } public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; }}
package com.pluto.gateway.security; import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; import javax.annotation.Resource; import java.util.ArrayList; /** * @author ceshi * @date 2021/3/9 14:03 * @description * @version 1.0.0 */ @service public class UserDetailsServiceImpl implements ReactiveUserDetailsService { @Resource private PasswordEncoder passwordEncoder; @Override public Mono<UserDetails> findByUsername(String username) { SecurityUserDetails securityUserDetails = new SecurityUserDetails( "user", passwordEncoder.encode("user"), true, true, true, true, new ArrayList<>(), 1L ); return Mono.just(securityUserDetails); }}

3.1 Custom login successful Handler

package com.pluto.gateway.security; import com.alibaba.fastjson.JSONObject; import com.pluto.common.basic.utils.JwtTokenUtil; import com.pluto.common.basic.utils.ResultVoUtil; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.security.core.Authentication; import org.springframework.security.web.server.WebFilterExchange; import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; import java.util.HashMap; import java.util.Map; /** * @author ShiLei * @version 1.0.0 * @date 2021/3/11 15:00 * @description Login successfully processed */ @Component public class DefaultAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {/ * * * * / token expired time @Value("${jwt.token.expired}") private int jwtTokenExpired; / * * / * * refresh token time @ Value (" ${JWT. Token. Refresh. Expired} ") private int jwtTokenRefreshExpired; @Override public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) { return Mono.defer(() -> Mono.just(webFilterExchange.getExchange().getResponse()).flatMap(response -> { DataBufferFactory dataBufferFactory = response.bufferFactory(); // generate JWT Token Map<String, Object bb0 Map = new HashMap<>(2); SecurityUserDetails userDetails = (SecurityUserDetails) authentication.getPrincipal(); map.put("userId", userDetails.getUserId()); map.put("username", userDetails.getUsername()); map.put("roles",userDetails.getAuthorities()); String token = JwtTokenUtil.generateToken(map, userDetails.getUsername(), jwtTokenExpired); String refreshToken = JwtTokenUtil.generateToken(map, userDetails.getUsername(), jwtTokenRefreshExpired); Map<String, Object> tokenMap = new HashMap<>(2); tokenMap.put("token", token); tokenMap.put("refreshToken", refreshToken); DataBuffer dataBuffer = dataBufferFactory.wrap(JSONObject.toJSONString(ResultVoUtil.success(tokenMap)).getBytes()); return response.writeWith(Mono.just(dataBuffer)); })); }}

3.2 Custom login failure Handler

package com.pluto.gateway.security; import com.alibaba.fastjson.JSONObject; import com.pluto.common.basic.enums.UserStatusCodeEnum; import com.pluto.common.basic.utils.ResultVoUtil; import com.pluto.common.basic.vo.ResultVO; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.security.authentication.*; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.server.WebFilterExchange; import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; import java.util.Map; /** * @author ShiLei * @version 1.0.0 * @date 2021/3/11 15:14 * @description Login failure */ @Component public class DefaultAuthenticationFailureHandler implements ServerAuthenticationFailureHandler { @Override public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) { return Mono.defer(() -> Mono.just(webFilterExchange.getExchange() .getResponse()).flatMap(response -> { DataBufferFactory dataBufferFactory = response.bufferFactory(); ResultVO<Map<String, Object>> resultVO = ResultVoUtil.error(); / / account does not exist the if (exception instanceof UsernameNotFoundException) {resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_NOT_EXIST); } else if (Exception instanceof BadCredentialsException) {resultVO = ResultVoUtil.failed(UserStatusCodeEnum.LOGIN_PASSWORD_ERROR); // Account expired} else if (Exception instanceof AccountExpiredException) {resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_EXPIRED); // Account locked} else if (Exception instanceof LockedException) {resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_LOCKED); / / the user credentials failed} else if (exception instanceof CredentialsExpiredException) {resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_CREDENTIAL_EXPIRED); ResultVo =; // Account disabled} else if (Exception instanceof DisabledException) {resultVo = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_DISABLE); } DataBuffer dataBuffer = dataBufferFactory.wrap(JSONObject.toJSONString(resultVO).getBytes()); return response.writeWith(Mono.just(dataBuffer)); })); }}

3.3 Custom unauthenticated Handler

package com.pluto.gateway.security; import com.alibaba.fastjson.JSONObject; import com.pluto.common.basic.enums.UserStatusCodeEnum; import com.pluto.common.basic.utils.ResultVoUtil; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.server.ServerAuthenticationEntryPoint; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.nio.charset.Charset; /** * @Author ShiLei * @Version 1.0.0 * @Date 2021/3/11 15:17 * @Description not authenticated */ @Component public class DefaultAuthenticationEntryPoint implements ServerAuthenticationEntryPoint { @Override public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException ex) { return Mono.defer(() -> Mono.just(exchange.getResponse())).flatMap(response -> { response.setStatusCode(HttpStatus.UNAUTHORIZED); response.getHeaders().setContentType(MediaType.APPLICATION_JSON); DataBufferFactory dataBufferFactory = response.bufferFactory(); String result = JSONObject.toJSONString(ResultVoUtil.failed(UserStatusCodeEnum.USER_UNAUTHORIZED)); DataBuffer buffer = dataBufferFactory.wrap(result.getBytes( Charset.defaultCharset())); return response.writeWith(Mono.just(buffer)); }); }}

3.4 Custom authentication failure Handler

package com.pluto.gateway.security; import com.alibaba.fastjson.JSONObject; import com.pluto.common.basic.enums.UserStatusCodeEnum; import com.pluto.common.basic.utils.ResultVoUtil; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.nio.charset.Charset; /** * @author ShiLei * @version 1.0.0 * @date 2021/3/11 11:12 * @description */ @Component public class DefaultAccessDeniedHandler implements ServerAccessDeniedHandler { @Override public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) { return Mono.defer(() -> Mono.just(exchange.getResponse())) .flatMap(response -> { response.setStatusCode(HttpStatus.OK); response.getHeaders().setContentType(MediaType.APPLICATION_JSON); DataBufferFactory dataBufferFactory = response.bufferFactory(); String result = JSONObject.toJSONString(ResultVoUtil.failed(UserStatusCodeEnum.PERMISSION_DENIED)); DataBuffer buffer = dataBufferFactory.wrap(result.getBytes( Charset.defaultCharset())); return response.writeWith(Mono.just(buffer)); }); }}

4. Customize JWT Token authentication management

package com.pluto.gateway.security; import org.apache.commons.lang3.StringUtils; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.web.server.context.ServerSecurityContextRepository; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import javax.annotation.Resource; import java.util.List; /** * @author ShiLei * @version 1.0.0 * @date 2021/3/11 16:27 * @description */ @Component public class DefaultSecurityContextRepository implements ServerSecurityContextRepository { public final static String TOKEN_HEADER = "Authorization"; public final static String BEARER = "Bearer "; @Resource private TokenAuthenticationManager tokenAuthenticationManager; @Override public Mono<Void> save(ServerWebExchange exchange, SecurityContext context) { return Mono.empty(); } @Override public Mono<SecurityContext> load(ServerWebExchange exchange) { ServerHttpRequest request = exchange.getRequest(); List<String> headers = request.getHeaders().get(TOKEN_HEADER); if (! CollectionUtils.isEmpty(headers)) { String authorization = headers.get(0); if (StringUtils.isNotEmpty(authorization)) { String token = authorization.substring(BEARER.length()); if (StringUtils.isNotEmpty(token)) { return tokenAuthenticationManager.authenticate( new UsernamePasswordAuthenticationToken(token, null) ).map(SecurityContextImpl::new); } } } return Mono.empty(); }}
package com.pluto.gateway.security; import com.pluto.common.basic.utils.JwtTokenUtil; import org.springframework.context.annotation.Primary; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; import java.util.Collection; /** * @author ShiLei * @version 1.0.0 * @date 2021/3/11 13:23 * @description Token processing */ @Component @primary public class TokenAuthenticationManager implements ReactiveAuthenticationManager { @Override @SuppressWarnings("unchecked") public Mono<Authentication> authenticate(Authentication authentication) { return Mono.just(authentication) .map(auth -> JwtTokenUtil.parseJwtRsa256(auth.getPrincipal().toString())) .map(claims -> { Collection<? extends GrantedAuthority> roles = (Collection<? extends GrantedAuthority>) claims.get("roles"); return new UsernamePasswordAuthenticationToken( claims.getSubject(), null, roles ); }); }}

5. Custom authentication management

package com.pluto.gateway.security; import com.alibaba.fastjson.JSONObject; import com.pluto.common.basic.enums.UserStatusCodeEnum; import com.pluto.common.basic.utils.ResultVoUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.ReactiveAuthorizationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.web.server.authorization.AuthorizationContext; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.Collection; /** * @author ShiLei * @version 1.0.0 * @date 2021/3/11 13:10 * @description */ @Component @slf4j public class DefaultAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> { private final AntPathMatcher  antPathMatcher = new AntPathMatcher(); @Override public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext authorizationContext) { return authentication.map(auth -> { ServerWebExchange exchange = authorizationContext.getExchange(); ServerHttpRequest request = exchange.getRequest(); Collection<? extends GrantedAuthority> authorities = auth.getAuthorities(); for (GrantedAuthority authority : authorities) { String authorityAuthority = authority.getAuthority(); String path = request.getURI().getPath(); If (antPathMatcher.match(AuthorityAuthority, Path)) {log.info(String. Format (" User request API validation passed, GrantedAuthority:{%s} Path:{%s} ", authorityAuthority, path)); return new AuthorizationDecision(true); } } return new AuthorizationDecision(false); }).defaultIfEmpty(new AuthorizationDecision(false)); } @Override public Mono<Void> verify(Mono<Authentication> authentication, AuthorizationContext object) { return check(authentication, object) .filter(AuthorizationDecision::isGranted) .switchIfEmpty(Mono.defer(() -> { String body = JSONObject.toJSONString(ResultVoUtil.failed(UserStatusCodeEnum.PERMISSION_DENIED)); return Mono.error(new AccessDeniedException(body)); })).flatMap(d -> Mono.empty()); }}

GitHub repository address (remember to switch to the dev-1.0.0 branch)

Welcome everyone who loves programming to communicate and make progress together, remember to click on the collection oh!