spring-cloud-gateway-oauth2

前言

我们理想的微服务权限解决方案应该是这样的,认证服务负责认证,网关负责校验认证和鉴权,其他API服务负责处理自己的业务逻辑。安全相关的逻辑只存在于认证服务和网关服务中,其他服务只是单纯地提供服务而没有任何安全相关逻辑。

架构

通过认证服务(oauth2-auth)进行统一认证,然后通过网关(oauth2-gateway)来统一校验认证和鉴权。采用Nacos作为注册中心,Gateway作为网关,使用nimbus-jose-jwtJWT库操作JWT令牌。

  • oauth2-auth:Oauth2认证服务,负责对登录用户进行认证,整合Spring Security Oauth2
  • ouath2-gateway:网关服务,负责请求转发和鉴权功能,整合Spring Security Oauth2
  • oauth2-resource:受保护的API服务,用户鉴权通过后可以访问该服务,不整合Spring Security Oauth2

具体实现

一、认证服务oauth2-auth

1、首先来搭建认证服务,它将作为Oauth2的认证服务使用,并且网关服务的鉴权功能也需要依赖它,在pom.xml中添加相关依赖,主要是Spring Security、Oauth2、JWT、Redis相关依赖

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><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></dependency><dependency><groupId>com.nimbusds</groupId><artifactId>nimbus-jose-jwt</artifactId><version>8.16</version></dependency><!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
</dependencies>

2、在application.yml中添加相关配置,主要是Nacos和Redis相关配置

server:port: 9401
spring:profiles:active: devapplication:name: oauth2-authcloud:nacos:discovery:server-addr: localhost:8848jackson:date-format: yyyy-MM-dd HH:mm:ssredis:database: 0port: 6379host: localhostpassword:
management:endpoints:web:exposure:include: "*"

3、使用keytool生成RSA证书jwt.jks,复制到resource目录下,在JDK的bin目录下使用如下命令即可

keytool -genkey -alias jwt -keyalg RSA -keystore jwt.jks

4、创建UserServiceImpl类实现Spring Security的UserDetailsService接口,用于加载用户信息

package cn.gathub.auth.service.impl;import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;import javax.annotation.PostConstruct;import cn.gathub.auth.constant.MessageConstant;
import cn.gathub.auth.domain.entity.User;
import cn.gathub.auth.service.UserService;
import cn.gathub.auth.service.principal.UserPrincipal;
import cn.hutool.core.collection.CollUtil;/*** 用户管理业务类** @author Honghui [wanghonghui_work@163.com] 2021/3/16*/
@Service
public class UserServiceImpl implements UserService {private List<User> userList;private final PasswordEncoder passwordEncoder;public UserServiceImpl(PasswordEncoder passwordEncoder) {this.passwordEncoder = passwordEncoder;}@PostConstructpublic void initData() {String password = passwordEncoder.encode("123456");userList = new ArrayList<>();userList.add(new User(1L, "admin", password, 1, CollUtil.toList("ADMIN")));userList.add(new User(2L, "user", password, 1, CollUtil.toList("USER")));}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {List<User> findUserList = userList.stream().filter(item -> item.getUsername().equals(username)).collect(Collectors.toList());if (CollUtil.isEmpty(findUserList)) {throw new UsernameNotFoundException(MessageConstant.USERNAME_PASSWORD_ERROR);}UserPrincipal userPrincipal = new UserPrincipal(findUserList.get(0));if (!userPrincipal.isEnabled()) {throw new DisabledException(MessageConstant.ACCOUNT_DISABLED);} else if (!userPrincipal.isAccountNonLocked()) {throw new LockedException(MessageConstant.ACCOUNT_LOCKED);} else if (!userPrincipal.isAccountNonExpired()) {throw new AccountExpiredException(MessageConstant.ACCOUNT_EXPIRED);} else if (!userPrincipal.isCredentialsNonExpired()) {throw new CredentialsExpiredException(MessageConstant.CREDENTIALS_EXPIRED);}return userPrincipal;}}

5、创建ClientServiceImpl类实现Spring Security的ClientDetailsService接口,用于加载客户端信息

