当前位置:   article > 正文

从零开始搭建高负载java架构(05)——gateway网关节点(权限验证篇)_reactiveauthorizationmanager

reactiveauthorizationmanager

目录

1. 基本原理

 2. 具体实现

2.1 通过mysql加载用户信息的实现

 2.2 实现基本的Security逻辑的配置类实现

2.3 实现基本的访问路径资源权限

2.4 登录验证中常用的回调函数自定义

 2.4.1 自定义登录成功回调ServerAuthenticationSuccessHandler

2.4.2 自定义登录失败回调ServerAuthenticationFailureHandler

2.4.3 自定义因未登录而访问未授权路径的回调ServerAuthenticationEntryPoint

 2.4.4 自定义访问未授权资源路径的回调ServerAccessDeniedHandler

2.4.5 自定义成功退出登录的回调ServerLogoutSuccessHandler

2.4.6 最终配置类引用上述自定义回调的修改


1. 基本原理

spring gateway采用的是webflux的反应式实现,因此对应的sring security也需要用webflux的处理方式,个人整理的WebFlux应用的认证过程如下:

  1. 用户通过form表单输入用户名和密码,通过POST请求登录认证
  2. 后台通过ServerFormLoginAuthenticationConverter类的convert接口,把获取到的用户名和密码封装成一个UsernamePasswordAuthenticationToken对象(Authentication的子类),然后把它传递给 ReactiveAuthenticationManager进行认证。
  3. ReactiveAuthenticationManager的认证逻辑接口authenticate
    (1) 如果认证失败则返回null,程序会自动跳到第1 步让用户重新输入用户名和密码
    (2) 如果认证成功则返回UsernamePasswordAuthenticationToken对象(authenticated字段被设置为true),并进行下面的步骤
  4. 将返回的UsernamePasswordAuthenticationToken对象通过类ServerSecurityContextRepository的save接口,把UsernamePasswordAuthenticationToken对象保存到SecurityContext对象中
  5. 然后默认会将用户重定向到之前访问的页面。
  6. 用户登录认证成功后再次访问之前受保护的资源时,就会调用ReactiveAuthorizationManager类的check接口对用户访问的路径进行权限鉴定,如不存在对应路径的访问权限,则默认会返回 403 错误码(如果要特殊处理,可以实现ServerAccessDeniedHandler类的handle接口,返回特定的信息)。

【注意】另外,如果要通过数据库或redis查找用户信息,可以重载实现ReactiveUserDetailsService的接口findByUsername,从其他数据源里查出user数据转成UserDetails对象,如果除了用户名和密码,还有其他额外的用户信息需要保存,可以重置UserDetails类,添加额外的信息

基本逻辑类都在包: spring-security-core-6.0.3.jar中

  1. AuthenticationWebFilter登录认证过滤器的filter接口
    包路径:org.springframework.security.web.server.authentication
  2. ServerFormLoginAuthenticationConverter 登录认证:
    包路径:org.springframework.security.web.server.authentication
    调用包:org.springframework.security.web.server的同名类:
    ServerFormLoginAuthenticationConverter的apply接口
    取Form转成Map的username和password
  3. ServerHttpBasicAuthenticationConverter类
    包路径:org.springframework.security.web.server.authentication
    http头字段:Authorization
    格式:Basic base64(用户名:密码)
  4. 默认的观察者模式ReactiveAuthenticationManager类:
    ObservationReactiveAuthenticationManager
    包路径:org.springframework.security.authentication
  5. 默认的ReactiveAuthenticationManager:
    UserDetailsRepositoryReactiveAuthenticationManager
    (主要验证逻辑在父类:AbstractUserDetailsReactiveAuthenticationManager)
    验证通过在createUsernamePasswordAuthenticationToken接口中设置登录验证通过标识authenticated为true
    包路径:org.springframework.security.authentication
  6. 默认的ServerSecurityContextRepository:
    WebSessionServerSecurityContextRepository
  7. 默认的SecurityContext:
    SecurityContextImpl
    包路径:org.springframework.security.core.context
  8. 默认的ReactiveAuthorizationManager:
    AuthenticatedReactiveAuthorizationManager<AuthorizationContext>
    包路径:org.springframework.security.authorization

 2. 具体实现

