SpringCloudAlibaba篇(八)SpringCloudGateWay聚合swagger3、SpringBoot2.6.X整合swagger3+knife4j
文章目录
前言
通常微服务的认证和授权思路有两种:
- 网关只负责转发请求,认证鉴权交给每个微服务控制
- 统一在网关层面认证鉴权,微服务只负责业务
第二种方案的流程图
采用技术栈
父工程依赖及统一版本
附:父工程依赖
<packaging>pom</packaging><properties><fate.project.version>1.0.0</fate.project.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version><maven.plugin.version>3.8.1</maven.plugin.version><spring.boot.version>2.6.3</spring.boot.version><spring-cloud.version>2021.0.1</spring-cloud.version><spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version><alibaba.nacos.version>1.4.2</alibaba.nacos.version><alibaba.sentinel.version>1.8.3</alibaba.sentinel.version><alibaba.dubbo.version>2.7.15</alibaba.dubbo.version><alibaba.rocketMq.version>4.9.2</alibaba.rocketMq.version><alibaba.seata.version>1.4.2</alibaba.seata.version><mybatis.plus.version>3.5.1</mybatis.plus.version><knife4j.version>3.0.2</knife4j.version><swagger.version>3.0.0</swagger.version></properties><dependencyManagement><dependencies><!-- springBoot --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring.boot.version}</version><type>pom</type><scope>import</scope></dependency><!-- springCloud --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version></dependency></dependencies>
1. 搭建Oauth2-server
1.1 oauth2-server 依赖
引用版本可查看上面的父工程依赖
<dependencies><dependency><groupId>top.fate</groupId><artifactId>fate-common</artifactId><version>1.0.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><!--排除logback--><exclusions><exclusion><artifactId>spring-boot-starter-logging</artifactId><groupId>org.springframework.boot</groupId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!--添加log4j2--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></dependency><!--SpringBoot2.4.x之后默认不加载bootstrap.yml文件,需要在pom里加上依赖--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId></dependency><!-- Nacos --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><exclusions><exclusion><groupId>com.alibaba.nacos</groupId><artifactId>nacos-client</artifactId></exclusion></exclusions><version>${spring-cloud-alibaba.version}</version></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><exclusions><exclusion><groupId>com.alibaba.nacos</groupId><artifactId>nacos-client</artifactId></exclusion></exclusions><version>${spring-cloud-alibaba.version}</version></dependency><dependency><groupId>com.alibaba.nacos</groupId><artifactId>nacos-client</artifactId><version>${alibaba.nacos.version}</version></dependency><!-- Druid --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.22</version></dependency><!-- MySQL --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.16</version></dependency><!-- MyBatis-Plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis.plus.version}</version></dependency><!-- zipkin --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-zipkin</artifactId><version>2.2.8.RELEASE</version></dependency><!-- redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>com.nimbusds</groupId><artifactId>nimbus-jose-jwt</artifactId><version>9.9.3</version></dependency><!--security --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId><version>2.2.5.RELEASE</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.0</version></dependency></dependencies>
1.2 bootstrap.yml
server:port:9999url:nacos: localhost:8848spring:application:name: oauth2-server #实例名profiles:active: dev
cloud:nacos:discovery:#集群环境隔离cluster-name: shanghai
#命名空间namespace: ${spring.profiles.active}#持久化实例 ture为临时实例 false为持久化实例 临时实例发生异常直接剔除, 而持久化实例等待恢复ephemeral:true#注册中心地址server-addr: ${url.nacos}config:namespace: ${spring.profiles.active}file-extension: yaml
#配置中心地址server-addr: ${url.nacos}
extension-configs[0]:data-id: mysql-oauth2.yaml
group: DEFAULT_GROUP
refresh:false
extension-configs[1]:data-id: log.properties
group: DEFAULT_GROUP
refresh:false
extension-configs[2]:data-id: zipkin.yaml
group: DEFAULT_GROUP
refresh:false
extension-configs[3]:data-id: mybatis-plus.yaml
group: DEFAULT_GROUP
refresh:false
extension-configs[4]:data-id: redis.yaml
group: DEFAULT_GROUP
refresh:false
mysql-oauth2.yaml
spring:datasource:url: jdbc:mysql://localhost:3306/oauth?useSSL=false&allowPublicKeyRetrieval=trueusername: root
password:123456driver-class-name: com.mysql.cj.jdbc.Driver
log.properties
logging.level.root=info
zipkin.yaml
spring:zipkin:base-url: http://127.0.0.1:9411sender:type: web
sleuth:sampler:probability:1.0
mybatis-plus.yaml
mybatis-plus:mapper-locations: classpath:mapper/*/*.xml,mapper/*.xmlglobal-config:db-config:id-type: auto
field-strategy: NOT_EMPTY
db-type: MYSQL
configuration:map-underscore-to-camel-case:truecall-setters-on-nulls:true#log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
redis.yaml
spring:redis:host: localhost
port:6379
1.3 keytool生成RSA证书
在jdk/bin目录下执行该命令,生成jks文件之后复制到项目中resources中
keytool -genkey -alias jwt -keyalg RSA -keystore jwt.jks -keypass 123456
1.4 SysUserServiceImpl 用户信息实现类
实现Spring Security的UserDetailsService接口,用于加载用户信息
packagetop.fate.service.impl;importcn.hutool.core.util.ArrayUtil;importcom.baomidou.mybatisplus.extension.service.impl.ServiceImpl;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.core.authority.AuthorityUtils;importorg.springframework.security.core.userdetails.UserDetails;importorg.springframework.security.core.userdetails.UserDetailsService;importorg.springframework.security.core.userdetails.UsernameNotFoundException;importorg.springframework.stereotype.Service;importtop.fate.domain.SecurityUser;importjava.util.ArrayList;importjava.util.List;importjava.util.Objects;/**
* <p>
* 用户信息表 服务实现类
* </p>
*
* @author fate急速出击
* @since 2022-05-13
*/@ServicepublicclassSysUserServiceImplimplementsUserDetailsService{@OverridepublicUserDetailsloadUserByUsername(String username)throwsUsernameNotFoundException{returnSecurityUser.builder().userId(UUID.randomUUID().toString().replaceAll("-","")).username("admin").password(newBCryptPasswordEncoder().encode("123456")).authorities(AuthorityUtils.createAuthorityList("user","admin")).build();}}
SecurityUser 用户封装类
packagetop.fate.domain;importlombok.AllArgsConstructor;importlombok.Builder;importlombok.Data;importlombok.NoArgsConstructor;importorg.springframework.security.core.GrantedAuthority;importorg.springframework.security.core.userdetails.UserDetails;importjava.util.Collection;/**
* 存储用户的详细信息,实现UserDetails,后续有定制的字段可以自己拓展
* @auther:Wangxl
* @Emile:[email protected]
* @Time:2022/5/13 11:36
*/@Data@Builder@AllArgsConstructor@NoArgsConstructorpublicclassSecurityUserimplementsUserDetails{privateString userId;//用户名privateString username;//密码privateString password;//权限+角色集合privateCollection<?extendsGrantedAuthority> authorities;@OverridepublicCollection<?extendsGrantedAuthority>getAuthorities(){return authorities;}@OverridepublicStringgetPassword(){return password;}@OverridepublicStringgetUsername(){return username;}// 账户是否未过期@OverridepublicbooleanisAccountNonExpired(){returntrue;}// 账户是否未被锁@OverridepublicbooleanisAccountNonLocked(){returntrue;}@OverridepublicbooleanisCredentialsNonExpired(){returntrue;}@OverridepublicbooleanisEnabled(){returntrue;}}
## 1.5 JWT内容增强器
```java
package top.fate.component;
import org.s
pringframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.stereotype.Component;
import top.fate.domain.SecurityUser;
import java.util.HashMap;
import java.util.Map;
/**
* JWT内容增强器
* @auther:Wangxl
* @Emile:[email protected]
* @Time:2022/5/13 11:32
*/
@Component
public class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
Map<String, Object> info = new HashMap<>();
//把用户ID设置到JWT中
info.put("id", securityUser.getUserId());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
}
1.6 Oauth2ServerConfig 认证服务器配置
加载用户信息的服务UserServiceImpl及RSA的钥匙对KeyPair
packagetop.fate.config;importlombok.AllArgsConstructor;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.core.io.ClassPathResource;importorg.springframework.security.authentication.AuthenticationManager;importorg.springframework.security.crypto.password.PasswordEncoder;importorg.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;importorg.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;importorg.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;importorg.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;importorg.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;importorg.springframework.security.oauth2.provider.token.TokenEnhancer;importorg.springframework.security.oauth2.provider.token.TokenEnhancerChain;importorg.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;importorg.springframework.security.rsa.crypto.KeyStoreKeyFactory;importtop.fate.component.JwtTokenEnhancer;importtop.fate.service.impl.SysUserServiceImpl;importjava.security.KeyPair;importjava.util.ArrayList;importjava.util.List;/**
* 认证服务器配置
* @auther:Wangxl
* @Emile:[email protected]
* @Time:2022/5/13 11:36
*/@AllArgsConstructor@Configuration@EnableAuthorizationServerpublicclassOauth2ServerConfigextendsAuthorizationServerConfigurerAdapter{privatefinalPasswordEncoder passwordEncoder;privatefinalSysUserServiceImpl userDetailsService;privatefinalAuthenticationManager authenticationManager;privatefinalJwtTokenEnhancer jwtTokenEnhancer;@Overridepublicvoidconfigure(ClientDetailsServiceConfigurer clients)throwsException{
clients.inMemory().withClient("client-app").secret(passwordEncoder.encode("123456")).scopes("all").authorizedGrantTypes("password","refresh_token").accessTokenValiditySeconds(3600).refreshTokenValiditySeconds(86400);}@Overridepublicvoidconfigure(AuthorizationServerEndpointsConfigurer endpoints)throwsException{TokenEnhancerChain enhancerChain =newTokenEnhancerChain();List<TokenEnhancer> delegates =newArrayList<>();
delegates.add(jwtTokenEnhancer);
delegates.add(accessTokenConverter());
enhancerChain.setTokenEnhancers(delegates);//配置JWT的内容增强器
endpoints.authenticationManager(authenticationManager).userDetailsService(userDetailsService)//配置加载用户信息的服务.accessTokenConverter(accessTokenConverter()).tokenEnhancer(enhancerChain);}@Overridepublicvoidconfigure(AuthorizationServerSecurityConfigurer security)throwsException{
security.allowFormAuthenticationForClients();}@BeanpublicJwtAccessTokenConverteraccessTokenConverter(){JwtAccessTokenConverter jwtAccessTokenConverter =newJwtAccessTokenConverter();
jwtAccessTokenConverter.setKeyPair(keyPair());return jwtAccessTokenConverter;}@BeanpublicKeyPairkeyPair(){//从classpath下的证书中获取秘钥对KeyStoreKeyFactory keyStoreKeyFactory =newKeyStoreKeyFactory(newClassPathResource("jwt.jks"),"123456".toCharArray());return keyStoreKeyFactory.getKeyPair("jwt","123456".toCharArray());}}
1.7 获取RSA公钥接口
packagetop.fate.controller;importcom.nimbusds.jose.jwk.JWKSet;importcom.nimbusds.jose.jwk.RSAKey;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjava.security.KeyPair;importjava.security.interfaces.RSAPublicKey;importjava.util.Map;/**
* @auther:Wangxl
* @Emile:[email protected]
* @Time:2022/5/13 17:48
*/@RestController@RequestMapping(value ="rsa")publicclassKeyPairController{@AutowiredprivateKeyPair keyPair;@GetMapping(value ="publicKey")publicMap<String,Object>getKey(){RSAPublicKey aPublic =(RSAPublicKey) keyPair.getPublic();RSAKey key =newRSAKey.Builder(aPublic).build();returnnewJWKSet(key).toJSONObject();}}
1.8 配置Spring Security,允许获取公钥接口的访问
packagetop.fate.config;importorg.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.authentication.AuthenticationManager;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.EnableWebSecurity;importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;importorg.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;importorg.springframework.security.crypto.password.PasswordEncoder;/**
* SpringSecurity配置
* @auther:Wangxl
* @Emile:[email protected]
* @Time:2022/5/14 15:36
*/@Configuration@EnableWebSecuritypublicclassWebSecurityConfigextendsWebSecurityConfigurerAdapter{@Overrideprotectedvoidconfigure(HttpSecurity http)throwsException{
http.authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll().antMatchers("/rsa/publicKey").permitAll().anyRequest().authenticated();}@Bean@OverridepublicAuthenticationManagerauthenticationManagerBean()throwsException{returnsuper.authenticationManagerBean();}@BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}}
1.9 初始化用户权限demo
packagetop.fate.service.impl;importcn.hutool.core.collection.CollUtil;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.stereotype.Service;importtop.fate.model.SysConstant;importjavax.annotation.PostConstruct;importjavax.annotation.Resource;importjava.util.List;importjava.util.Map;importjava.util.TreeMap;/**
* @auther:Wangxl
* @Emile:[email protected]
* @Time:2022/5/14 10:33
*/@ServicepublicclassResourceServiceImpl{privateMap<String,List<String>> resourceRolesMap;@ResourceprivateRedisTemplate<String,Object> redisTemplate;@PostConstructpublicvoidinitData(){
resourceRolesMap =newTreeMap<>();
resourceRolesMap.put("/user/tb-user/list",CollUtil.toList("ADMIN"));
resourceRolesMap.put("/order/order/getUserService",CollUtil.toList("ADMIN","ROOT"));
redisTemplate.opsForHash().putAll("oauth2:oauth_urls", resourceRolesMap);}}
1.10 Redis相关配置
packagetop.fate.config;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.data.redis.connection.RedisConnectionFactory;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.data.redis.repository.configuration.EnableRedisRepositories;importorg.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;importorg.springframework.data.redis.serializer.StringRedisSerializer;/**
* Redis相关配置
* @auther:Wangxl
* @Emile:[email protected]
* @Time:2022/5/16 14:29
*/@Configuration@EnableRedisRepositoriespublicclassRedisRepositoryConfig{@BeanpublicRedisTemplate<String,Object>redisTemplate(RedisConnectionFactory connectionFactory){RedisTemplate<String,Object> redisTemplate =newRedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);StringRedisSerializer stringRedisSerializer =newStringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer =newJackson2JsonRedisSerializer<>(Object.class);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();return redisTemplate;}}
2. gateway
2.1 网关依赖
<dependencies><!--网关依赖gateway--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><!-- Sentinel --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId><version>${spring-cloud-alibaba.version}</version></dependency><!-- Nacos --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><exclusions><exclusion><groupId>com.alibaba.nacos</groupId><artifactId>nacos-client</artifactId></exclusion></exclusions><version>${spring-cloud-alibaba.version}</version></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><exclusions><exclusion><groupId>com.alibaba.nacos</groupId><artifactId>nacos-client</artifactId></exclusion></exclusions><version>${spring-cloud-alibaba.version}</version></dependency><dependency><groupId>com.alibaba.nacos</groupId><artifactId>nacos-client</artifactId><version>${alibaba.nacos.version}</version></dependency><!--SpringBoot2.4.x之后默认不加载bootstrap.yml文件,需要在pom里加上依赖--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId></dependency><!-- 加入 log4j2 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId><exclusions><exclusion><groupId>*</groupId><artifactId>*</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></dependency><!-- zipkin --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-zipkin</artifactId><version>2.2.8.RELEASE</version></dependency><dependency><groupId>io.zipkin.brave</groupId><artifactId>brave-instrumentation-dubbo</artifactId></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>3.0.3</version></dependency><!-- redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- oauth2 --><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-resource-server</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-client</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-jose</artifactId></dependency><dependency><groupId>com.nimbusds</groupId><artifactId>nimbus-jose-jwt</artifactId><version>9.9.3</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.0</version></dependency><dependency><groupId>top.fate</groupId><artifactId>fate-common</artifactId><version>1.0.0</version></dependency></dependencies>
2.2 applicaion.yaml
spring:security:oauth2:resourceserver:jwt:jwk-set-uri:'http://localhost:9999/rsa/publicKey'secure:ignore:urls:#配置白名单路径-"/actuator/**"-"/oauth2-server/oauth/token"-"/oauth2-server/**"-"/order/**"
2.3 bootstrap.yaml
logging:file:# 配置日志的路径,包含 spring.application.namepath: ${spring.application.name}url:nacos: localhost:8848spring:application:name: gateway #实例名profiles:active: dev
cloud:nacos:discovery:#集群环境隔离cluster-name: shanghai
#命名空间namespace: ${spring.profiles.active}#持久化实例 ture为临时实例 false为持久化实例 临时实例发生异常直接剔除, 而持久化实例等待恢复ephemeral:true#注册中心地址server-addr: ${url.nacos}config:namespace: ${spring.profiles.active}file-extension: yaml
#配置中心地址server-addr: ${url.nacos}
extension-configs[0]:data-id: gateway.yaml
group: DEFAULT_GROUP
refresh:false
extension-configs[1]:data-id: sentinel.yaml
group: DEFAULT_GROUP
refresh:false
extension-configs[2]:data-id: log.properties
group: DEFAULT_GROUP
refresh:false
extension-configs[3]:data-id: redis.yaml
group: DEFAULT_GROUP
refresh:false
gateway.yaml
server:port:30001spring:cloud:gateway:enabled:truediscovery:locator:lower-case-service-id:trueroutes:-id: user-service
uri: lb://user-service
predicates:- Path=/user/**filters:- StripPrefix=1
-id: order-service
uri: lb://order-service
predicates:- Path=/order/**filters:- StripPrefix=1
-id:uri: lb://oauth2-server
predicates:- Path=/oauth2-server/**filters:- StripPrefix=1
sentinel.yaml
spring:cloud:sentinel:transport:dashboard: localhost:8080
log.properties
logging.level.root=info
redis.yaml
spring:redis:host: localhost
port:6379
2.4 鉴权管理器
packagetop.fate.authorization;importcn.hutool.core.convert.Convert;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.security.authorization.AuthorizationDecision;importorg.springframework.security.authorization.ReactiveAuthorizationManager;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.GrantedAuthority;importorg.springframework.security.web.server.authorization.AuthorizationContext;importorg.springframework.stereotype.Component;importreactor.core.publisher.Mono;importjavax.annotation.Resource;importjava.net.URI;importjava.util.List;importjava.util.stream.Collectors;/**
* 鉴权管理器,用于判断是否有资源的访问权限
* @auther:Wangxl
* @Emile:[email protected]
* @Time:2022/5/7 9:27
*/@ComponentpublicclassAuthorizationManagerimplementsReactiveAuthorizationManager<AuthorizationContext>{@ResourceprivateRedisTemplate<String,Object> redisTemplate;@OverridepublicMono<AuthorizationDecision>check(Mono<Authentication> mono,AuthorizationContext authorizationContext){//从Redis中获取当前路径可访问角色列表URI uri = authorizationContext.getExchange().getRequest().getURI();Object obj = redisTemplate.opsForHash().get("oauth2:oauth_urls", uri.getPath());List<String> authorities =Convert.toList(String.class, obj);
authorities = authorities.stream().map(i -> i ="ROLE_"+ i).collect(Collectors.toList());//认证通过且角色匹配的用户可访问当前路径return mono
.filter(Authentication::isAuthenticated).flatMapIterable(Authentication::getAuthorities).map(GrantedAuthority::getAuthority).any(authorities::contains).map(AuthorizationDecision::new).defaultIfEmpty(newAuthorizationDecision(false));}}
2.5 自定义返回结果:没有登录或token过期时
packagetop.fate.component;importcn.hutool.json.JSONUtil;importorg.springframework.core.io.buffer.DataBuffer;importorg.springframework.http.HttpHeaders;importorg.springframework.http.HttpStatus;importorg.springframework.http.MediaType;importorg.springframework.http.server.reactive.ServerHttpResponse;importorg.springframework.security.core.AuthenticationException;importorg.springframework.security.web.server.ServerAuthenticationEntryPoint;importorg.springframework.stereotype.Component;importorg.springframework.web.server.ServerWebExchange;importreactor.core.publisher.Mono;importtop.fate.api.R;importtop.fate.api.ResultCode;importjava.nio.charset.Charset;/**
* 自定义返回结果:没有登录或token过期时
* @auther:Wangxl
* @Emile:[email protected]
* @Time:2022/5/15 19:37
*/@ComponentpublicclassRestAuthenticationEntryPointimplementsServerAuthenticationEntryPoint{@OverridepublicMono<Void>commence(ServerWebExchange exchange,AuthenticationException e){ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().add(HttpHeaders.CONTENT_TYPE,MediaType.APPLICATION_JSON_VALUE);String body=JSONUtil.toJsonStr(R.fail(ResultCode.UN_AUTHORIZED.getCode(),ResultCode.UN_AUTHORIZED.getMessage()));DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));return response.writeWith(Mono.just(buffer));}}
2.6 自定义返回结果:没有权限访问时
packagetop.fate.component;importcn.hutool.json.JSONUtil;importorg.springframework.core.io.buffer.DataBuffer;importorg.springframework.http.HttpHeaders;importorg.springframework.http.HttpStatus;importorg.springframework.http.MediaType;importorg.springframework.http.server.reactive.ServerHttpResponse;importorg.springframework.security.core.AuthenticationException;importorg.springframework.security.web.server.ServerAuthenticationEntryPoint;importorg.springframework.stereotype.Component;importorg.springframework.web.server.ServerWebExchange;importreactor.core.publisher.Mono;importtop.fate.api.R;importtop.fate.api.ResultCode;importjava.nio.charset.Charset;/**
* 自定义返回结果:没有登录或token过期时
* @auther:Wangxl
* @Emile:[email protected]
* @Time:2022/5/15 19:37
*/@ComponentpublicclassRestAuthenticationEntryPointimplementsServerAuthenticationEntryPoint{@OverridepublicMono<Void>commence(ServerWebExchange exchange,AuthenticationException e){ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().add(HttpHeaders.CONTENT_TYPE,MediaType.APPLICATION_JSON_VALUE);String body=JSONUtil.toJsonStr(R.fail(ResultCode.UN_AUTHORIZED.getCode(),ResultCode.UN_AUTHORIZED.getMessage()));DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));return response.writeWith(Mono.just(buffer));}}
2.7 网关白名单配置
packagetop.fate.config;importlombok.Data;importlombok.EqualsAndHashCode;importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.stereotype.Component;importjava.util.List;/**
* 网关白名单配置
* @auther:Wangxl
* @Emile:[email protected]
* @Time:2022/5/16 14:27
*/@Data@EqualsAndHashCode(callSuper =false)@Component@ConfigurationProperties(prefix="secure.ignore")publicclassIgnoreUrlsConfig{privateList<String> urls;}
2.8 Redis相关配置
packagetop.fate.config;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.data.redis.connection.RedisConnectionFactory;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.data.redis.repository.configuration.EnableRedisRepositories;importorg.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;importorg.springframework.data.redis.serializer.StringRedisSerializer;/**
* Redis相关配置
* @auther:Wangxl
* @Emile:[email protected]
* @Time:2022/5/16 14:29
*/@Configuration@EnableRedisRepositoriespublicclassRedisRepositoryConfig{@BeanpublicRedisTemplate<String,Object>redisTemplate(RedisConnectionFactory connectionFactory){RedisTemplate<String,Object> redisTemplate =newRedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);StringRedisSerializer stringRedisSerializer =newStringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer =newJackson2JsonRedisSerializer<>(Object.class);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();return redisTemplate;}}
2.9 资源服务器配置
packagetop.fate.config;importcn.hutool.core.util.ArrayUtil;importlombok.AllArgsConstructor;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.core.convert.converter.Converter;importorg.springframework.security.authentication.AbstractAuthenticationToken;importorg.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;importorg.springframework.security.config.web.server.SecurityWebFiltersOrder;importorg.springframework.security.config.web.server.ServerHttpSecurity;importorg.springframework.security.oauth2.jwt.Jwt;importorg.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;importorg.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;importorg.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;importorg.springframework.security.web.server.SecurityWebFilterChain;importreactor.core.publisher.Mono;importtop.fate.authorization.AuthorizationManager;importtop.fate.component.RestAuthenticationEntryPoint;importtop.fate.component.RestfulAccessDeniedHandler;importtop.fate.filter.IgnoreUrlsRemoveJwtFilter;/**
* 资源服务器配置
* @auther:Wangxl
* @Emile:[email protected]
* @Time:2022/5/16 14:31
*/@AllArgsConstructor@Configuration@EnableWebFluxSecuritypublicclassResourceServerConfig{privatefinalAuthorizationManager authorizationManager;privatefinalIgnoreUrlsConfig ignoreUrlsConfig;privatefinalRestfulAccessDeniedHandler restfulAccessDeniedHandler;privatefinalRestAuthenticationEntryPoint restAuthenticationEntryPoint;privatefinalIgnoreUrlsRemoveJwtFilter ignoreUrlsRemoveJwtFilter;@BeanpublicSecurityWebFilterChainspringSecurityFilterChain(ServerHttpSecurity http){
http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());//自定义处理JWT请求头过期或签名错误的结果
http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);//对白名单路径,直接移除JWT请求头
http.addFilterBefore(ignoreUrlsRemoveJwtFilter,SecurityWebFiltersOrder.AUTHENTICATION);
http.authorizeExchange().pathMatchers(ArrayUtil.toArray(ignoreUrlsConfig.getUrls(),String.class)).permitAll()//白名单配置.anyExchange().access(authorizationManager)//鉴权管理器配置.and().exceptionHandling().accessDeniedHandler(restfulAccessDeniedHandler)//处理未授权.authenticationEntryPoint(restAuthenticationEntryPoint)//处理未认证.and().csrf().disable();return http.build();}@BeanpublicConverter<Jwt,?extendsMono<?extendsAbstractAuthenticationToken>>jwtAuthenticationConverter(){JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter =newJwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");JwtAuthenticationConverter jwtAuthenticationConverter =newJwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);returnnewReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);}}
2.10 将登录用户的JWT转化成用户信息的全局过滤器
packagetop.fate.filter;importcn.hutool.core.util.StrUtil;importcom.nimbusds.jose.JWSObject;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.cloud.gateway.filter.GatewayFilterChain;importorg.springframework.cloud.gateway.filter.GlobalFilter;importorg.springframework.core.Ordered;importorg.springframework.http.server.reactive.ServerHttpRequest;importorg.springframework.stereotype.Component;importorg.springframework.web.server.ServerWebExchange;importreactor.core.publisher.Mono;importjava.text.ParseException;/**
* 将登录用户的JWT转化成用户信息的全局过滤器
* @auther:Wangxl
* @Emile:[email protected]
* @Time:2022/5/16 12:31
*/@ComponentpublicclassAuthGlobalFilterimplementsGlobalFilter,Ordered{privatestaticLogger LOGGER =LoggerFactory.getLogger(AuthGlobalFilter.class);@OverridepublicMono<Void>filter(ServerWebExchange exchange,GatewayFilterChain chain){String token = exchange.getRequest().getHeaders().getFirst("Authorization");if(StrUtil.isEmpty(token)){return chain.filter(exchange);}try{//从token中解析用户信息并设置到Header中去String realToken = token.replace("Bearer ","");JWSObject jwsObject =JWSObject.parse(realToken);String userStr = jwsObject.getPayload().toString();
LOGGER.info("AuthGlobalFilter.filter() user:{}",userStr);ServerHttpRequest request = exchange.getRequest().mutate().header("user", userStr).build();
exchange = exchange.mutate().request(request).build();}catch(ParseException e){
e.printStackTrace();}return chain.filter(exchange);}@OverridepublicintgetOrder(){return0;}}
2.11 白名单路径访问时需要移除JWT请求头
packagetop.fate.filter;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.http.server.reactive.ServerHttpRequest;importorg.springframework.stereotype.Component;importorg.springframework.util.AntPathMatcher;importorg.springframework.util.PathMatcher;importorg.springframework.web.server.ServerWebExchange;importorg.springframework.web.server.WebFilter;importorg.springframework.web.server.WebFilterChain;importreactor.core.publisher.Mono;importtop.fate.config.IgnoreUrlsConfig;importjava.net.URI;importjava.util.List;/**
* 白名单路径访问时需要移除JWT请求头
* @auther:Wangxl
* @Emile:[email protected]
* @Time:2022/5/16 16:42
*/@ComponentpublicclassIgnoreUrlsRemoveJwtFilterimplementsWebFilter{@AutowiredprivateIgnoreUrlsConfig ignoreUrlsConfig;@OverridepublicMono<Void>filter(ServerWebExchange exchange,WebFilterChain chain){ServerHttpRequest request = exchange.getRequest();URI uri = request.getURI();PathMatcher pathMatcher =newAntPathMatcher();//白名单路径移除JWT请求头List<String> ignoreUrls = ignoreUrlsConfig.getUrls();for(String ignoreUrl : ignoreUrls){if(pathMatcher.match(ignoreUrl, uri.getPath())){
request = exchange.getRequest().mutate().header("Authorization","").build();
exchange = exchange.mutate().request(request).build();return chain.filter(exchange);}}return chain.filter(exchange);}}
测试
访问 http://localhost:30001/oauth2-server/oauth/token
密码模式
刷新token
版权归原作者 fate急速出击 所有, 如有侵权,请联系我们删除。