package cn.gathub.auth.service.impl;import org.springframework.http.HttpStatus;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;import javax.annotation.PostConstruct;import cn.gathub.auth.constant.MessageConstant;
import cn.gathub.auth.domain.entity.Client;
import cn.gathub.auth.service.ClientService;
import cn.gathub.auth.service.principal.ClientPrincipal;
import cn.hutool.core.collection.CollUtil;/*** 客户端管理业务类** @author Honghui [wanghonghui_work@163.com] 2021/3/18*/
@Service
public class ClientServiceImpl implements ClientService {private List<Client> clientList;private final PasswordEncoder passwordEncoder;public ClientServiceImpl(PasswordEncoder passwordEncoder) {this.passwordEncoder = passwordEncoder;}@PostConstructpublic void initData() {String clientSecret = passwordEncoder.encode("123456");clientList = new ArrayList<>();// 1、密码模式clientList.add(Client.builder().clientId("client-app").resourceIds("oauth2-resource").secretRequire(false).clientSecret(clientSecret).scopeRequire(false).scope("all").authorizedGrantTypes("password,refresh_token").authorities("ADMIN,USER").accessTokenValidity(3600).refreshTokenValidity(86400).build());// 2、授权码模式clientList.add(Client.builder().clientId("client-app-2").resourceIds("oauth2-resource2").secretRequire(false).clientSecret(clientSecret).scopeRequire(false).scope("all").authorizedGrantTypes("authorization_code,refresh_token").webServerRedirectUri("https://www.gathub.cn,https://www.baidu.com").authorities("USER").accessTokenValidity(3600).refreshTokenValidity(86400).build());}@Overridepublic ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {List<Client> findClientList = clientList.stream().filter(item -> item.getClientId().equals(clientId)).collect(Collectors.toList());if (CollUtil.isEmpty(findClientList)) {throw new ResponseStatusException(HttpStatus.NOT_FOUND, MessageConstant.NOT_FOUND_CLIENT);}return new ClientPrincipal(findClientList.get(0));}
}

6、添加认证服务相关配置Oauth2ServerConfig,需要配置加载用户信息的服务UserServiceImpl和加载客户端信息的服务ClientServiceImpl及RSA的钥匙对KeyPair

package cn.gathub.auth.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
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.rsa.crypto.KeyStoreKeyFactory;import java.security.KeyPair;
import java.util.ArrayList;
import java.util.List;import cn.gathub.auth.component.JwtTokenEnhancer;
import cn.gathub.auth.service.ClientService;
import cn.gathub.auth.service.UserService;
import lombok.AllArgsConstructor;/*** 认证服务器配置** @author Honghui [wanghonghui_work@163.com] 2021/3/16*/
@AllArgsConstructor
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {private final UserService userService;private final ClientService clientService;private final AuthenticationManager authenticationManager;private final JwtTokenEnhancer jwtTokenEnhancer;@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {//    clients.inMemory()
//        // 1、密码模式
//        .withClient("client-app")
//        .secret(passwordEncoder.encode("123456"))
//        .scopes("read,write")
//        .authorizedGrantTypes("password", "refresh_token")
//        .accessTokenValiditySeconds(3600)
//        .refreshTokenValiditySeconds(86400)
//        .and()
//        // 2、授权码授权
//        .withClient("client-app-2")
//        .secret(passwordEncoder.encode("123456"))
//        .scopes("read")
//        .authorizedGrantTypes("authorization_code", "refresh_token")
//        .accessTokenValiditySeconds(3600)
//        .refreshTokenValiditySeconds(86400)
//        .redirectUris("https://www.gathub.cn", "https://www.baidu.com");clients.withClientDetails(clientService);}@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {TokenEnhancerChain enhancerChain = new TokenEnhancerChain();List<TokenEnhancer> delegates = new ArrayList<>();delegates.add(jwtTokenEnhancer);delegates.add(accessTokenConverter());enhancerChain.setTokenEnhancers(delegates); //配置JWT的内容增强器endpoints.authenticationManager(authenticationManager).userDetailsService(userService) //配置加载用户信息的服务.accessTokenConverter(accessTokenConverter()).tokenEnhancer(enhancerChain);}@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) {security.allowFormAuthenticationForClients();}@Beanpublic JwtAccessTokenConverter accessTokenConverter() {JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();jwtAccessTokenConverter.setKeyPair(keyPair());return jwtAccessTokenConverter;}@Beanpublic KeyPair keyPair() {// 从classpath下的证书中获取秘钥对KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "654321".toCharArray());return keyStoreKeyFactory.getKeyPair("jwt", "654321".toCharArray());}}

7、如果你想往JWT中添加自定义信息的话,比如说登录用户的ID,可以自己实现TokenEnhancer接口

package cn.gathub.auth.component;import org.springframework.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 java.util.HashMap;
import java.util.Map;import cn.gathub.auth.service.principal.UserPrincipal;/*** JWT内容增强器** @author Honghui [wanghonghui_work@163.com] 2021/3/16*/
@Component
public class JwtTokenEnhancer implements TokenEnhancer {@Overridepublic OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();Map<String, Object> info = new HashMap<>();// 把用户ID设置到JWT中info.put("id", userPrincipal.getId());((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);return accessToken;}
}

8、由于我们的网关服务需要RSA的公钥来验证签名是否合法,所以认证服务需要有个接口把公钥暴露出来

package cn.gathub.auth.controller;import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;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公钥接口** @author Honghui [wanghonghui_work@163.com] 2021/3/16*/
@RestController
public class KeyPairController {private final KeyPair keyPair;public KeyPairController(KeyPair keyPair) {this.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();}}

9、还需要配置Spring Security,允许获取公钥接口的访问

package cn.gathub.auth.config;import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;/*** SpringSecurity配置** @author Honghui [wanghonghui_work@163.com] 2021/3/16*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll().antMatchers("/rsa/publicKey").permitAll().anyRequest().authenticated();}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}}

10、创建一个资源服务ResourceServiceImpl,初始化的时候把资源与角色匹配关系缓存到Redis中,方便网关服务进行鉴权的时候获取

package cn.gathub.auth.service;import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.Map;
import java.util.TreeMap;import javax.annotation.PostConstruct;import cn.gathub.auth.constant.RedisConstant;
import cn.hutool.core.collection.CollUtil;/*** 资源与角色匹配关系管理业务类** @author Honghui [wanghonghui_work@163.com] 2021/3/16*/
@Service
public class ResourceServiceImpl {private final RedisTemplate<String, Object> redisTemplate;public ResourceServiceImpl(RedisTemplate<String, Object> redisTemplate) {this.redisTemplate = redisTemplate;}@PostConstructpublic void initData() {Map<String, List<String>> resourceRolesMap = new TreeMap<>();resourceRolesMap.put("/resource/hello", CollUtil.toList("ADMIN"));resourceRolesMap.put("/resource/user/currentUser", CollUtil.toList("ADMIN", "USER"));redisTemplate.opsForHash().putAll(RedisConstant.RESOURCE_ROLES_MAP, resourceRolesMap);}
}

二、网关服务oauth2-gateway

接下来搭建网关服务,它将作为Oauth2的资源服务、客户端服务使用,对访问微服务的请求进行统一的校验认证和鉴权操作

1、在pom.xml中添加相关依赖,主要是Gateway、Oauth2和JWT相关依赖

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><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>8.16</version></dependency>
</dependencies>

2、在application.yml中添加相关配置,主要是路由规则的配置、Oauth2中RSA公钥的配置及路由白名单的配置

server:port: 9201
spring:profiles:active: devapplication:name: oauth2-gatewaycloud:nacos:discovery:server-addr: localhost:8848gateway:routes: # 配置路由路径- id: oauth2-resource-routeuri: lb://oauth2-resourcepredicates:- Path=/resource/**filters:- StripPrefix=1- id: oauth2-auth-routeuri: lb://oauth2-authpredicates:- Path=/auth/**filters:- StripPrefix=1- id: oauth2-auth-loginuri: lb://oauth2-authpredicates:- Path=/loginfilters:- PreserveHostHeader- id: oauth2-auth-tokenuri: lb://oauth2-authpredicates:- Path=/oauth/tokenfilters:- PreserveHostHeader- id: oauth2-auth-authorizeuri: lb://oauth2-authpredicates:- Path=/oauth/authorizefilters:- PreserveHostHeaderdiscovery:locator:enabled: true # 开启从注册中心动态创建路由的功能lower-case-service-id: true # 使用小写服务名,默认是大写security:oauth2:resourceserver:jwt:jwk-set-uri: 'http://localhost:9401/rsa/publicKey' # 配置RSA的公钥访问地址redis:database: 0port: 6379host: localhostpassword:
secure:ignore:urls: # 配置白名单路径- "/actuator/**"- "/oauth/token"- "/oauth/authorize"- "/login"

3、对网关服务进行配置安全配置,由于Gateway使用的是WebFlux,所以需要使用@EnableWebFluxSecurity注解开启

package cn.gathub.gateway.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
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 cn.gathub.gateway.authorization.AuthorizationManager;
import cn.gathub.gateway.component.RestAuthenticationEntryPoint;
import cn.gathub.gateway.component.RestfulAccessDeniedHandler;
import cn.gathub.gateway.constant.AuthConstant;
import cn.gathub.gateway.filter.IgnoreUrlsRemoveJwtFilter;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import reactor.core.publisher.Mono;/*** 资源服务器配置** @author Honghui [wanghonghui_work@163.com] 2021/3/16*/
@AllArgsConstructor
@Configuration
@EnableWebFluxSecurity
public class ResourceServerConfig {private final AuthorizationManager authorizationManager;private final IgnoreUrlsConfig ignoreUrlsConfig;private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;private final IgnoreUrlsRemoveJwtFilter ignoreUrlsRemoveJwtFilter;@Beanpublic SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());// 1、自定义处理JWT请求头过期或签名错误的结果http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);// 2、对白名单路径,直接移除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();}@Beanpublic Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX);jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstant.AUTHORITY_CLAIM_NAME);JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);}}

4、在WebFluxSecurity中自定义鉴权操作需要实现ReactiveAuthorizationManager接口

package cn.gathub.gateway.authorization;import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;import cn.gathub.gateway.constant.AuthConstant;
import cn.gathub.gateway.constant.RedisConstant;
import cn.hutool.core.convert.Convert;
import reactor.core.publisher.Mono;/*** 鉴权管理器,用于判断是否有资源的访问权限** @author Honghui [wanghonghui_work@163.com] 2021/3/16*/
@Component
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {private final RedisTemplate<String, Object> redisTemplate;public AuthorizationManager(RedisTemplate<String, Object> redisTemplate) {this.redisTemplate = redisTemplate;}@Overridepublic Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {// 1、从Redis中获取当前路径可访问角色列表URI uri = authorizationContext.getExchange().getRequest().getURI();Object obj = redisTemplate.opsForHash().get(RedisConstant.RESOURCE_ROLES_MAP, uri.getPath());List<String> authorities = Convert.toList(String.class, obj);authorities = authorities.stream().map(i -> i = AuthConstant.AUTHORITY_PREFIX + i).collect(Collectors.toList());// 2、认证通过且角色匹配的用户可访问当前路径return mono.filter(Authentication::isAuthenticated).flatMapIterable(Authentication::getAuthorities).map(GrantedAuthority::getAuthority).any(authorities::contains).map(AuthorizationDecision::new).defaultIfEmpty(new AuthorizationDecision(false));}}

5、这里我们还需要实现一个全局过滤器AuthGlobalFilter,当鉴权通过后将JWT令牌中的用户信息解析出来,然后存入请求的Header中,这样后续服务就不需要解析JWT令牌了,可以直接从请求的Header中获取到用户信息

package cn.gathub.gateway.filter;import com.nimbusds.jose.JWSObject;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;import java.text.ParseException;import cn.hutool.core.util.StrUtil;
import reactor.core.publisher.Mono;/*** 将登录用户的JWT转化成用户信息的全局过滤器** @author Honghui [wanghonghui_work@163.com] 2021/3/16*/
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {private final static Logger LOGGER = LoggerFactory.getLogger(AuthGlobalFilter.class);@Overridepublic Mono<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);}@Overridepublic int getOrder() {return 0;}
}

三、资源服务(API服务)oauth2-resource

最后我们搭建一个API服务,它不会集成和实现任何安全相关逻辑,全靠网关来保护它

1、在pom.xml中添加相关依赖,就添加了一个web依赖

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>

2、在application.yml添加相关配置,很常规的配置

server:port: 9501
spring:profiles:active: devapplication:name: oauth2-resourcecloud:nacos:discovery:server-addr: localhost:8848
management:endpoints:web:exposure:include: "*"

3、创建一个测试接口,网关验证通过即可访问

package cn.gathub.resource.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** @author Honghui [wanghonghui_work@163.com] 2021/3/16*/
@RestController
public class HelloController {@GetMapping("/hello")public String hello() {return "Hello World !";}}

4、创建一个获取登录中的用户信息的接口,用于从请求的Header中直接获取登录用户信息

package cn.gathub.resource.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;import cn.gathub.resource.domain.User;
import cn.hutool.core.convert.Convert;
import cn.hutool.json.JSONObject;/*** 获取登录用户信息接口** @author Honghui [wanghonghui_work@163.com] 2021/3/16*/
@RestController
@RequestMapping("/user")
public class UserController {@GetMapping("/currentUser")public User currentUser(HttpServletRequest request) {// 从Header中获取用户信息String userStr = request.getHeader("user");JSONObject userJsonObject = new JSONObject(userStr);return User.builder().username(userJsonObject.getStr("user_name")).id(Convert.toLong(userJsonObject.get("id"))).roles(Convert.toList(String.class, userJsonObject.get("authorities"))).build();}
}

功能演示

在此之前先启动我们的 Nacos 和 Redis 服务,然后依次启动oauth2-authoauth2-gatewayoauth2-api服务

我这里测试使用的 Docker 跑的单机版的 Nacos

docker pull nacos/nacos-server
docker run --env MODE=standalone --name nacos -d -p 8848:8848 nacos/nacos-server

1、使用密码模式获取JWT令牌,访问地址:http://localhost:9201/oauth/token

2、使用获取到的JWT令牌访问需要权限的接口,访问地址:
http://localhost:9201/resource/hello

3、使用获取到的JWT令牌访问获取当前登录用户信息的接口,访问地址:http://localhost:9201/resource/user/currentUser

4、当token不存在时,访问地址:http://localhost:9201/resource/user/currentUser

5、当JWT令牌过期时,使用refresh_token获取新的JWT令牌,访问地址:http://localhost:9201/oauth/token

6、使用授码模式登录时,先访问地址获取授权码:
http://localhost:9201/oauth/authorize?response_type=code&client_id=client-app-2&redirect_uri=https://www.baidu.com

7、访问地址,跳转登录页面

8、登录成功,进入授权页面

9、通过授权,拿到授权码

10、拿到授权码,访问地址登录:http://localhost:9201/oauth/token

11、使用没有访问权限的user账号登录,访问接口时会返回如下信息,访问地址:http://localhost:9201/resource/hello

项目源码地址

https://github.com/it-wwh/spring-cloud-gateway-oauth2

公众号

微服务权限终极解决方案(spring-cloud-gateway-oauth2)相关推荐

  1. 微服务接入oauth2_微服务权限终极解决方案,Spring Cloud Gateway+Oauth2实现统一认证和鉴权!...

    最近发现了一个很好的微服务权限解决方案,可以通过认证服务进行统一认证,然后通过网关来统一校验认证和鉴权.此方案为目前最新方案,仅支持Spring Boot 2.2.0.Spring Cloud Hox ...

  2. 「springcloud 2021 系列」Spring Cloud Gateway + OAuth2 + JWT 实现统一认证与鉴权

    通过认证服务进行统一认证,然后通过网关来统一校验认证和鉴权. 将采用 Nacos 作为注册中心,Gateway 作为网关,使用 nimbus-jose-jwt JWT 库操作 JWT 令牌 理论介绍 ...

  3. 微服务网关终结者?Spring Cloud推出新成员Spring Cloud Gateway

    导语:Spring Cloud Gateway基于Spring Boot 2,该项目提供了一个构建在Spring 生态之上的 API 网关.Spring Cloud Gateway旨在提供一种简单而有 ...

  4. 两大微服务框架dubbo和spring cloud对比

    一.基本介绍 dubbo Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,以及 SOA 服务治理方案.简单的说,Dubbo 就是个服务框架,说白了就是个远程服务 ...

  5. spring cloud微服务分布式云架构-Spring Cloud简介

    Spring Cloud是一系列框架的有序集合.利用Spring Boot的开发模式简化了分布式系统基础设施的开发,如服务发现.注册.配置中心.消息总线.负载均衡.断路器.数据监控等(这里只简单的列了 ...

  6. spring cloud微服务分布式云架构 - Spring Cloud简介

    Spring Cloud是一系列框架的有序集合.利用Spring Boot的开发模式简化了分布式系统基础设施的开发,如服务发现.注册.配置中心.消息总线.负载均衡.断路器.数据监控等(这里只简单的列了 ...

  7. Spring Cloud Gateway +Oauth2 +JWT+Vue 实现前后端分离RBAC权限管理

    这是一篇很长的文章,所以需要有点耐心,当然也可以直接查看源码:源码 对于有不太明白的地方可以给我留言,如果网关是zuul或者不是基于spring cloud的实现的,那其实更简单了1.1.如果是zuu ...

  8. spring cloud微服务分布式云架构 - Spring Cloud简介(一)

    点击上面 免费订阅本账号! 本文作者:it菲菲 原文:https://yq.aliyun.com/articles/672239? 点击阅读全文前往 Spring Cloud是一系列框架的有序集合.利 ...

  9. 微服务技术方案:Spring Cloud 从入门到实战

    随着互联网技术的发展与不断创新,以及用户流量的不断增大,越来越多的企业项目面临大数据.高并发等问题,随之而来的就是通过分布式模型组建架构,微服务思想就集中体现了应用价值,2020 年的你还没有掌握微服 ...

最新文章

  1. 如何使用Fiddler抓包操作?
  2. jenkins部署java项目之小白的笔记
  3. Mysql ERROR 2002 (HY000) Can't connect to local MySQL server through socket
  4. arduino步进电机程序库_【MM32 eMiniBoard测评报告】+驱动步进电机
  5. Android应用APP: 基于MobileNet和EfficientNet的图像分类模型_调试运行以及打包Tensorflow官方提供的Image classification demo
  6. 权御天下计算机音乐数字乐谱,权御天下(单音 适合电吉他)
  7. SQL Server 2012软件安装教程
  8. git可视化工具Sourcetree使用全攻略(包括各种git冲突解决)
  9. 京东联盟新版API接口PHP版SDK的坑
  10. python0.1+0.2不等于0.3_为什么计算机编程语言中:0.1+0.2不等于0.3
  11. UE4联机多人游戏基本设置
  12. [教程]配置青鸟云Web服务器
  13. phhphphphphphphp转自 细雪之舞 专注DEV
  14. 基于Matlab2012a的LineStretcher测线编号程序开发
  15. 华为手机计算机怎么语音算术,只动口不动手!华为手机这个自带功能绝了,录音秒变文字!...
  16. itextsharp操作pdf——插入图片
  17. freetype使用文泉驿显示及保存图片
  18. 一个漂亮配色的网站推荐
  19. 华为云、阿里云背后的700个智慧城市,能否实现“双碳”?
  20. vue列表左右箭头滑动_vue.js+elementUI实现点击左右箭头切换头像功能(类似轮播图效果)...

热门文章

  1. 我和Vision的故事
  2. 安装 | 企业版CDH服务器集群配置(详细)
  3. 小程序精细运营解决方案
  4. recreate()、finish()与onBackPressed()
  5. web人脸识别登陆系统简单版(springboot+mybatis)
  6. GAWK(一):RS、FS与ORS、OFS等内置变量的用法
  7. 前端----利用html,css,js自制有趣的音乐播放器
  8. DT CQT测试软件不包括哪个状态,DT-CQT测试方法.ppt
  9. 软件测试的分类 xmind 整理 留存
  10. WIN7系统 ORACLE 11G 安装后 只能localhost 访问本机,不能用IP地址访问