2.1 通过mysql加载用户信息的实现

基本的重载方法,为了支持从数据库查找用户登录信息,实现接口类:UserDetails

  1. package com.cloudservice.gateway_service.security;
  2. import java.util.Collection;
  3. import java.util.Set;
  4. import org.springframework.security.core.GrantedAuthority;
  5. import org.springframework.security.core.userdetails.UserDetails;
  6. public class GatewayUserDetails implements UserDetails {
  7. private Long id; // 用户id
  8. private String password; // 密码
  9. private String username; // 用户名
  10. private boolean enabled; // 帐户是否可用
  11. private Set<GatewayUserGrantedAuthority> authorities; // 权限信息
  12. public Long getId() {
  13. return id;
  14. }
  15. public void setId(Long id) {
  16. this.id = id;
  17. }
  18. @Override
  19. public Collection<? extends GrantedAuthority> getAuthorities() {
  20. return authorities;
  21. }
  22. public void setAuthorities(Set<GatewayUserGrantedAuthority> authorities) {
  23. this.authorities = authorities;
  24. }
  25. @Override
  26. public String getPassword() {
  27. return password;
  28. }
  29. public void setPassword(String password) {
  30. this.password = password;
  31. }
  32. @Override
  33. public String getUsername() {
  34. return username;
  35. }
  36. public void setUsername(String username) {
  37. this.username = username;
  38. }
  39. @Override
  40. public boolean isAccountNonExpired() {
  41. // TODO 帐号是到到期
  42. return true;
  43. }
  44. @Override
  45. public boolean isAccountNonLocked() {
  46. // TODO 帐号是否锁定
  47. return true;
  48. }
  49. @Override
  50. public boolean isCredentialsNonExpired() {
  51. // TODO 密码是否到期
  52. return true;
  53. }
  54. @Override
  55. public boolean isEnabled() {
  56. return this.enabled;
  57. }
  58. public void setEnabled(boolean enabled) {
  59. this.enabled = enabled;
  60. }
  61. }

定义用户表的领域类:Users

  1. package com.cloudservice.gateway_service.security;
  2. import java.util.HashSet;
  3. import java.util.Set;
  4. import jakarta.persistence.Entity;
  5. import jakarta.persistence.GeneratedValue;
  6. import jakarta.persistence.GenerationType;
  7. import jakarta.persistence.Id;
  8. import jakarta.validation.constraints.NotNull;
  9. import jakarta.validation.constraints.Size;
  10. import lombok.AllArgsConstructor;
  11. import lombok.Data;
  12. import lombok.NoArgsConstructor;
  13. @Data
  14. @AllArgsConstructor
  15. @NoArgsConstructor
  16. @Entity
  17. public class Users {
  18. @Id
  19. @GeneratedValue(strategy = GenerationType.IDENTITY)
  20. private Long id;
  21. @NotNull
  22. @Size(min=5, message="Username must be at least 5 characters long")
  23. private String username;
  24. @NotNull
  25. private String password;
  26. private boolean enabled;
  27. public GatewayUserDetails to_user_details() {
  28. GatewayUserDetails userDetails = new GatewayUserDetails();
  29. userDetails.setId(id);
  30. userDetails.setUsername(username);
  31. userDetails.setPassword(password);
  32. userDetails.setEnabled(enabled);
  33. // 此处为了测试路径权限,写死order路径权限,实际中应该是从角色与路径的授权表groups_authorities里读取加载
  34. Set<GatewayUserGrantedAuthority> authorities = new HashSet<GatewayUserGrantedAuthority>();
  35. authorities.add(new GatewayUserGrantedAuthority("/order/"));
  36. userDetails.setAuthorities(authorities);
  37. return userDetails;
  38. }
  39. public static Users from_user_details(GatewayUserDetails userDetails) {
  40. return new Users(userDetails.getId()
  41. , userDetails.getUsername()
  42. , userDetails.getPassword()
  43. , userDetails.isEnabled());
  44. }
  45. }

简单通过使用JPA 的Repository类来根据用户名查数据库返回基本的用户信息:

  1. package com.cloudservice.gateway_service.security;
  2. import org.springframework.data.repository.CrudRepository;
  3. public interface UsersRepository extends CrudRepository<Users, Long>{
  4. Iterable<Users> findByUsername(String username);
  5. }

 网关的nacos配置里加上mysql的datasource配置:

spring:
  datasource:
    url: jdbc:mysql://192.168.10.111:32001/gateway?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true
    username: mysql用户名
    password: mysql密码
    driver-class-name: com.mysql.cj.jdbc.Driver

 自定义加载用户信息的类ReactiveUserDetailsService:

  1. package com.cloudservice.gateway_service.security;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import org.apache.commons.codec.digest.DigestUtils;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
  7. import org.springframework.security.core.userdetails.UserDetails;
  8. import org.springframework.stereotype.Component;
  9. import lombok.extern.slf4j.Slf4j;
  10. import reactor.core.publisher.Flux;
  11. import reactor.core.publisher.Mono;
  12. @Slf4j
  13. @Component
  14. public class GatewayReactiveUserDetailsService implements ReactiveUserDetailsService {
  15. @Autowired
  16. private UsersRepository usersRepository;
  17. @Override
  18. public Mono<UserDetails> findByUsername(String username) {
  19. // TODO 优先查找缓存再查找数据库
  20. // String Val1 = passwordEncoder.encode("123456");
  21. log.info("gateway find user: {} {}", username, DigestUtils.sha256Hex("123456"));
  22. List<UserDetails> userDetailsList = new ArrayList<UserDetails>();
  23. usersRepository.findByUsername(username).forEach(user -> {
  24. userDetailsList.add(user.to_user_details());
  25. });
  26. return Flux.fromIterable(userDetailsList).next();
  27. }
  28. }

 2.2 实现基本的Security逻辑的配置类实现

  1. package com.cloudservice.gateway_service.security;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
  6. import org.springframework.security.config.web.server.ServerHttpSecurity;
  7. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  8. import org.springframework.security.crypto.password.PasswordEncoder;
  9. import org.springframework.security.web.server.SecurityWebFilterChain;
  10. @Configuration
  11. @EnableWebFluxSecurity
  12. public class GatewaySecurityConfig {
  13. @Bean
  14. public PasswordEncoder passwordEncoder() {
  15. return new BCryptPasswordEncoder();
  16. }
  17. @Bean
  18. public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
  19. http
  20. .authorizeExchange()
  21. .pathMatchers("/favicon.*", "/login", "/logout").permitAll()
  22. .anyExchange().authenticated()
  23. .and().formLogin()
  24. .and().csrf().disable();
  25. return http.build();
  26. }
  27. }

2.3 实现基本的访问路径资源权限

重载路径权限验证类 ReactiveAuthorizationManager<AuthorizationContext>,代码如下:

  1. package com.cloudservice.gateway_service.security;
  2. import org.springframework.security.authentication.AuthenticationTrustResolver;
  3. import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
  4. import org.springframework.security.authorization.AuthorizationDecision;
  5. import org.springframework.security.authorization.ReactiveAuthorizationManager;
  6. import org.springframework.security.core.Authentication;
  7. import org.springframework.security.web.server.authorization.AuthorizationContext;
  8. import org.springframework.stereotype.Component;
  9. import reactor.core.publisher.Mono;
  10. @Component
  11. public class GatewayReactiveAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext>{
  12. private AuthenticationTrustResolver authTrustResolver = new AuthenticationTrustResolverImpl();
  13. // 验证通过则返回:AuthorizationDecision(true)
  14. // 验证失败则返回:AuthorizationDecision(false)
  15. @Override
  16. public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext context) {
  17. return authentication
  18. .filter(authentication_filter -> select_context(authentication_filter, context))
  19. .map(authentication_map -> authenticate(authentication_map, context))
  20. .defaultIfEmpty(new AuthorizationDecision(false));
  21. }
  22. private boolean select_context(Authentication authentication, AuthorizationContext context) {
  23. // String req_path = context.getExchange().getRequest().getURI().getPath();
  24. // log.info("check filter path: {}", req_path);
  25. return !this.authTrustResolver.isAnonymous(authentication);
  26. }
  27. private AuthorizationDecision authenticate(Authentication authentication, AuthorizationContext context) {
  28. if (authentication.isAuthenticated()) {
  29. // 判断context.getExchange().getRequest().getPath()是否在authentication_notanonymous.getAuthorities()集合中
  30. String req_path = context.getExchange().getRequest().getURI().getPath();
  31. if (authentication.getAuthorities().contains(new GatewayUserGrantedAuthority(req_path)) == false) {
  32. return new AuthorizationDecision(false);
  33. }
  34. }
  35. return new AuthorizationDecision(authentication.isAuthenticated());
  36. }
  37. }

