需求:
希望用户的token失效不是由登录后开始计时,而是在用户超时未请求后失效(不使用refresh_token模式),也就是要在用户每次请求后去重置token的有效期。
实现方案:网关gateway实现
ResourceServerConfiguration.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.authentication.ServerAuthenticationEntryPointFailureHandler;
/**
* 资源服务器配置
* @Author 陈奇
**/
@Configuration
public class ResourceServerConfiguration {
// 网关授权相关配置 根据自身项目调整
@Autowired
private SecurityProperties securityProperties;
@Autowired
private TokenStore tokenStore;
// 网关url鉴权实现类 extends AbstractDefaultPermissionServiceImpl implements ReactiveAuthorizationManager<AuthorizationContext>
@Autowired
private PermissionAuthManagerAbstract permissionAuthManager;
@RefreshScope
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
//认证处理器
ReactiveAuthenticationManager customAuthenticationManager = new CustomAuthenticationManager(tokenStore);
JsonAuthenticationEntryPoint entryPoint = new JsonAuthenticationEntryPoint();
//token转换器
ServerBearerTokenAuthenticationConverter tokenAuthenticationConverter = new ServerBearerTokenAuthenticationConverter();
tokenAuthenticationConverter.setAllowUriQueryParameter(true);
//oauth2认证过滤器
AuthenticationWebFilter oauth2Filter = new AuthenticationWebFilter(customAuthenticationManager);
oauth2Filter.setServerAuthenticationConverter(tokenAuthenticationConverter);
oauth2Filter.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(entryPoint));
oauth2Filter.setAuthenticationSuccessHandler(new Oauth2AuthSuccessHandler());
http.addFilterAt(oauth2Filter, SecurityWebFiltersOrder.AUTHENTICATION);
ServerHttpSecurity.AuthorizeExchangeSpec authorizeExchange = http.authorizeExchange();
if (securityProperties.getAuth().getHttpUrls().length > 0) {
authorizeExchange.pathMatchers(securityProperties.getAuth().getHttpUrls()).authenticated();
}
if (securityProperties.getIgnore().getUrls().length > 0) {
authorizeExchange.pathMatchers(securityProperties.getIgnore().getUrls()).permitAll();
}
authorizeExchange
.pathMatchers(HttpMethod.OPTIONS).permitAll()
.anyExchange()
.access(permissionAuthManager)
.and()
.exceptionHandling()
.accessDeniedHandler(new JsonAccessDeniedHandler())
.authenticationEntryPoint(entryPoint)
.and()
.headers()
.frameOptions()
.disable()
.and()
.httpBasic().disable()
.csrf().disable();
return http.build();
}
}
CustomAuthenticationManager.java
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import reactor.core.publisher.Mono;
import java.util.Date;
/**
* 请求token鉴权
* @Author 陈奇
**/
public class CustomAuthenticationManager implements ReactiveAuthenticationManager {
// 这个可以动态获取
public static final long TOKEN_RENEW_TIME = 60 * 60 * 1000L;
private TokenStore tokenStore;
public CustomAuthenticationManager(TokenStore tokenStore) {
this.tokenStore = tokenStore;
}
@Override
public Mono<Authentication> authenticate(Authentication authentication) {
return Mono.justOrEmpty(authentication)
.filter(a -> a instanceof BearerTokenAuthenticationToken)
.cast(BearerTokenAuthenticationToken.class)
.map(BearerTokenAuthenticationToken::getToken)
.flatMap((accessTokenValue -> {
OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
StringBuilder msg = new StringBuilder();
String cnColon = ":";
if (accessToken == null) {
msg.append(ConfigConstants.INVALID_ACCESS_TOKEN).append(cnColon).append(accessTokenValue);
return Mono.error(new InvalidTokenException(msg.toString()));
} else if (accessToken.isExpired()) {
tokenStore.removeAccessToken(accessToken);
msg.append(ConfigConstants.ACCESS_TOKEN_EXPIRED).append(cnColon).append(accessTokenValue);
return Mono.error(new InvalidTokenException(msg.toString()));
}
OAuth2Authentication result = tokenStore.readAuthentication(accessToken);
if (result == null) {
msg.append(ConfigConstants.INVALID_ACCESS_TOKEN).append(cnColon).append(accessTokenValue);
return Mono.error(new InvalidTokenException(msg.toString()));
}
// 重置token有效期
DefaultOAuth2AccessToken oAuth2AccessToken = (DefaultOAuth2AccessToken) accessToken;
oAuth2AccessToken.setExpiration(new Date(System.currentTimeMillis() + TOKEN_RENEW_TIME));
tokenStore.storeAccessToken(accessToken, result);
return Mono.just(result);
}))
.cast(Authentication.class);
}
}