這段時間在學習搭建基于spring boot的spring oauth2 和jwt整合,
說實話挺折騰的,使用jwt做用戶鑒權,難點在于token的重繪和注銷,
當然注銷的難度更大,網上的一些方案也沒有很出色的,這個功能基本讓我放棄了jwt(滑稽笑~),
所以今天我單純的先記錄jwt token的重繪,
Token重繪
jwt token重繪方案可以分為兩種:一種是校驗token前重繪,第二種是校驗失敗后重繪,
校驗后重繪
我們先來說說第二種方案
驗證失效后,Oauth2框架會把例外資訊發送到OAuth2AuthenticationEntryPoint類里處理,這時候我們可以在這里做jwt token重繪并跳轉,
網上大部分方案也是這種:失效后,使用refresh_token獲取新的access_token,并將新的access_token設定到response.header然后跳轉,前端接收并無感更新新的access_token,
這里就不多做描述,可以參考這兩篇:
https://www.cnblogs.com/xuchao0506/p/13073913.html
https://blog.csdn.net/m0_37834471/article/details/83213002
校驗前重繪
接著說第一種,其實兩種方案的代碼我都寫過,最終使用了第一種,原因是兼容其他token重繪方案,
我在使用第二種方案并且jwt token重繪功能正常使用后,想換一種token方案做兼容,
切換成memory token的時候,發現OAuth2AuthenticationEntryPoint里面拿不到舊的token資訊導致重繪失敗,
我們翻一下原始碼
DefaultTokenServices.java
public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException,
InvalidTokenException {
OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
if (accessToken == null) {
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
}
else if (accessToken.isExpired()) {
// 失效后accessToken即被洗掉
tokenStore.removeAccessToken(accessToken);
throw new InvalidTokenException("Access token expired: " + accessTokenValue);
}
// 忽略部分代碼
return result;
}
可以看到JwtTokenStore的removeAccessToken:它是一個空方法,什么也沒做,所以我們在OAuth2AuthenticationEntryPoint依然能拿到舊的token并作處理,

但是其他的token策略在token過期后,被remove掉了,一點資訊都沒留下,巧婦難為無米之炊,所以,我之后選擇選擇了第一種方案,在token校驗remove前做重繪處理,
jwt token重繪的方案是這樣的:
客戶端發送請求大部分只攜帶access_token,并不攜帶refresh_token、client_id及client_secret等資訊,所以我是先把refresh_token、client_id等資訊放到access_token里面,
因為jwt并不具有續期的功能,所以在判斷token過期后,立刻使用refresh_token重繪,并且在response的header里面添加標識告訴前端你的token實際上已經過期了需要更新,
當然,其他的類似memory token、redis token可以延期的,更新策略就沒這么復雜:直接延長過期時間并且不需要更新token,
說了這么多,放token重繪相關代碼:
首先,我們需要把refresh_token、client_id、client_secret放入到access_token中,以便重繪,所以我們需要重寫JwtAccessTokenConverter的enhance方法,
OauthJwtAccessTokenConverter.java
public class OauthJwtAccessTokenConverter extends JwtAccessTokenConverter {
private JsonParser objectMapper = JsonParserFactory.create();
public OauthJwtAccessTokenConverter(SecurityUserService userService) {
// 使用SecurityContextHolder.getContext().getAuthentication()能獲取到User資訊
super.setAccessTokenConverter(new OauthAccessTokenConverter(userService));
}
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken(accessToken);
Map<String, Object> info = new LinkedHashMap<String, Object>(accessToken.getAdditionalInformation());
String tokenId = result.getValue();
if (!info.containsKey(TOKEN_ID)) {
info.put(TOKEN_ID, tokenId);
} else {
tokenId = (String) info.get(TOKEN_ID);
}
// access_token 包含自動重繪過期token需要的資料(client_id/secret/refresh_token)
Map<String, Object> details = (Map<String, Object>) authentication.getUserAuthentication().getDetails();
if (!Objects.isNull(details) && details.size() > 0) {
info.put(OauthConstant.OAUTH_CLIENT_ID,
details.getOrDefault("client_id", details.get(OauthConstant.OAUTH_CLIENT_ID)));
info.put(OauthConstant.OAUTH_CLIENT_SECRET,
details.getOrDefault("client_secret", details.get(OauthConstant.OAUTH_CLIENT_SECRET)));
}
OAuth2RefreshToken refreshToken = result.getRefreshToken();
if (refreshToken != null) {
DefaultOAuth2AccessToken encodedRefreshToken = new DefaultOAuth2AccessToken(accessToken);
encodedRefreshToken.setValue(refreshToken.getValue());
// Refresh tokens do not expire unless explicitly of the right type
encodedRefreshToken.setExpiration(null);
try {
Map<String, Object> claims = objectMapper
.parseMap(JwtHelper.decode(refreshToken.getValue()).getClaims());
if (claims.containsKey(TOKEN_ID)) {
encodedRefreshToken.setValue(claims.get(TOKEN_ID).toString());
}
} catch (IllegalArgumentException e) {
}
Map<String, Object> refreshTokenInfo = new LinkedHashMap<String, Object>(
accessToken.getAdditionalInformation());
refreshTokenInfo.put(TOKEN_ID, encodedRefreshToken.getValue());
// refresh token包含client id/secret, 自動重繪過期token時用到,
if (!Objects.isNull(details) && details.size() > 0) {
refreshTokenInfo.put(OauthConstant.OAUTH_CLIENT_ID,
details.getOrDefault("client_id", details.get(OauthConstant.OAUTH_CLIENT_ID)));
refreshTokenInfo.put(OauthConstant.OAUTH_CLIENT_SECRET,
details.getOrDefault("client_secret", details.get(OauthConstant.OAUTH_CLIENT_SECRET)));
}
refreshTokenInfo.put(ACCESS_TOKEN_ID, tokenId);
encodedRefreshToken.setAdditionalInformation(refreshTokenInfo);
DefaultOAuth2RefreshToken token = new DefaultOAuth2RefreshToken(
encode(encodedRefreshToken, authentication));
if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
Date expiration = ((ExpiringOAuth2RefreshToken) refreshToken).getExpiration();
encodedRefreshToken.setExpiration(expiration);
token = new DefaultExpiringOAuth2RefreshToken(encode(encodedRefreshToken, authentication), expiration);
}
result.setRefreshToken(token);
info.put(OauthConstant.OAUTH_REFRESH_TOKEN, token.getValue());
}
result.setAdditionalInformation(info);
result.setValue(encode(result, authentication));
return result;
}
}
呼叫oauth/token介面回傳資訊如下:

資訊準備好了,就要開始處理重繪,就是改寫DefaultTokenServices的loadAuthentication方法,
OauthTokenServices.java
public class OauthTokenServices extends DefaultTokenServices {
private static final Logger logger = LoggerFactory.getLogger(OauthTokenServices.class);
private TokenStore tokenStore;
// 自定義的token重繪處理器
private TokenRefreshExecutor executor;
public OauthTokenServices(TokenStore tokenStore, TokenRefreshExecutor executor) {
super.setTokenStore(tokenStore);
this.tokenStore = tokenStore;
this.executor = executor;
}
@Override
public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException, InvalidTokenException {
OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
executor.setAccessToken(accessToken);
// 是否重繪token
if (executor.shouldRefresh()) {
try {
logger.info("refresh token.");
String newAccessTokenValue = https://www.cnblogs.com/braska/p/executor.refresh();
// token如果是續期不做remove操作,如果是重新生成則洗掉舊的token
if (!newAccessTokenValue.equals(accessTokenValue)) {
tokenStore.removeAccessToken(accessToken);
}
accessTokenValue = newAccessTokenValue;
} catch (Exception e) {
logger.error("token refresh failed.", e);
}
}
return super.loadAuthentication(accessTokenValue);
}
}
類里面的TokenRefreshExecutor就是我們的重點,這個類定義了兩個比較重要的介面,
shouldRefresh:是否需要重繪
refresh:重繪
TokenRefreshExecutor.java
public interface TokenRefreshExecutor {
/**
* 執行重繪
* @return
* @throws Exception
*/
String refresh() throws Exception;
/**
* 是否需要重繪
* @return
*/
boolean shouldRefresh();
void setTokenStore(TokenStore tokenStore);
void setAccessToken(OAuth2AccessToken accessToken);
void setClientService(ClientDetailsService clientService);
}
然后我們來看看OauthJwtTokenRefreshExecutor.java
public class OauthJwtTokenRefreshExecutor extends AbstractTokenRefreshExecutor {
private static final Logger logger = LoggerFactory.getLogger(OauthJwtTokenRefreshExecutor.class);
@Override
public boolean shouldRefresh() {
// 舊token過期才重繪
return getAccessToken() != null && getAccessToken().isExpired();
}
@Override
public String refresh() throws Exception{
HttpServletRequest request = ServletUtil.getRequest();
HttpServletResponse response = ServletUtil.getResponse();
MultiValueMap<String, Object> parameters = new LinkedMultiValueMap<>();
// OauthJwtAccessTokenConverter中存入access_token中的資料,在這里使用
parameters.add("client_id", TokenUtil.getStringInfo(getAccessToken(), OauthConstant.OAUTH_CLIENT_ID));
parameters.add("client_secret", TokenUtil.getStringInfo(getAccessToken(), OauthConstant.OAUTH_CLIENT_SECRET));
parameters.add("refresh_token", TokenUtil.getStringInfo(getAccessToken(), OauthConstant.OAUTH_REFRESH_TOKEN));
parameters.add("grant_type", "refresh_token");
// 發送重繪的http請求
Map result = RestfulUtil.post(getOauthTokenUrl(request), parameters);
if (Objects.isNull(result) || result.size() <= 0 || !result.containsKey("access_token")) {
throw new IllegalStateException("refresh token failed.");
}
String accessToken = result.get("access_token").toString();
OAuth2AccessToken oAuth2AccessToken = getTokenStore().readAccessToken(accessToken);
OAuth2Authentication auth2Authentication = getTokenStore().readAuthentication(oAuth2AccessToken);
// 保存授權資訊,以便全域呼叫
SecurityContextHolder.getContext().setAuthentication(auth2Authentication);
// 前端收到該event事件時,更新access_token
response.setHeader("event", "token-refreshed");
response.setHeader("access_token", accessToken);
// 回傳新的token資訊
return accessToken;
}
private String getOauthTokenUrl(HttpServletRequest request) {
return String.format("%s://%s:%s%s%s",
request.getScheme(),
request.getLocalAddr(),
request.getLocalPort(),
Strings.isNotBlank(request.getContextPath()) ? "/" + request.getContextPath() : "",
"/oauth/token");
}
}
類寫完了,開始使用,
@Configuration
public class TokenConfig {
@Bean
public TokenStore tokenStore(AccessTokenConverter converter) {
return new JwtTokenStore((JwtAccessTokenConverter) converter);
// return new InMemoryTokenStore();
}
@Bean
public AccessTokenConverter accessTokenConverter(SecurityUserService userService) {
JwtAccessTokenConverter accessTokenConverter = new OauthJwtAccessTokenConverter(userService);
accessTokenConverter.setSigningKey("sign_key");
return accessTokenConverter;
/*DefaultAccessTokenConverter converter = new DefaultAccessTokenConverter();
DefaultUserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();
userTokenConverter.setUserDetailsService(userService);
converter.setUserTokenConverter(userTokenConverter);
return converter;*/
}
@Bean
public TokenRefreshExecutor tokenRefreshExecutor(TokenStore tokenStore,
ClientDetailsService clientService) {
TokenRefreshExecutor executor = new OauthJwtTokenRefreshExecutor();
// TokenRefreshExecutor executor = new OauthTokenRefreshExecutor();
executor.setTokenStore(tokenStore);
executor.setClientService(clientService);
return executor;
}
@Bean
public AuthorizationServerTokenServices tokenServices(TokenStore tokenstore,
AccessTokenConverter accessTokenConverter,
ClientDetailsService clientService,
TokenRefreshExecutor executor) {
OauthTokenServices tokenServices = new OauthTokenServices(tokenstore, executor);
// 非jwtConverter可注釋setTokenEnhancer
tokenServices.setTokenEnhancer((TokenEnhancer) accessTokenConverter);
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(clientService);
tokenServices.setReuseRefreshToken(true);
return tokenServices;
}
}
然后是認證服務器相關代碼
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager manager;
@Autowired
private SecurityUserService userService;
@Autowired
private TokenStore tokenStore;
@Autowired
private AccessTokenConverter tokenConverter;
@Autowired
private AuthorizationServerTokenServices tokenServices;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore)
.authenticationManager(manager)
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
.userDetailsService(userService)
.accessTokenConverter(tokenConverter)
.tokenServices(tokenServices);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()") //url:/oauth/token_key,exposes public key for token verification if using JWT tokens
.checkTokenAccess("isAuthenticated()") //url:/oauth/check_token allow check token
.allowFormAuthenticationForClients();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService());
}
public ClientDetailsService clientDetailsService() {
return new OauthClientService();
}
}
前端處理
前端用的axios,
service.interceptors.response.use(res => {
// 快取自動重繪生成的新token
if (res.headers['event'] && "token-refreshed" === res.headers['event']) {
setToken(res.headers['access_token'])
store.commit('SET_TOKEN', res.headers['access_token'])
}
// 忽略部分代碼
}
這樣就做到了jwt無感重繪,
講完了jwt的token重繪,多嘴說說memory token的重繪,
上面講了,memory token重繪策略比較簡單,每次請求過來直接給token延期即可,
OauthTokenRefreshExecutor.java
public class OauthTokenRefreshExecutor extends AbstractTokenRefreshExecutor {
private int accessTokenValiditySeconds = 60 * 60 * 12;
@Override
public boolean shouldRefresh() {
// 與jwt不同,因為每次請求都需要延長token失效時間,所以這里是token未過期時就需要重繪
return getAccessToken() != null && !getAccessToken().isExpired();
}
@Override
public String refresh() {
int seconds;
if (getAccessToken() instanceof DefaultOAuth2AccessToken) {
// 獲取client中的過期時間, 沒有則默認12小時
if (getClientService() != null) {
OAuth2Authentication auth2Authentication = getTokenStore().readAuthentication(getAccessToken());
String clientId = auth2Authentication.getOAuth2Request().getClientId();
ClientDetails client = getClientService().loadClientByClientId(clientId);
seconds = client.getAccessTokenValiditySeconds();
} else {
seconds = accessTokenValiditySeconds;
}
// 只修改token失效時間
((DefaultOAuth2AccessToken) getAccessToken()).setExpiration(new Date(System.currentTimeMillis() + (seconds * 1000l)));
}
// 回傳的還是舊的token
return getAccessToken().getValue();
}
}
然后修改TokenConfig相關bean注冊即可,
好了,Token重繪這塊差不多就這樣了,Token注銷暫時沒有好的思路,
如果Token重繪有更好的方案可以告知,也歡迎分享Token注銷方案,
2020/09/11
webflux版本
上面的demo是springmvc版本的,今天來個spring-cloud-gateway webflux版本的主動重繪,
首先定義一個ReactiveTokenRefresher介面,跟上面的refresh介面差不多一樣,就是refresh方法的回傳值變成了Mono<String>
public interface ReactiveTokenRefresher {
Mono<String> refresh();
boolean shouldRefresh();
}
AbstractReactiveTokenRefresher類
public abstract class AbstractReactiveTokenRefresher implements ReactiveTokenRefresher {
private OAuth2AccessToken accessToken;
private TokenStore tokenStore;
private ClientDetailsService clientDetailsService;
@Override
public Mono<String> refresh() {
Preconditions.checkNotNull(accessToken != null, "accessToken must be specify.");
return doRefresh();
}
public abstract Mono<String> doRefresh();
@Override
public boolean shouldRefresh() {
return false;
}
// fixme 省略getter/setter
}
然后就是實作類JwtReactiveTokenRefresher
public class JwtReactiveTokenRefresher extends AbstractReactiveTokenRefresher {
@Override
public Mono<String> doRefresh() {
return ReactiveXsoftContextHolder.get(ServerWebExchange.class).flatMap(exchange -> {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
Map<String, Object> parameters = new LinkedHashMap<>();
// OauthJwtAccessTokenConverter中存入access_token中的資料,在這里使用
parameters.put("client_id", TokenUtil.decryptInfo(getAccessToken(), OauthConstant.OAUTH_CLIENT_ID));
parameters.put("client_secret", TokenUtil.decryptInfo(getAccessToken(), OauthConstant.OAUTH_CLIENT_SECRET));
parameters.put("refresh_token", TokenUtil.decryptInfo(getAccessToken(), OauthConstant.OAUTH_REFRESH_TOKEN));
parameters.put("grant_type", "refresh_token");
// 發送重繪的http請求
Map<String, Object> result = null;
try {
result = HttpUtil.post(getOauthTokenUrl(request), parameters);
if (Objects.isNull(result) || result.size() <= 0 || !result.containsKey("access_token")) {
return Mono.error(new BadCredentialsException("token invalidate."));
}
String accessToken = result.get("access_token").toString();
OAuth2AccessToken oAuth2AccessToken = getTokenStore().readAccessToken(accessToken);
OAuth2Authentication auth2Authentication = getTokenStore().readAuthentication(oAuth2AccessToken);
// 保存授權資訊,以便全域呼叫
SecurityContextHolder.getContext().setAuthentication(auth2Authentication);
// 前端收到該event事件時,更新access_token
HttpHeaders headers = response.getHeaders();
headers.put("event", Lists.newArrayList("token-refreshed"));
headers.put("access_token", Lists.newArrayList(accessToken));
return Mono.just(accessToken);
} catch (IOException e) {
// fixme review了一下代碼,這里要改成Mono.error(),懶得重新復制黏貼代碼,,,
return Mono.error(new RuntimeException("error to refresh access token"));
}
});
}
@Override
public boolean shouldRefresh() {
// 舊token過期才重繪
return getAccessToken() != null && getAccessToken().isExpired();
}
private String getOauthTokenUrl(ServerHttpRequest request) {
return String.format("%s://%s:%s%s%s",
request.getURI().getScheme(),
request.getURI().getHost(),
request.getURI().getPort(),
!Strings.isNullOrEmpty(request.getPath().contextPath().value()) ? "/" + request.getPath().contextPath().value() : "",
"/oauth/token");
}
}
可以看出核心代碼基本都沒變化,所以就不多做描述,這里注意一下ReactiveXSoftContextHolder這個類:自定義快取背景關系中的ServerWebExchange,因為webflux是異步web框架,所以我們無法通過RequestContextHolder.getRequestAttributes()獲取到背景關系中的req和resp, 但是webflux提供了強大的Mono.subscriberContext()方法來保存當前執行緒的背景關系資訊,結合ServerWebExchange類,獲取req和resp就不是問題了,
ReactiveXSoftContextHolder類
public class ReactiveXSoftContextHolder {
public static <T> Context withXSoftContext(Class<T> clazz, Mono<T> exchange) {
return Context.of(clazz, exchange);
}
public static <T> Mono<T> get(Class<T> clazz) {
return Mono.subscriberContext()
.filter(c -> c.hasKey(clazz))
.flatMap(c -> c.<Mono<T>>get(clazz));
}
}
基礎類代碼都放的差不多了,接下來講怎么呼叫refresher和contextHolder,
首先講contextHolder,比較簡單,改寫AuthenticationWebFilter的filter方法,
public class XSoftAuthenticationWebFilter extends AuthenticationWebFilter {
public XSoftAuthenticationWebFilter(ReactiveAuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return super.filter(exchange, chain).subscriberContext( // 這里保存背景關系中的exchange
ReactiveXSoftContextHolder.withXSoftContext(ServerWebExchange.class, Mono.just(exchange)));
}
}
contextHolder就完事兒了,接下來是refresher,webflux版的security是在ReactiveAuthenticationManager里面做token校驗,所以我們改一下這個類,
XSoftReactiveAuthenticationManager代碼如下
public class XSoftReactiveAuthenticationManager implements ReactiveAuthenticationManager {
private static final Logger logger = LoggerFactory.getLogger(XSoftReactiveAuthenticationManager.class);
private TokenStore tokenStore;
// 自定義的token重繪處理器
private JwtReactiveTokenRefresher executor;
public XSoftReactiveAuthenticationManager(TokenStore tokenStore) {
this.tokenStore = tokenStore;
}
public void setExecutor(JwtReactiveTokenRefresher executor) {
this.executor = executor;
}
@Override
public Mono<Authentication> authenticate(Authentication authentication) {
return Mono.justOrEmpty(authentication)
.filter(a -> a instanceof BearerTokenAuthenticationToken)
.cast(BearerTokenAuthenticationToken.class)
.map(BearerTokenAuthenticationToken::getToken)
.flatMap(this::checkAccessToken)
.cast(Authentication.class);
}
private Mono<OAuth2Authentication> checkAccessToken(final String accessToken) {
OAuth2AccessToken oAuth2AccessToken = this.tokenStore.readAccessToken(accessToken);
if (executor != null) {
executor.setAccessToken(oAuth2AccessToken);
if (executor.shouldRefresh()) {
try {
logger.info("refresh token.");
return executor.refresh().map(token -> {
if (!token.equals(accessToken)) {
tokenStore.removeAccessToken(oAuth2AccessToken);
}
return token;
}).flatMap(token -> {
OAuth2Authentication oAuth2Authentication = this.tokenStore.readAuthentication(token);
if (oAuth2Authentication == null) {
return Mono.error(new BadCredentialsException("Token 無效!"));
} else {
return Mono.just(oAuth2Authentication);
}
});
} catch (Exception e) {
logger.error("token refresh failed.", e);
return Mono.error(new AccountExpiredException("Token 失效!"));
}
}
} else {
if (oAuth2AccessToken == null) {
return Mono.error(new BadCredentialsException("Token 無效!"));
} else if (oAuth2AccessToken.isExpired()) {
return Mono.error(new AccountExpiredException("Token 失效!"));
}
}
OAuth2Authentication oAuth2Authentication = this.tokenStore.readAuthentication(accessToken);
if (oAuth2Authentication == null) {
return Mono.error(new BadCredentialsException("Token 無效!"));
} else {
return Mono.just(oAuth2Authentication);
}
}
}
注冊manager
@Configuration
public class TokenConfiguration {
@Autowired
private AuthenticateRouteService routeService;
@Bean
public TokenStore tokenStore(AccessTokenConverter converter) {
return new JwtTokenStore((JwtAccessTokenConverter) converter);
}
@Bean
public AccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new OauthJwtAccessTokenConverter(securityUserService());
accessTokenConverter.setSigningKey("sign_key");
return accessTokenConverter;
}
@Bean
public XSoftReactiveAuthorizationManager xSoftReactiveAuthorizationManager() {
XSoftReactiveAuthorizationManager manager = new XSoftReactiveAuthorizationManager();
manager.setAuthenticateRouteService(routeService);
return manager;
}
@Bean
public UserDetailsService securityUserService() {
return new SecurityUserService();
}
}
最后就是webflux security的config類了
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class AuthorizationConfiguration {
@Autowired
private TokenStore tokenStore;
@Autowired
private XSoftReactiveAuthorizationManager authorizationManager;
@Bean
public SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) throws Exception {
http
.httpBasic().disable()
.csrf().disable()
.authorizeExchange()
.pathMatchers(HttpMethod.OPTIONS).permitAll()
.anyExchange().access(authorizationManager)
.and()
.exceptionHandling().authenticationEntryPoint(xSoftServerAuthenticationEntryPoint())
.and()
// 跨域過濾器
.addFilterAt(corsFilter(), SecurityWebFiltersOrder.CORS)
//oauth2認證過濾器
.addFilterAt(authenticationWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION);
return http.build();
}
@Bean
public XSoftServerAuthenticationEntryPoint xSoftServerAuthenticationEntryPoint() {
return new XSoftServerAuthenticationEntryPoint();
}
private AuthenticationWebFilter authenticationWebFilter() {
XSoftReactiveAuthenticationManager tokenAuthenticationManager = new XSoftReactiveAuthenticationManager(tokenStore);
JwtReactiveTokenRefresher executor = new JwtReactiveTokenRefresher();
// executor.setClientService();
executor.setTokenStore(tokenStore);
tokenAuthenticationManager.setExecutor(executor);
XSoftAuthenticationWebFilter authenticationWebFilter = new XSoftAuthenticationWebFilter(tokenAuthenticationManager);
authenticationWebFilter.setServerAuthenticationConverter(new ServerBearerTokenAuthenticationConverter());
authenticationWebFilter.setAuthenticationFailureHandler(
new ServerAuthenticationEntryPointFailureHandler(xSoftServerAuthenticationEntryPoint()));
return authenticationWebFilter;
}
/**
* 跨域配置
*/
public WebFilter corsFilter() {
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
if (CorsUtils.isCorsRequest(request)) {
HttpHeaders requestHeaders = request.getHeaders();
ServerHttpResponse response = ctx.getResponse();
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
if (requestMethod != null) {
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
}
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "18000L");
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(ctx);
};
}
}
spring-cloud-gateway的security主動重繪過期token就好了,
如圖,token過期后繼續請求介面,后臺主動重繪后并往response headers里面寫event事件,
gitee地址
https://gitee.com/xshower/blyat-xsoft ,springmvc的security核心代碼都在gravel-kernel模塊, webflux的security核心代碼都在gateway里面,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/137118.html
標籤:Java