用户可访问路径的集合存放在authorities集合里,为了支持集合类的contains操作,必须重载GrantedAuthority类并实现equals和hashCode接口,具体实现代码如下:

  1. package com.cloudservice.gateway_service.security;
  2. import org.springframework.security.core.GrantedAuthority;
  3. public class GatewayUserGrantedAuthority implements GrantedAuthority, java.lang.Comparable<Object> {
  4. String authority;
  5. public GatewayUserGrantedAuthority() {}
  6. public GatewayUserGrantedAuthority(String authority) {
  7. this.authority = authority;
  8. }
  9. @Override
  10. public String getAuthority() {
  11. return this.authority;
  12. }
  13. public void setAuthority(String authority) {
  14. this.authority = authority;
  15. }
  16. @Override
  17. public int compareTo(Object obj) {
  18. if (obj instanceof GatewayUserGrantedAuthority) {
  19. return this.authority.compareTo(((GatewayUserGrantedAuthority)obj).getAuthority());
  20. }
  21. return 1;
  22. }
  23. public boolean equals(Object obj) {
  24. if (!(obj instanceof GatewayUserGrantedAuthority)) {
  25. return false;
  26. }
  27. if (obj == this) {
  28. return true;
  29. }
  30. return this.authority.equals(((GatewayUserGrantedAuthority)obj).getAuthority());
  31. }
  32. public int hashCode() {
  33. return this.authority.hashCode();
  34. }
  35. }

修改配置类GatewaySecurityConfig的springSecurityFilterChain接口,引用路径鉴权类GatewayReactiveAuthorizationManager:

  1. @Configuration
  2. @EnableWebFluxSecurity
  3. public class GatewaySecurityConfig {
  4. @Bean
  5. public PasswordEncoder passwordEncoder() {
  6. return new BCryptPasswordEncoder();
  7. }
  8. @Autowired
  9. private GatewayReactiveAuthorizationManager gatewayReactiveAuthorizationManager;
  10. @Bean
  11. public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
  12. http
  13. .authorizeExchange()
  14. .pathMatchers("/favicon.ico", "/login", "/logout").permitAll()
  15. .anyExchange().access(gatewayReactiveAuthorizationManager)
  16. .and().formLogin()
  17. .and().csrf().disable();
  18. return http.build();
  19. }
  20. }

2.4 登录验证中常用的回调函数自定义

 2.4.1 自定义登录成功回调ServerAuthenticationSuccessHandler

通过自定义登录成功时的回调函数,可以自定义登录成功后的返回消息,vue3中可以直接返回json格式,方便判断后在vue3的js代码中实现提示后跳转逻辑,实现代码如下:

  1. package com.cloudservice.gateway_service.security;
  2. import org.springframework.core.io.buffer.DataBuffer;
  3. import org.springframework.http.server.reactive.ServerHttpResponse;
  4. import org.springframework.security.core.Authentication;
  5. import org.springframework.security.web.server.WebFilterExchange;
  6. import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
  7. import org.springframework.stereotype.Component;
  8. import org.springframework.web.server.ServerWebExchange;
  9. import com.fasterxml.jackson.databind.ObjectMapper;
  10. import lombok.extern.slf4j.Slf4j;
  11. import reactor.core.publisher.Mono;
  12. @Slf4j
  13. @Component
  14. public class CustomLoginSuccessHandler implements ServerAuthenticationSuccessHandler{
  15. @Override
  16. public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
  17. ServerWebExchange exchange = webFilterExchange.getExchange();
  18. ServerHttpResponse response = exchange.getResponse();
  19. response.getHeaders().add("Content-Type", "application/json; charset=UTF-8");
  20. log.info("user login success: {}", authentication.getName());
  21. Object principal = authentication.getPrincipal();
  22. ObjectMapper objectMapper = new ObjectMapper();
  23. DataBuffer bodyDataBuffer = null;
  24. try {
  25. bodyDataBuffer = response.bufferFactory().wrap(objectMapper.writeValueAsBytes(principal));
  26. } catch (Exception e) {
  27. e.printStackTrace();
  28. }
  29. return response.writeWith(Mono.just(bodyDataBuffer));
  30. }
  31. }

2.4.2 自定义登录失败回调ServerAuthenticationFailureHandler

通过自定义登录失败回调,可以定制返回登录失败的消息,vue3里一般需要返回json格式数据,实现代码如下:

  1. package com.cloudservice.gateway_service.security;
  2. import java.util.HashMap;
  3. import org.springframework.core.io.buffer.DataBuffer;
  4. import org.springframework.http.HttpStatus;
  5. import org.springframework.http.server.reactive.ServerHttpResponse;
  6. import org.springframework.security.authentication.BadCredentialsException;
  7. import org.springframework.security.authentication.LockedException;
  8. import org.springframework.security.core.AuthenticationException;
  9. import org.springframework.security.web.server.WebFilterExchange;
  10. import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
  11. import org.springframework.stereotype.Component;
  12. import com.alibaba.fastjson.JSON;
  13. import lombok.extern.slf4j.Slf4j;
  14. import reactor.core.publisher.Mono;
  15. @Slf4j
  16. @Component
  17. public class CustomLoginFailureHandler implements ServerAuthenticationFailureHandler {
  18. @Override
  19. public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
  20. ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
  21. response.setStatusCode(HttpStatus.FORBIDDEN);
  22. response.getHeaders().add("Content-Type", "application/json; charset=UTF-8");
  23. log.info("user login fail: {}", webFilterExchange.getExchange().getRequest().getPath());
  24. HashMap<String, String> map = new HashMap<String, String>();
  25. map.put("code", "-1001");
  26. if (exception instanceof LockedException) {
  27. map.put("message", "账户被锁定,请联系管理员!");
  28. } else if (exception instanceof BadCredentialsException) {
  29. map.put("message", "用户名或者密码输入错误,请重新输入!");
  30. } else {
  31. map.put("message", exception.getMessage());
  32. }
  33. DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONBytes(map));
  34. return response.writeWith(Mono.just(dataBuffer));
  35. }
  36. }

2.4.3 自定义因未登录而访问未授权路径的回调ServerAuthenticationEntryPoint

通过自定义未登录而访问路径的错误回调 ,可以返回vue3需要的json格式,也可以强制跳转到登录页面,此处实现是强制跳转页面:

  1. package com.cloudservice.gateway_service.security;
  2. import java.net.URI;
  3. import java.util.HashMap;
  4. import org.springframework.core.io.buffer.DataBuffer;
  5. import org.springframework.http.HttpStatus;
  6. import org.springframework.http.server.reactive.ServerHttpResponse;
  7. import org.springframework.security.core.AuthenticationException;
  8. import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
  9. import org.springframework.stereotype.Component;
  10. import org.springframework.web.server.ServerWebExchange;
  11. import com.alibaba.fastjson.JSON;
  12. import lombok.extern.slf4j.Slf4j;
  13. import reactor.core.publisher.Mono;
  14. @Slf4j
  15. @Component
  16. public class CustomNoLoginHandler implements ServerAuthenticationEntryPoint {
  17. @Override
  18. public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException ex) {
  19. return Mono.fromRunnable(() -> {
  20. ServerHttpResponse response = exchange.getResponse();
  21. response.setStatusCode(HttpStatus.FOUND);
  22. response.getHeaders().add("Content-Type", "application/json; charset=UTF-8");
  23. // 强制跳转到登录页面,在vue3中一般是返回json数据,并交由vue3来跳转
  24. response.getHeaders().setLocation(URI.create("/login"));
  25. log.info("url when no login: {}", exchange.getRequest().getPath());
  26. HashMap<String, Object> map = new HashMap<>();
  27. map.put("code", HttpStatus.FOUND.value());
  28. map.put("message", "暂未登录,请您先进行登录");
  29. DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONBytes(map));
  30. response.writeWith(Mono.just(dataBuffer));
  31. });
  32. }
  33. }

【注意】加了此自定义回调时,不能正常访问security默认定义的login页面,需要自定义登录页面,本人简单的实现示例:

此示例的controller使用到thymeleaf的模板,因此需要在pom.xml中引用依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

创建一个用来接收GET方式的login请求的controller:

  1. package com.cloudservice.gateway_service.security;
  2. import org.springframework.stereotype.Controller;
  3. import org.springframework.web.bind.annotation.GetMapping;
  4. @Controller
  5. public class CustomLoginControl {
  6. @GetMapping("/login")
  7. public String login() {
  8. return "login";
  9. }
  10. }

 在src/main/resources/templates/目录下定义登录页面模板login.html

  1. <html lang="en"><head>
  2. <meta charset="utf-8">
  3. <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  4. <title>Please sign in</title>
  5. </head>
  6. <body>
  7. <div class="container">
  8. <form method="post" action="/login">
  9. <h2>Please sign in</h2>
  10. <p>
  11. <label>Username</label>
  12. <input type="text" id="username" name="username" placeholder="Username" required="" autofocus="">
  13. </p>
  14. <p>
  15. <label>Password</label>
  16. <input type="password" id="password" name="password" placeholder="Password" required="">
  17. </p>
  18. <button type="submit">Sign in</button>
  19. </form>
  20. </div>
  21. </body>
  22. </html>

 2.4.4 自定义访问未授权资源路径的回调ServerAccessDeniedHandler

当访问资源路径进行权限验证时,ReactiveAuthorizationManager验证不通过,则会回调此类的接口,可以自定义返回的数据格式,代码如下:

  1. package com.cloudservice.gateway_service.security;
  2. import java.util.HashMap;
  3. import org.springframework.core.io.buffer.DataBuffer;
  4. import org.springframework.http.HttpStatus;
  5. import org.springframework.http.server.reactive.ServerHttpResponse;
  6. import org.springframework.security.access.AccessDeniedException;
  7. import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
  8. import org.springframework.stereotype.Component;
  9. import org.springframework.web.server.ServerWebExchange;
  10. import com.alibaba.fastjson.JSON;
  11. import lombok.extern.slf4j.Slf4j;
  12. import reactor.core.publisher.Mono;
  13. @Slf4j
  14. @Component
  15. public class CustomUrlNoRightHandler implements ServerAccessDeniedHandler{
  16. @Override
  17. public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) {
  18. ServerHttpResponse response = exchange.getResponse();
  19. response.setStatusCode(HttpStatus.FORBIDDEN);
  20. response.getHeaders().add("Content-Type", "application/json; charset=UTF-8");
  21. log.info("url no right: {}", exchange.getRequest().getPath());
  22. HashMap<String, String> map = new HashMap<>();
  23. map.put("code", "-1002");
  24. map.put("message", "资源路径无访问权限!");
  25. DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONBytes(map));
  26. return response.writeWith(Mono.just(dataBuffer));
  27. }
  28. }

2.4.5 自定义成功退出登录的回调ServerLogoutSuccessHandler

