spring gateway集成spring security


spring gateway

分布式开发时,微服务会有很多,但是网关是请求的第一入口,所以一般会把客户端请求的权限验证统一放在网关进行认证与鉴权。SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

注意:

由于web容器不同,在gateway项目中使用的webflux,是不能和spring-web混合使用的。

 

Spring MVC和WebFlux的区别
 
11772383-b70d80a3893f3a04.png

依赖:

        <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> 

配置spring security

spring security设置要采用响应式配置,基于WebFlux中WebFilter实现,与Spring MVC的Security是通过Servlet的Filter实现类似,也是一系列filter组成的过滤链。

  1. 部分概念是对应的:
Reactive Web
@EnableWebFluxSecurity @EnableWebSecurity
ReactiveSecurityContextHolder SecurityContextHolder
AuthenticationWebFilter FilterSecurityInterceptor
ReactiveAuthenticationManager AuthenticationManager
ReactiveUserDetailsService UserDetailsService
ReactiveAuthorizationManager AccessDecisionManager
  1. 首先需要配置@EnableWebFluxSecurity注解,开启Spring WebFlux Security的支持
import java.util.LinkedList; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.authentication.AuthenticationWebFilter; /** * @Author: pilsy * @Date: 2020/6/29 0029 16:54 */ @Configuration @EnableWebFluxSecurity public class SecurityConfig { @Autowired private AuthenticationConverter authenticationConverter; @Autowired private AuthorizeConfigManager authorizeConfigManager; @Autowired private AuthEntryPointException serverAuthenticationEntryPoint; @Autowired private JsonServerAuthenticationSuccessHandler jsonServerAuthenticationSuccessHandler; @Autowired private JsonServerAuthenticationFailureHandler jsonServerAuthenticationFailureHandler; @Autowired private JsonServerLogoutSuccessHandler jsonServerLogoutSuccessHandler; @Autowired private AuthenticationManager authenticationManager; private static final String[] AUTH_WHITELIST = new String[]{"/login", "/logout"}; @Bean public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { SecurityWebFilterChain chain = http.formLogin() .loginPage("/login") // 登录成功handler .authenticationSuccessHandler(jsonServerAuthenticationSuccessHandler) // 登陆失败handler .authenticationFailureHandler(jsonServerAuthenticationFailureHandler) // 无访问权限handler .authenticationEntryPoint(serverAuthenticationEntryPoint) .and() .logout() // 登出成功handler .logoutSuccessHandler(jsonServerLogoutSuccessHandler) .and() .csrf().disable() .httpBasic().disable() .authorizeExchange() // 白名单放行 .pathMatchers(AUTH_WHITELIST).permitAll() // 访问权限控制 .anyExchange().access(authorizeConfigManager) .and().build(); // 设置自定义登录参数转换器 chain.getWebFilters() .filter(webFilter -> webFilter instanceof AuthenticationWebFilter) .subscribe(webFilter -> { AuthenticationWebFilter filter = (AuthenticationWebFilter) webFilter; filter.setServerAuthenticationConverter(authenticationConverter); }); return chain; } /** * 注册用户信息验证管理器,可按需求添加多个按顺序执行 * @return */ @Bean ReactiveAuthenticationManager reactiveAuthenticationManager() { LinkedList<ReactiveAuthenticationManager> managers = new LinkedList<>(); managers.add(authenticationManager); return new DelegatingReactiveAuthenticationManager(managers); } /** * BCrypt密码编码 * @return */ @Bean public BCryptPasswordEncoder bcryptPasswordEncoder() { return new BCryptPasswordEncoder(); } } 
  1. 特殊handler的实现
  • JsonServerAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler
  • JsonServerAuthenticationFailureHandler implements ServerAuthenticationFailureHandler
  • JsonServerLogoutSuccessHandler implements ServerLogoutSuccessHandler
  • AuthEntryPointException implements ServerAuthenticationEntryPoint
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.security.core.Authentication; import org.springframework.security.web.server.WebFilterExchange; import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler; import org.springframework.stereotype.Component; import com.alibaba.fastjson.JSONObject; import com.gsoft.foa.common.dto.AjaxResult; import com.gsoft.foa.common.dto.ApiErrorCode; import io.netty.util.CharsetUtil; import reactor.core.publisher.Mono; /** * @Author: pilsy * @Date: 2020/7/10 0010 15:05 */ @Component public class JsonServerLogoutSuccessHandler implements ServerLogoutSuccessHandler { @Override public Mono<Void> onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) { ServerHttpResponse response = exchange.getExchange().getResponse(); response.setStatusCode(HttpStatus.OK); response.getHeaders().set(HttpHeaders.CONTENT_TYPE, "application/json; charset=UTF-8"); String result = JSONObject.toJSONString(AjaxResult.restResult("注销成功", ApiErrorCode.SUCCESS)); DataBuffer buffer = response.bufferFactory().wrap(result.getBytes(CharsetUtil.UTF_8)); return response.writeWith(Mono.just(buffer)); } } 
  1. 表单登陆时security默认只会获取了username,password参数,但有时候需要一些特殊属性,所以需要覆盖默认获取的表单参数的Converter
import org.springframework.http.HttpHeaders; import org.springframework.security.core.Authentication; import org.springframework.security.web.server.authentication.ServerFormLoginAuthenticationConverter; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * 将表单参数转换为AuthenticationToken * * @Author: pilsy * @Date: 2020/7/15 0015 15:41 */ @Component public class AuthenticationConverter extends ServerFormLoginAuthenticationConverter { private String usernameParameter = "username"; private String passwordParameter = "password"; @Override public Mono<Authentication> convert(ServerWebExchange exchange) { HttpHeaders headers = exchange.getRequest().getHeaders(); String tenant = headers.getFirst("_tenant"); String host = headers.getHost().getHostName(); return exchange.getFormData() .map(data -> { String username = data.getFirst(this.usernameParameter); String password = data.getFirst(this.passwordParameter); return new AuthenticationToken(username, password, tenant, host); }); } } 
import lombok.Getter; import lombok.Setter; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import java.util.Collection; /** * 存储用户信息的token * * @Author: pilsy * @Date: 2020/7/15 0015 16:08 */ @SuppressWarnings("serial") @Getter @Setter public class AuthenticationToken extends UsernamePasswordAuthenticationToken { private String tenant; private String host; public AuthenticationToken(Object principal, Object credentials, String tenant, String host) { super(principal, credentials); this.tenant = tenant; this.host = host; } public AuthenticationToken(Object principal, Object credentials) { super(principal, credentials); } public AuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { super(principal, credentials, authorities); } } 
  1. 验证用户身份
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AbstractUserDetailsReactiveAuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; /** * 验证用户 * * @Author: pilsy * @Date: 2020/7/15 0015 16:43 */ 
import com.gsoft.foa.gateway.repository.AccountInfoRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; 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.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; /** * 身份认证类 * * @Author: pilsy * @Date: 2020/6/29 0029 18:01 */ @Slf4j @Component public class MySqlReactiveUserDetailsServiceImpl implements ReactiveUserDetailsService, ReactiveUserDetailsPasswordService { private static final String USER_NOT_EXISTS = "用户不存在!"; private final AccountInfoRepository accountInfoRepository; public MySqlReactiveUserDetailsServiceImpl(AccountInfoRepository accountInfoRepository) { this.accountInfoRepository = accountInfoRepository; } @Autowired BCryptPasswordEncoder bCryptPasswordEncoder; @Override public Mono<UserDetails> findByUsername(String username) { return accountInfoRepository.findByUsername(username) .switchIfEmpty(Mono.defer(() -> Mono.error(new UsernameNotFoundException(USER_NOT_EXISTS)))) .doOnNext(u -> log.info( String.format("查询账号成功 user:%s password:%s", u.getUsername(), u.getPassword()))) .cast(UserDetails.class); } @Override public Mono<UserDetails> updatePassword(UserDetails user, String newPassword) { return accountInfoRepository.findByUsername(user.getUsername()) .switchIfEmpty(Mono.defer(() -> Mono.error(new UsernameNotFoundException(USER_NOT_EXISTS)))) .map(foundedUser -> { foundedUser.setPassword(bCryptPasswordEncoder.encode(newPassword)); return foundedUser; }) .flatMap(updatedUser -> accountInfoRepository.save(updatedUser)) .cast(UserDetails.class); } } 
  1. 鉴权

import com.alibaba.fastjson.JSONObject; import com.gsoft.foa.common.dto.AjaxResult; import com.gsoft.foa.common.dto.ApiErrorCode; 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; /** * API请求权限校验配置类 * * @Author: pilsy * @Date: 2020/7/1 0001 18:27 */ @Slf4j @Component public class AuthorizeConfigManager implements ReactiveAuthorizationManager<AuthorizationContext> { private 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("用户请求API校验通过,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(d -> d.isGranted()) .switchIfEmpty(Mono.defer(() -> { AjaxResult<String> ajaxResult = AjaxResult.restResult("当前用户没有访问权限! ", ApiErrorCode.FAILED); String body = JSONObject.toJSONString(ajaxResult); return Mono.error(new AccessDeniedException(body)); })) .flatMap(d -> Mono.empty()); } }


作者:pilisiyang
链接:https://www.jianshu.com/p/acb2c3ec6401
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM