Merge branch 'master' into dev

This commit is contained in:
haoxr 2020-11-25 10:18:52 +08:00
commit efd0fa482a
23 changed files with 242 additions and 181 deletions

View File

@ -37,7 +37,7 @@
<dependency>
<groupId>com.youlai</groupId>
<artifactId>common-database</artifactId>
<artifactId>common-mybatis</artifactId>
<version>${youlai.version}</version>
</dependency>

View File

@ -30,7 +30,7 @@
<dependency>
<groupId>com.youlai</groupId>
<artifactId>common-database</artifactId>
<artifactId>common-mybatis</artifactId>
<version>${youlai.version}</version>
</dependency>

View File

@ -1,14 +1,21 @@
package com.youlai.auth.config;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import com.youlai.auth.domain.User;
import com.youlai.auth.filter.CustomClientCredentialsTokenEndpointFilter;
import com.youlai.auth.service.JdbcClientDetailsServiceImpl;
import com.youlai.auth.service.UserDetailsServiceImpl;
import com.youlai.common.core.constant.AuthConstants;
import com.youlai.common.core.result.Result;
import com.youlai.common.core.result.ResultCode;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
@ -20,6 +27,7 @@ import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.sql.DataSource;
import java.security.KeyPair;
@ -63,22 +71,48 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
tokenEnhancers.add(jwtAccessTokenConverter());
tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);
endpoints.authenticationManager(authenticationManager)
endpoints
.authenticationManager(authenticationManager)
.accessTokenConverter(jwtAccessTokenConverter())
.tokenEnhancer(tokenEnhancerChain)
.userDetailsService(userDetailsService)
// refresh token有两种使用方式重复使用(true)非重复使用(false)默认为true
// 1 重复使用access token过期刷新时 refresh token过期时间未改变仍以初次生成的时间为准
// 2 非重复使用access token过期刷新时 refresh token过期时间延续在refresh token有效期内刷新便永不失效达到无需再次登录的目的
.reuseRefreshTokens(false);
.reuseRefreshTokens(true);
}
/**
* 允许表单认证
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.allowFormAuthenticationForClients();
/*security.allowFormAuthenticationForClients();*/
CustomClientCredentialsTokenEndpointFilter endpointFilter = new CustomClientCredentialsTokenEndpointFilter(security);
endpointFilter.afterPropertiesSet();
endpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint());
security.addTokenEndpointAuthenticationFilter(endpointFilter);
security.authenticationEntryPoint(authenticationEntryPoint())
.tokenKeyAccess("isAuthenticated()")
.checkTokenAccess("permitAll()");
}
/**
* 自定义认证异常响应数据
* @return
*/
@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return (request, response, e) -> {
response.setStatus(HttpStatus.HTTP_OK);
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Cache-Control", "no-cache");
Result result = Result.custom(ResultCode.CLIENT_AUTHENTICATION_FAILED);
response.getWriter().print(JSONUtil.toJsonStr(result));
response.getWriter().flush();
};
}

View File

@ -1,5 +1,6 @@
package com.youlai.auth.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -12,20 +13,23 @@ import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
.and()
.authorizeRequests().antMatchers("/rsa/publicKey","/oauth/logout").permitAll().anyRequest().authenticated()
.and()
.csrf().disable();
// 登录失败处理handler返回一段json
http
.authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
.and()
.authorizeRequests().antMatchers("/oauth/public_key", "/oauth/logout").permitAll().anyRequest().authenticated()
.and()
.csrf().disable();
}
/**
* 如果不配置SpringBoot会自动配置一个AuthenticationManager,覆盖掉内存中的用户
* 如果不配置SpringBoot会自动配置一个AuthenticationManager,覆盖掉内存中的用户
*/
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
@ -33,7 +37,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
}
@Bean
public PasswordEncoder passwordEncoder() {
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}

View File

@ -4,20 +4,24 @@ import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@AllArgsConstructor
public class WxConfig {
private WxProperties properties;
@Value("${wx.miniapp.appid}")
private String appid;
@Value("${wx.miniapp.secret}")
private String secret;
@Bean
public WxMaConfig wxMaConfig() {
WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
config.setAppid(properties.getAppid());
config.setSecret(properties.getSecret());
config.setAppid(appid);
config.setSecret(secret);
return config;
}

View File

@ -1,22 +0,0 @@
package com.youlai.auth.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxProperties {
/**
* 设置微信小程序的appid
*/
private String appid;
/**
* 设置微信小程序的Secret
*/
private String secret;
}

View File

@ -6,6 +6,8 @@ import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.youlai.auth.domain.Oauth2Token;
import com.youlai.common.core.constant.AuthConstants;
import com.youlai.common.core.constant.Constants;
@ -20,6 +22,7 @@ import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.apache.logging.log4j.util.Strings;
import org.springframework.data.redis.core.RedisTemplate;
@ -31,7 +34,9 @@ import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;
import javax.servlet.http.HttpServletRequest;
import java.security.KeyPair;
import java.security.Principal;
import java.security.interfaces.RSAPublicKey;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@ -39,6 +44,7 @@ import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/oauth")
@AllArgsConstructor
@Slf4j
public class AuthController {
private TokenEndpoint tokenEndpoint;
@ -46,6 +52,7 @@ public class AuthController {
private WxMaService wxService;
private UmsMemberFeignClient umsMemberFeignClient;
private PasswordEncoder passwordEncoder;
private KeyPair keyPair;
@ApiOperation("OAuth2认证生成token")
@ -66,7 +73,6 @@ public class AuthController {
@ApiIgnore Principal principal,
@ApiIgnore @RequestParam Map<String, String> parameters
) throws HttpRequestMethodNotSupportedException, WxErrorException {
String clientId = parameters.get("client_id");
if (StrUtil.isBlank(clientId)) {
@ -74,8 +80,8 @@ public class AuthController {
}
// 微信小程序端认证处理
if(AuthConstants.WEAPP_CLIENT_ID.equals(clientId)){
return this.handleForWxAppAuth(principal,parameters);
if (AuthConstants.WEAPP_CLIENT_ID.equals(clientId)) {
return this.handleForWxAppAuth(principal, parameters);
}
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
@ -162,4 +168,11 @@ public class AuthController {
}
@GetMapping("/public_key")
public Map<String, Object> getKey() {
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAKey key = new RSAKey.Builder(publicKey).build();
return new JWKSet(key).toJSONObject();
}
}

View File

@ -1,29 +0,0 @@
package com.youlai.auth.controller;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.KeyPair;
import java.security.interfaces.RSAPublicKey;
import java.util.Map;
/**
* RSA公钥开放接口
*/
@RestController
@AllArgsConstructor
public class PublicKeyController {
private KeyPair keyPair;
@GetMapping("/rsa/publicKey")
public Map<String, Object> getKey() {
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAKey key = new RSAKey.Builder(publicKey).build();
return new JWKSet(key).toJSONObject();
}
}

View File

@ -0,0 +1,38 @@
package com.youlai.auth.exception;
import com.youlai.common.core.result.Result;
import com.youlai.common.core.result.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
@Slf4j
public class AuthExceptionHandler {
/**
* 用户名和密码异常
*
* @param e
* @return
*/
@ExceptionHandler(InvalidGrantException.class)
public Result handleInvalidGrantException(InvalidGrantException e) {
return Result.custom(ResultCode.USERNAME_OR_PASSWORD_ERROR);
}
/**
* 账户异常(禁用锁定过期)
*
* @param e
* @return
*/
@ExceptionHandler({InternalAuthenticationServiceException.class})
public Result handleInternalAuthenticationServiceException(InternalAuthenticationServiceException e) {
return Result.error(e.getMessage());
}
}

View File

@ -0,0 +1,37 @@
package com.youlai.auth.filter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter;
import org.springframework.security.web.AuthenticationEntryPoint;
/**
* 重写filter实现客户端自定义异常处理
*/
public class CustomClientCredentialsTokenEndpointFilter extends ClientCredentialsTokenEndpointFilter {
private AuthorizationServerSecurityConfigurer configurer;
private AuthenticationEntryPoint authenticationEntryPoint;
public CustomClientCredentialsTokenEndpointFilter(AuthorizationServerSecurityConfigurer configurer) {
this.configurer = configurer;
}
@Override
public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
super.setAuthenticationEntryPoint(null);
this.authenticationEntryPoint = authenticationEntryPoint;
}
@Override
protected AuthenticationManager getAuthenticationManager() {
return configurer.and().getSharedObject(AuthenticationManager.class);
}
@Override
public void afterPropertiesSet() {
setAuthenticationFailureHandler((request, response, e) -> authenticationEntryPoint.commence(request, response, e));
setAuthenticationSuccessHandler((request, response, authentication) -> {
});
}
}

View File

@ -39,7 +39,9 @@ public class UserDetailsServiceImpl implements UserDetailsService {
switch (clientId) {
case AuthConstants.ADMIN_CLIENT_ID: // 后台用户
Result<UserDTO> userResult = adminUserFeignClient.loadUserByUsername(username);
if (userResult == null || !ResultCode.SUCCESS.getCode().equals(userResult.getCode())) {
if (userResult == null || !ResultCode.SUCCESS.getCode().equals(userResult.getCode())
|| userResult.getData() == null
) {
throw new UsernameNotFoundException("用户不存在");
}
UserDTO userDTO = userResult.getData();
@ -48,7 +50,8 @@ public class UserDetailsServiceImpl implements UserDetailsService {
break;
case AuthConstants.WEAPP_CLIENT_ID: // 小程序会员
Result<MemberDTO> memberResult = umsMemberFeignClient.loadMemberByOpenid(username);
if (memberResult == null || !ResultCode.SUCCESS.getCode().equals(memberResult.getCode())) {
if (memberResult == null || !ResultCode.SUCCESS.getCode().equals(memberResult.getCode())
||memberResult.getData()==null) {
throw new UsernameNotFoundException("会员不存在");
}
MemberDTO memberDTO = memberResult.getData();
@ -62,12 +65,8 @@ public class UserDetailsServiceImpl implements UserDetailsService {
throw new LockedException("该账号已被锁定!");
} else if (!user.isAccountNonExpired()) {
throw new AccountExpiredException("该账号已过期!");
} else if (!user.isCredentialsNonExpired()) {
throw new CredentialsExpiredException("该账户的登录凭证已过期,请重新登录!");
}
return user;
}
}

View File

@ -59,7 +59,7 @@ public interface AuthConstants {
/**
* 有来商城后台管理客户端ID
*/
String ADMIN_CLIENT_ID="youlai-mall-admin";
String ADMIN_CLIENT_ID="youlai-admin";
/**

View File

@ -24,6 +24,7 @@ public enum ResultCode implements IResultCode, Serializable {
USERNAME_OR_PASSWORD_ERROR("A0210","用户名或密码错误"),
INPUT_PASSWORD_EXCEED_LIMIT("A0211","用户输入密码次数超限"),
CLIENT_AUTHENTICATION_FAILED("A0212","客户端认证失败"), // *
TOKEN_INVALID_OR_EXPIRED("A0230","token无效或已过期"),
USER_AUTHORIZED_ERROR ("A0300","访问权限异常"),

View File

@ -9,7 +9,7 @@
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common-database</artifactId>
<artifactId>common-mybatis</artifactId>
<dependencies>
<dependency>
@ -27,4 +27,4 @@
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
</project>

View File

@ -1,8 +1,8 @@
package com.youlai.common.database.config;
package com.youlai.common.mybatis.config;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.youlai.common.database.handler.MetaHandler;
import com.youlai.common.mybatis.handler.MetaHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@ -29,4 +29,5 @@ public class MybatisPlusConfig {
globalConfig.setMetaObjectHandler(new MetaHandler());
return globalConfig;
}
}

View File

@ -1,4 +1,4 @@
package com.youlai.common.database.handler;
package com.youlai.common.mybatis.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
@ -6,9 +6,6 @@ import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @author haoxr
**/
@Component
public class MetaHandler implements MetaObjectHandler {
@ -22,4 +19,5 @@ public class MetaHandler implements MetaObjectHandler {
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("gmtCreate", new Date(), metaObject);
}
}

View File

@ -1,2 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.youlai.common.database.config.MybatisPlusConfig
com.youlai.common.mybatis.config.MybatisPlusConfig

View File

@ -22,10 +22,10 @@
<version>${youlai.version}</version>
</dependency>
<!-- spring boot web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

View File

@ -18,7 +18,7 @@
<module>common-knife4j</module>
<module>common-redis</module>
<module>common-web</module>
<module>common-database</module>
<module>common-mybatis</module>
</modules>
<properties>
@ -118,7 +118,7 @@
<dependency>
<groupId>com.youlai</groupId>
<artifactId>common-database</artifactId>
<artifactId>common-mybatis</artifactId>
<version>${youlai.version}</version>
</dependency>

View File

@ -1,36 +0,0 @@
package com.youlai.gateway.component;
import cn.hutool.json.JSONUtil;
import com.youlai.common.core.result.Result;
import com.youlai.common.core.result.ResultCode;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
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 reactor.core.publisher.Mono;
import java.nio.charset.Charset;
/**
* 无权访问自定义响应
*/
@Component
public class CustomServerAccessDeniedHandler implements ServerAccessDeniedHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException e) {
ServerHttpResponse response=exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
response.getHeaders().set("Access-Control-Allow-Origin","*");
response.getHeaders().set("Cache-Control","no-cache");
String body= JSONUtil.toJsonStr(Result.custom(ResultCode.USER_ACCESS_UNAUTHORIZED));
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
return response.writeWith(Mono.just(buffer));
}
}

View File

@ -1,37 +0,0 @@
package com.youlai.gateway.component;
import cn.hutool.json.JSONUtil;
import com.youlai.common.core.result.Result;
import com.youlai.common.core.result.ResultCode;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
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 reactor.core.publisher.Mono;
import java.nio.charset.Charset;
/**
* 无效token/token过期 自定义响应
*/
@Component
public class CustomServerAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {
@Override
public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
response.getHeaders().set("Access-Control-Allow-Origin", "*");
response.getHeaders().set("Cache-Control", "no-cache");
String body = JSONUtil.toJsonStr(Result.custom(ResultCode.TOKEN_INVALID_OR_EXPIRED));
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
return response.writeWith(Mono.just(buffer));
}
}

View File

@ -1,25 +1,36 @@
package com.youlai.gateway.config;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.json.JSONUtil;
import com.youlai.common.core.constant.AuthConstants;
import com.youlai.gateway.component.AuthorizationManager;
import com.youlai.gateway.component.CustomServerAccessDeniedHandler;
import com.youlai.gateway.component.CustomServerAuthenticationEntryPoint;
import com.youlai.common.core.result.Result;
import com.youlai.common.core.result.ResultCode;
import com.youlai.gateway.security.AuthorizationManager;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import reactor.core.publisher.Mono;
import java.nio.charset.Charset;
/**
* 资源服务器配置
*/
@ -29,34 +40,79 @@ import reactor.core.publisher.Mono;
public class ResourceServerConfig {
private AuthorizationManager authorizationManager;
private CustomServerAccessDeniedHandler customServerAccessDeniedHandler;
private CustomServerAuthenticationEntryPoint customServerAuthenticationEntryPoint;
private WhiteListConfig whiteListConfig;
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http.oauth2ResourceServer().jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
// 自定义处理JWT请求头过期或签名错误的结果
http.oauth2ResourceServer().authenticationEntryPoint(customServerAuthenticationEntryPoint);
http.oauth2ResourceServer().authenticationEntryPoint(authenticationEntryPoint());
http.authorizeExchange()
.pathMatchers(ArrayUtil.toArray(whiteListConfig.getUrls(),String.class)).permitAll()
.pathMatchers(ArrayUtil.toArray(whiteListConfig.getUrls(), String.class)).permitAll()
.anyExchange().access(authorizationManager)
.and()
.exceptionHandling()
.accessDeniedHandler(customServerAccessDeniedHandler) // 处理未授权
.authenticationEntryPoint(customServerAuthenticationEntryPoint) //处理未认证
.accessDeniedHandler(accessDeniedHandler()) // 处理未授权
.authenticationEntryPoint(authenticationEntryPoint()) //处理未认证
.and().csrf().disable();
return http.build();
}
/**
* 未授权
*
* @return
*/
@Bean
ServerAccessDeniedHandler accessDeniedHandler() {
return (exchange, denied) -> {
Mono<Void> mono = Mono.defer(() -> Mono.just(exchange.getResponse()))
.flatMap(response -> {
response.setStatusCode(HttpStatus.OK);
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
response.getHeaders().set("Access-Control-Allow-Origin", "*");
response.getHeaders().set("Cache-Control", "no-cache");
String body = JSONUtil.toJsonStr(Result.custom(ResultCode.USER_ACCESS_UNAUTHORIZED));
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
return response.writeWith(Mono.just(buffer))
.doOnError(error -> DataBufferUtils.release(buffer));
});
return mono;
};
}
/**
* token无效或者已过期自定义响应
*/
@Bean
ServerAuthenticationEntryPoint authenticationEntryPoint() {
return (exchange, e) -> {
Mono<Void> mono = Mono.defer(() -> Mono.just(exchange.getResponse()))
.flatMap(response -> {
response.setStatusCode(HttpStatus.OK);
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
response.getHeaders().set("Access-Control-Allow-Origin", "*");
response.getHeaders().set("Cache-Control", "no-cache");
String body = JSONUtil.toJsonStr(Result.custom(ResultCode.TOKEN_INVALID_OR_EXPIRED));
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
return response.writeWith(Mono.just(buffer))
.doOnError(error -> DataBufferUtils.release(buffer));
});
return mono;
};
}
/**
* @return
* @link https://blog.csdn.net/qq_24230139/article/details/105091273
* ServerHttpSecurity没有将jwt中authorities的负载部分当做Authentication
* 需要把jwt的Claim中的authorities加入
* 方案重新定义ReactiveAuthenticationManager权限管理器默认转换器JwtGrantedAuthoritiesConverter
* @return
*/
@Bean
public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
@ -68,5 +124,4 @@ public class ResourceServerConfig {
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
}
}

View File

@ -1,4 +1,4 @@
package com.youlai.gateway.component;
package com.youlai.gateway.security;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;