用于自定义退出登录是的返回数据格式,示例代码如下:

  1. package com.cloudservice.gateway_service.security;
  2. import java.util.HashMap;
  3. import org.springframework.core.io.buffer.DataBuffer;
  4. import org.springframework.http.HttpStatus;
  5. import org.springframework.http.server.reactive.ServerHttpResponse;
  6. import org.springframework.security.core.Authentication;
  7. import org.springframework.security.web.server.WebFilterExchange;
  8. import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
  9. import org.springframework.stereotype.Component;
  10. import com.alibaba.fastjson.JSON;
  11. import lombok.extern.slf4j.Slf4j;
  12. import reactor.core.publisher.Mono;
  13. @Slf4j
  14. @Component
  15. public class CustomLogoutSuccessHandler implements ServerLogoutSuccessHandler {@Override
  16. public Mono<Void> onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) {
  17. ServerHttpResponse response = exchange.getExchange().getResponse();
  18. response.setStatusCode(HttpStatus.OK);
  19. response.getHeaders().add("Content-Type", "application/json; charset=UTF-8");
  20. log.info("user logout success: {}", authentication.getName());
  21. HashMap<String, String> map = new HashMap<>();
  22. map.put("code", "0");
  23. map.put("message", "退出登录成功!");
  24. DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONBytes(map));
  25. return response.writeWith(Mono.just(dataBuffer));
  26. }
  27. }

2.4.6 最终配置类引用上述自定义回调的修改

  1. package com.cloudservice.gateway_service.security;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
  6. import org.springframework.security.config.web.server.ServerHttpSecurity;
  7. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  8. import org.springframework.security.crypto.password.PasswordEncoder;
  9. import org.springframework.security.web.server.SecurityWebFilterChain;
  10. @Configuration
  11. @EnableWebFluxSecurity
  12. public class GatewaySecurityConfig {
  13. @Bean
  14. public PasswordEncoder passwordEncoder() {
  15. return new BCryptPasswordEncoder();
  16. }
  17. @Autowired
  18. private GatewayReactiveAuthorizationManager gatewayReactiveAuthorizationManager;
  19. @Autowired
  20. private CustomLoginFailureHandler customLoginFailureHandler;
  21. @Autowired
  22. private CustomLoginSuccessHandler customLoginSuccessHandler;
  23. @Autowired
  24. private CustomNoLoginHandler customNoLoginHandler;
  25. @Autowired
  26. private CustomLogoutSuccessHandler customLogoutSuccessHandler;
  27. @Autowired
  28. private CustomUrlNoRightHandler customUrlNoRightHandler;
  29. @Bean
  30. public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
  31. http
  32. .authorizeExchange()
  33. .pathMatchers("/favicon.ico", "/login", "/logout").permitAll()
  34. .anyExchange().access(gatewayReactiveAuthorizationManager)
  35. .and().formLogin()
  36. .authenticationFailureHandler(customLoginFailureHandler)
  37. .authenticationSuccessHandler(customLoginSuccessHandler)
  38. .and().exceptionHandling()
  39. .accessDeniedHandler(customUrlNoRightHandler)
  40. .authenticationEntryPoint(customNoLoginHandler)
  41. .and().logout().logoutSuccessHandler(customLogoutSuccessHandler)
  42. .and().csrf().disable();
  43. return http.build();
  44. }
  45. }

【注意】

1. 登录会话的超时配置,可以修改nacos配置,加上:

server:
  reactive:
    session:
      timeout: 1m # session超时时间为1分钟, 默认是60分钟

2. 为了防止集中登录导致的登录数据库过载,修改GatewayReactiveUserDetailsService类的findByUsername接口实现,引入redis,优先查看redis缓存里有没有用户信息,有则从redis中加载,否则才从mysql数据库中加载

[上一篇]从零开始搭建高负载java架构(04)——gateway网关节点(动态路由)

[下一篇]从零开始搭建高负载java架构(06):gateway网关节点(sentinel篇)

参考资料:

[1]Spring Security详细讲解(JWT+SpringSecurity登入案例)

[2]Spring Security核心类简介

[3]在 webflux 环境中使用 Spring Security

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/68881
推荐阅读
相关标签
  

闽ICP备14008679号