目录
2.4.1 自定义登录成功回调ServerAuthenticationSuccessHandler
2.4.2 自定义登录失败回调ServerAuthenticationFailureHandler
2.4.3 自定义因未登录而访问未授权路径的回调ServerAuthenticationEntryPoint
2.4.4 自定义访问未授权资源路径的回调ServerAccessDeniedHandler
2.4.5 自定义成功退出登录的回调ServerLogoutSuccessHandler
1. 基本原理
spring gateway采用的是webflux的反应式实现,因此对应的sring security也需要用webflux的处理方式,个人整理的WebFlux应用的认证过程如下:
- 用户通过form表单输入用户名和密码,通过POST请求登录认证
- 后台通过ServerFormLoginAuthenticationConverter类的convert接口,把获取到的用户名和密码封装成一个UsernamePasswordAuthenticationToken对象(Authentication的子类),然后把它传递给 ReactiveAuthenticationManager进行认证。
- ReactiveAuthenticationManager的认证逻辑接口authenticate
(1) 如果认证失败则返回null,程序会自动跳到第1 步让用户重新输入用户名和密码
(2) 如果认证成功则返回UsernamePasswordAuthenticationToken对象(authenticated字段被设置为true),并进行下面的步骤 - 将返回的UsernamePasswordAuthenticationToken对象通过类ServerSecurityContextRepository的save接口,把UsernamePasswordAuthenticationToken对象保存到SecurityContext对象中
- 然后默认会将用户重定向到之前访问的页面。
- 用户登录认证成功后再次访问之前受保护的资源时,就会调用ReactiveAuthorizationManager类的check接口对用户访问的路径进行权限鉴定,如不存在对应路径的访问权限,则默认会返回 403 错误码(如果要特殊处理,可以实现ServerAccessDeniedHandler类的handle接口,返回特定的信息)。
【注意】另外,如果要通过数据库或redis查找用户信息,可以重载实现ReactiveUserDetailsService的接口findByUsername,从其他数据源里查出user数据转成UserDetails对象,如果除了用户名和密码,还有其他额外的用户信息需要保存,可以重置UserDetails类,添加额外的信息
基本逻辑类都在包: spring-security-core-6.0.3.jar中
- AuthenticationWebFilter登录认证过滤器的filter接口
包路径:org.springframework.security.web.server.authentication - ServerFormLoginAuthenticationConverter 登录认证:
包路径:org.springframework.security.web.server.authentication
调用包:org.springframework.security.web.server的同名类:
ServerFormLoginAuthenticationConverter的apply接口
取Form转成Map的username和password - ServerHttpBasicAuthenticationConverter类
包路径:org.springframework.security.web.server.authentication
http头字段:Authorization
格式:Basic base64(用户名:密码) - 默认的观察者模式ReactiveAuthenticationManager类:
ObservationReactiveAuthenticationManager
包路径:org.springframework.security.authentication - 默认的ReactiveAuthenticationManager:
UserDetailsRepositoryReactiveAuthenticationManager
(主要验证逻辑在父类:AbstractUserDetailsReactiveAuthenticationManager)
验证通过在createUsernamePasswordAuthenticationToken接口中设置登录验证通过标识authenticated为true
包路径:org.springframework.security.authentication - 默认的ServerSecurityContextRepository:
WebSessionServerSecurityContextRepository - 默认的SecurityContext:
SecurityContextImpl
包路径:org.springframework.security.core.context - 默认的ReactiveAuthorizationManager:
AuthenticatedReactiveAuthorizationManager<AuthorizationContext>
包路径:org.springframework.security.authorization
2. 具体实现
2.1 通过mysql加载用户信息的实现
基本的重载方法,为了支持从数据库查找用户登录信息,实现接口类:UserDetails
package com.cloudservice.gateway_service.security;
import java.util.Collection;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
public class GatewayUserDetails implements UserDetails {
private Long id; // 用户id
private String password; // 密码
private String username; // 用户名
private boolean enabled; // 帐户是否可用
private Set<GatewayUserGrantedAuthority> authorities; // 权限信息
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
public void setAuthorities(Set<GatewayUserGrantedAuthority> authorities) {
this.authorities = authorities;
}
@Override
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public boolean isAccountNonExpired() {
// TODO 帐号是到到期
return true;
}
@Override
public boolean isAccountNonLocked() {
// TODO 帐号是否锁定
return true;
}
@Override
public boolean isCredentialsNonExpired() {
// TODO 密码是否到期
return true;
}
@Override
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
定义用户表的领域类:Users
package com.cloudservice.gateway_service.security;
import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Users {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Size(min=5, message="Username must be at least 5 characters long")
private String username;
@NotNull
private String password;
private boolean enabled;
public GatewayUserDetails to_user_details() {
GatewayUserDetails userDetails = new GatewayUserDetails();
userDetails.setId(id);
userDetails.setUsername(username);
userDetails.setPassword(password);
userDetails.setEnabled(enabled);
// 此处为了测试路径权限,写死order路径权限,实际中应该是从角色与路径的授权表groups_authorities里读取加载
Set<GatewayUserGrantedAuthority> authorities = new HashSet<GatewayUserGrantedAuthority>();
authorities.add(new GatewayUserGrantedAuthority("/order/"));
userDetails.setAuthorities(authorities);
return userDetails;
}
public static Users from_user_details(GatewayUserDetails userDetails) {
return new Users(userDetails.getId()
, userDetails.getUsername()
, userDetails.getPassword()
, userDetails.isEnabled());
}
}
简单通过使用JPA 的Repository类来根据用户名查数据库返回基本的用户信息:
package com.cloudservice.gateway_service.security;
import org.springframework.data.repository.CrudRepository;
public interface UsersRepository extends CrudRepository<Users, Long>{
Iterable<Users> findByUsername(String username);
}
网关的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:
package com.cloudservice.gateway_service.security;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Slf4j
@Component
public class GatewayReactiveUserDetailsService implements ReactiveUserDetailsService {
@Autowired
private UsersRepository usersRepository;
@Override
public Mono<UserDetails> findByUsername(String username) {
// TODO 优先查找缓存再查找数据库
// String Val1 = passwordEncoder.encode("123456");
log.info("gateway find user: {} {}", username, DigestUtils.sha256Hex("123456"));
List<UserDetails> userDetailsList = new ArrayList<UserDetails>();
usersRepository.findByUsername(username).forEach(user -> {
userDetailsList.add(user.to_user_details());
});
return Flux.fromIterable(userDetailsList).next();
}
}
2.2 实现基本的Security逻辑的配置类实现
package com.cloudservice.gateway_service.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
@Configuration
@EnableWebFluxSecurity
public class GatewaySecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.pathMatchers("/favicon.*", "/login", "/logout").permitAll()
.anyExchange().authenticated()
.and().formLogin()
.and().csrf().disable();
return http.build();
}
}
2.3 实现基本的访问路径资源权限
重载路径权限验证类 ReactiveAuthorizationManager<AuthorizationContext>,代码如下:
package com.cloudservice.gateway_service.security;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@Component
public class GatewayReactiveAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext>{
private AuthenticationTrustResolver authTrustResolver = new AuthenticationTrustResolverImpl();
// 验证通过则返回:AuthorizationDecision(true)
// 验证失败则返回:AuthorizationDecision(false)
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext context) {
return authentication
.filter(authentication_filter -> select_context(authentication_filter, context))
.map(authentication_map -> authenticate(authentication_map, context))
.defaultIfEmpty(new AuthorizationDecision(false));
}
private boolean select_context(Authentication authentication, AuthorizationContext context) {
// String req_path = context.getExchange().getRequest().getURI().getPath();
// log.info("check filter path: {}", req_path);
return !this.authTrustResolver.isAnonymous(authentication);
}
private AuthorizationDecision authenticate(Authentication authentication, AuthorizationContext context) {
if (authentication.isAuthenticated()) {
// 判断context.getExchange().getRequest().getPath()是否在authentication_notanonymous.getAuthorities()集合中
String req_path = context.getExchange().getRequest().getURI().getPath();
if (authentication.getAuthorities().contains(new GatewayUserGrantedAuthority(req_path)) == false) {
return new AuthorizationDecision(false);
}
}
return new AuthorizationDecision(authentication.isAuthenticated());
}
}
用户可访问路径的集合存放在authorities集合里,为了支持集合类的contains操作,必须重载GrantedAuthority类并实现equals和hashCode接口,具体实现代码如下:
package com.cloudservice.gateway_service.security;
import org.springframework.security.core.GrantedAuthority;
public class GatewayUserGrantedAuthority implements GrantedAuthority, java.lang.Comparable<Object> {
String authority;
public GatewayUserGrantedAuthority() {}
public GatewayUserGrantedAuthority(String authority) {
this.authority = authority;
}
@Override
public String getAuthority() {
return this.authority;
}
public void setAuthority(String authority) {
this.authority = authority;
}
@Override
public int compareTo(Object obj) {
if (obj instanceof GatewayUserGrantedAuthority) {
return this.authority.compareTo(((GatewayUserGrantedAuthority)obj).getAuthority());
}
return 1;
}
public boolean equals(Object obj) {
if (!(obj instanceof GatewayUserGrantedAuthority)) {
return false;
}
if (obj == this) {
return true;
}
return this.authority.equals(((GatewayUserGrantedAuthority)obj).getAuthority());
}
public int hashCode() {
return this.authority.hashCode();
}
}
修改配置类GatewaySecurityConfig的springSecurityFilterChain接口,引用路径鉴权类GatewayReactiveAuthorizationManager:
@Configuration
@EnableWebFluxSecurity
public class GatewaySecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
private GatewayReactiveAuthorizationManager gatewayReactiveAuthorizationManager;
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.pathMatchers("/favicon.ico", "/login", "/logout").permitAll()
.anyExchange().access(gatewayReactiveAuthorizationManager)
.and().formLogin()
.and().csrf().disable();
return http.build();
}
}
2.4 登录验证中常用的回调函数自定义
2.4.1 自定义登录成功回调ServerAuthenticationSuccessHandler
通过自定义登录成功时的回调函数,可以自定义登录成功后的返回消息,vue3中可以直接返回json格式,方便判断后在vue3的js代码中实现提示后跳转逻辑,实现代码如下:
package com.cloudservice.gateway_service.security;
import org.springframework.core.io.buffer.DataBuffer;
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.ServerAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
@Slf4j
@Component
public class CustomLoginSuccessHandler implements ServerAuthenticationSuccessHandler{
@Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
ServerWebExchange exchange = webFilterExchange.getExchange();
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("Content-Type", "application/json; charset=UTF-8");
log.info("user login success: {}", authentication.getName());
Object principal = authentication.getPrincipal();
ObjectMapper objectMapper = new ObjectMapper();
DataBuffer bodyDataBuffer = null;
try {
bodyDataBuffer = response.bufferFactory().wrap(objectMapper.writeValueAsBytes(principal));
} catch (Exception e) {
e.printStackTrace();
}
return response.writeWith(Mono.just(bodyDataBuffer));
}
}
2.4.2 自定义登录失败回调ServerAuthenticationFailureHandler
通过自定义登录失败回调,可以定制返回登录失败的消息,vue3里一般需要返回json格式数据,实现代码如下:
package com.cloudservice.gateway_service.security;
import java.util.HashMap;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
@Slf4j
@Component
public class CustomLoginFailureHandler implements ServerAuthenticationFailureHandler {
@Override
public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
response.setStatusCode(HttpStatus.FORBIDDEN);
response.getHeaders().add("Content-Type", "application/json; charset=UTF-8");
log.info("user login fail: {}", webFilterExchange.getExchange().getRequest().getPath());
HashMap<String, String> map = new HashMap<String, String>();
map.put("code", "-1001");
if (exception instanceof LockedException) {
map.put("message", "账户被锁定,请联系管理员!");
} else if (exception instanceof BadCredentialsException) {
map.put("message", "用户名或者密码输入错误,请重新输入!");
} else {
map.put("message", exception.getMessage());
}
DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONBytes(map));
return response.writeWith(Mono.just(dataBuffer));
}
}
2.4.3 自定义因未登录而访问未授权路径的回调ServerAuthenticationEntryPoint
通过自定义未登录而访问路径的错误回调 ,可以返回vue3需要的json格式,也可以强制跳转到登录页面,此处实现是强制跳转页面:
package com.cloudservice.gateway_service.security;
import java.net.URI;
import java.util.HashMap;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
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 com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
@Slf4j
@Component
public class CustomNoLoginHandler implements ServerAuthenticationEntryPoint {
@Override
public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException ex) {
return Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.FOUND);
response.getHeaders().add("Content-Type", "application/json; charset=UTF-8");
// 强制跳转到登录页面,在vue3中一般是返回json数据,并交由vue3来跳转
response.getHeaders().setLocation(URI.create("/login"));
log.info("url when no login: {}", exchange.getRequest().getPath());
HashMap<String, Object> map = new HashMap<>();
map.put("code", HttpStatus.FOUND.value());
map.put("message", "暂未登录,请您先进行登录");
DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONBytes(map));
response.writeWith(Mono.just(dataBuffer));
});
}
}
【注意】加了此自定义回调时,不能正常访问security默认定义的login页面,需要自定义登录页面,本人简单的实现示例:
此示例的controller使用到thymeleaf的模板,因此需要在pom.xml中引用依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
创建一个用来接收GET方式的login请求的controller:
package com.cloudservice.gateway_service.security;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class CustomLoginControl {
@GetMapping("/login")
public String login() {
return "login";
}
}
在src/main/resources/templates/目录下定义登录页面模板login.html
<html lang="en"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Please sign in</title>
</head>
<body>
<div class="container">
<form method="post" action="/login">
<h2>Please sign in</h2>
<p>
<label>Username</label>
<input type="text" id="username" name="username" placeholder="Username" required="" autofocus="">
</p>
<p>
<label>Password</label>
<input type="password" id="password" name="password" placeholder="Password" required="">
</p>
<button type="submit">Sign in</button>
</form>
</div>
</body>
</html>
2.4.4 自定义访问未授权资源路径的回调ServerAccessDeniedHandler
当访问资源路径进行权限验证时,ReactiveAuthorizationManager验证不通过,则会回调此类的接口,可以自定义返回的数据格式,代码如下:
package com.cloudservice.gateway_service.security;
import java.util.HashMap;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
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 com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
@Slf4j
@Component
public class CustomUrlNoRightHandler implements ServerAccessDeniedHandler{
@Override
public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.FORBIDDEN);
response.getHeaders().add("Content-Type", "application/json; charset=UTF-8");
log.info("url no right: {}", exchange.getRequest().getPath());
HashMap<String, String> map = new HashMap<>();
map.put("code", "-1002");
map.put("message", "资源路径无访问权限!");
DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONBytes(map));
return response.writeWith(Mono.just(dataBuffer));
}
}
2.4.5 自定义成功退出登录的回调ServerLogoutSuccessHandler
用于自定义退出登录是的返回数据格式,示例代码如下:
package com.cloudservice.gateway_service.security;
import java.util.HashMap;
import org.springframework.core.io.buffer.DataBuffer;
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.JSON;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
@Slf4j
@Component
public class CustomLogoutSuccessHandler implements ServerLogoutSuccessHandler {@Override
public Mono<Void> onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) {
ServerHttpResponse response = exchange.getExchange().getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().add("Content-Type", "application/json; charset=UTF-8");
log.info("user logout success: {}", authentication.getName());
HashMap<String, String> map = new HashMap<>();
map.put("code", "0");
map.put("message", "退出登录成功!");
DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONBytes(map));
return response.writeWith(Mono.just(dataBuffer));
}
}
2.4.6 最终配置类引用上述自定义回调的修改
package com.cloudservice.gateway_service.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
@Configuration
@EnableWebFluxSecurity
public class GatewaySecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
private GatewayReactiveAuthorizationManager gatewayReactiveAuthorizationManager;
@Autowired
private CustomLoginFailureHandler customLoginFailureHandler;
@Autowired
private CustomLoginSuccessHandler customLoginSuccessHandler;
@Autowired
private CustomNoLoginHandler customNoLoginHandler;
@Autowired
private CustomLogoutSuccessHandler customLogoutSuccessHandler;
@Autowired
private CustomUrlNoRightHandler customUrlNoRightHandler;
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.pathMatchers("/favicon.ico", "/login", "/logout").permitAll()
.anyExchange().access(gatewayReactiveAuthorizationManager)
.and().formLogin()
.authenticationFailureHandler(customLoginFailureHandler)
.authenticationSuccessHandler(customLoginSuccessHandler)
.and().exceptionHandling()
.accessDeniedHandler(customUrlNoRightHandler)
.authenticationEntryPoint(customNoLoginHandler)
.and().logout().logoutSuccessHandler(customLogoutSuccessHandler)
.and().csrf().disable();
return http.build();
}
}
【注意】
1. 登录会话的超时配置,可以修改nacos配置,加上:
server:
reactive:
session:
timeout: 1m # session超时时间为1分钟, 默认是60分钟
2. 为了防止集中登录导致的登录数据库过载,修改GatewayReactiveUserDetailsService类的findByUsername接口实现,引入redis,优先查看redis缓存里有没有用户信息,有则从redis中加载,否则才从mysql数据库中加载
[上一篇]从零开始搭建高负载java架构(04)——gateway网关节点(动态路由)
[下一篇]从零开始搭建高负载java架构(06):gateway网关节点(sentinel篇)
参考资料: