0


SpringCloudAlibaba篇(九)SpringCloudGateWay整合Oauth2+Jwt实现认证中心

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
在这里插入图片描述


本文转载自: https://blog.csdn.net/weixin_43627706/article/details/124836962
版权归原作者 fate急速出击 所有, 如有侵权,请联系我们删除。

“SpringCloudAlibaba篇(九)SpringCloudGateWay整合Oauth2+Jwt实现认证中心”的评论:

还没有评论