通过认证服务进行统一认证,然后通过网关来统一校验认证和鉴权。

将采用 Nacos 作为注册中心,Gateway 作为网关,使用 nimbus-jose-jwt JWT 库操作 JWT 令牌

理论介绍

Spring Security 是强大的且容易定制的,基于 Spring 开发的实现认证登录与资源授权的应用安全框架

SpringSecurity 的核心功能:

  • Authentication:身份认证,用户登陆的验证(解决你是谁的问题)
  • Authorization:访问授权,授权系统资源的访问权限(解决你能干什么的问题)
  • 安全防护,防止跨站请求,session 攻击等

SpringSecurity 配置类

  • configure(HttpSecurity httpSecurity)

    用于配置需要拦截的 url 路径、jwt 过滤器及出异常后的处理器

  • configure(AuthenticationManagerBuilder auth)

    用于配置 UserDetailsService 及 PasswordEncoder

  • RestfulAccessDeniedHandler

    当用户没有访问权限时的处理器,用于返回 JSON 格式的处理结果

  • RestAuthenticationEntryPoint

    当未登录或 token 失效时,返回 JSON 格式的结果

  • UserDetailsService

    SpringSecurity 定义的核心接口,用于根据用户名获取用户信息,需要自行实现

  • UserDetails

    SpringSecurity 定义用于封装用户信息的类(主要是用户信息和权限),需要自行实现

  • PasswordEncoder

    SpringSecurity 定义的用于对密码进行编码及比对的接口,目前使用的是 BCryptPasswordEncoder

  • JwtAuthenticationTokenFilter

    在用户名和密码校验前添加的过滤器,如果有 jwt 的 token,会自行根据 token 信息进行登录

JWT 单点登录

用户在应用 A 上登录认证,应用 A 会颁发给他一个 JWT 令牌(一个包含若干用户状态信息的字符串)。当用户访问应用 B 接口的时候,将这个字符串交给应用 B,应用 B 根据 Token 中的内容进行鉴权。不同的应用之间按照统一标准发放 JWT令牌,统一标准验证 JWT 令牌。从而你在应用 A 上获得的令牌,在应用 B 上也被认可,当然这样这些应用之间底层数据库必须是同一套用户、角色、权限数据。

  • 认证 Controller 代码统一
  • 鉴权 Filter 代码统一、校验规则是一样的
  • 使用同一套授权数据
  • 同一个用于签名和解签的 secret

JWT 存在的问题

说了这么多,JWT 也不是天衣无缝,由客户端维护登录状态带来的一些问题在这里依然存在,举例如下:

  • 续签问题,这是被很多人诟病的问题之一,传统的cookie+session的方案天然的支持续签,但是jwt由于服务端不保存用户状态,因此很难完美解决续签问题,如果引入redis,虽然可以解决问题,但是jwt也变得不伦不类了

  • 注销问题,由于服务端不再保存用户信息,所以一般可以通过修改secret来实现注销,服务端secret修改后,已经颁发的未过期的token就会认证失败,进而实现注销,不过毕竟没有传统的注销方便

  • 密码重置,密码重置后,原本的token依然可以访问系统,这时候也需要强制修改secret

  • 基于第2点和第3点,一般建议不同用户取不同secret

OAuth2

以上的所有的单点集群登陆方案,都是有一个前提就是:应用A、应用B、应用1、应用2、应用3都是你们公司的,你们公司内部应用之间进行单点登陆验证。

但是大家都见过这样一个场景:登录某一个网站,然后使用的是在QQ、微信上保存的用户数据。也就是说第三方应用想使用某个权威平台的用户数据做登录认证,那么这个权威平台该如何对第三方应用提供认证服务?目前比较通用的做法就是OAuth2(现代化的社交媒体网站登录基本都使用OAuth2)

  • Spring Security OAuth 项目进入维护状态,不再做新特性的开发。只做功能维护和次要特性开发
  • 未来所有的基于 Spring 的 OAuth2.0 的支持都基于 Spring Security 5.2 版本开发。即:Spring Security 5.2 以后的版本是 OAuth2.0 支持库,用来替换 Spring Security OAuth

OAuth2 需求场景

在说明OAuth2需求及使用场景之前,需要先介绍一下OAuth2授权流程中的各种角色:

  • 资源拥有者(User) - 指应用的用户,通常指的是系统的登录用户
  • 认证服务器 (Authorization Server)- 提供登录认证接口的服务器,比如:github登录、QQ登录、微信登录等
  • 资源服务器 (Resources Server) - 提供资源接口及服务的服务器,比如:用户信息接口等。通常和认证服务器是同一个应用。
  • 第三方客户端(Client) - 第三方应用,希望使用资源服务器提供的资源
  • 服务提供商(Provider): 认证服务和资源服务归属于一个机构,该机构就是服务提供商

OAuth2 四种授权模式

  • 授权码模式(authorization code)
  • 简化模式(implicit)
  • 密码模式(resource owner password credentials)
  • 客户端模式(client credentials)

密码模式与授权码模式最大的区别在于:

  • 授权码模式申请授权码的过程是用户直接与认证服务器进行交互,然后授权结果由认证服务器告知第三方客户端,也就是不会向第三方客户端暴露服务提供商的用户密码信息
  • 密码模式,是用户将用户密码信息交给第三方客户端,然后由第三方向服务提供商进行认证和资源请求。绝大多数的服务提供商都会选择使用授权码模式,避免自己的用户密码暴漏给第三方。所以密码模式只适用于服务提供商对第三方厂商(第三方应用)高度信任的情况下才能使用,或者这个“第三方应用”实际就是服务提供商自己的应用

整合 JWT

因为 Spring Security OAuth “认证服务器”支持多种认证模式,所以不想抛弃它。但是想把最后的"资源访问令牌",由 AccessToken 换成 JWT 令牌。因为 AccessToken 不带有任何的附加信息,就是一个字符串,JWT 是可以携带附加信息的。

应用架构

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

相关服务划分:

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

方案实现

下面介绍下这套解决方案的具体实现,依次搭建认证服务、网关服务和 API 服务

security-oauth2-auth 认证

首先来搭建认证服务,它将作为 Oauth2 的认证服务使用,并且网关服务的鉴权功能也需要依赖它

pom 依赖

  • pom.xml 中添加相关依赖,主要是 Spring Security、Oauth2、JWT、Redis 相关依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.security.oauth.boot</groupId><artifactId>spring-security-oauth2-autoconfigure</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-rsa</artifactId></dependency><dependency><groupId>com.nimbusds</groupId><artifactId>nimbus-jose-jwt</artifactId></dependency>
</dependencies>
  • application.yml 中添加相关配置,主要是 Nacos 和 Redis 相关配置
server:port: 9401spring:profiles:active: devapplication:name: security-oauth2-authcloud:nacos:discovery:server-addr: 192.168.123.22:8848username: nacospassword: nacos       redis:port: 6379host: localhostpassword: xxxmanagement:endpoints:web:exposure:include: "*"

认证服务配置

生成密钥库
  • 使用 keytool 生成 RSA 证书 jwt.jks,复制到 resource 目录下,在 JDK 的 bin 目录下使用如下命令即可
keytool -genkey -alias jwt -keyalg RSA -keystore jwt.jks
加载用户信息
  • 创建 UserServiceImpl 类实现 Spring Security 的 UserDetailsService 接口,用于加载用户信息
/*** 用户管理业务类*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService
{@Autowiredprivate UmsAdminService    adminService;@Autowiredprivate HttpServletRequest request;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{String  clientId = request.getParameter("client_id");UserDto userDto  = null;if (AuthConstant.ADMIN_CLIENT_ID.equals(clientId)){userDto = adminService.loadUserByUsername(username);}if (null == userDto){throw new UsernameNotFoundException(EC.ERROR_USER_PASSWORD_INCORRECT.getMsg());}SecurityUserDetails securityUser = new SecurityUserDetails(userDto);if (!securityUser.isEnabled()){throw new DisabledException(EC.ERROR_USER_ENABLED.getMsg());}else if (!securityUser.isAccountNonLocked()){throw new LockedException(EC.ERROR_USER_LOCKED.getMsg());}else if (!securityUser.isAccountNonExpired()){throw new AccountExpiredException(EC.ERROR_USER_EXPIRE.getMsg());}else if (!securityUser.isCredentialsNonExpired()){throw new CredentialsExpiredException(EC.ERROR_USER_UNAUTHORIZED.getMsg());}return securityUser;}}@Component
public class UmsAdminService
{@Autowiredprivate PasswordEncoder passwordEncoder;public UserDto loadUserByUsername(String username){String  password = passwordEncoder.encode("123456a");if("admin".equals(username)){return new UserDto("admin", password, 1, "", CollUtil.toList("ADMIN"));}else if("langya".equals(username)){return new UserDto("langya", password, 1, "", CollUtil.toList("ADMIN", "TEST"));}return null;}
}
认证服务配置
  • 添加认证服务相关配置 Oauth2ServerConfig,需要配置加载用户信息的服务 UserServiceImpl 及 RSA 的钥匙对KeyPair
/*** 认证服务器配置*/
@AllArgsConstructor
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter
{private final PasswordEncoder        passwordEncoder;private final UserDetailsServiceImpl userDetailsService;private final AuthenticationManager  authenticationManager;private final JwtTokenEnhancer       jwtTokenEnhancer;/*** 客户端信息配置*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception{clients.inMemory().withClient(AuthConstant.ADMIN_CLIENT_ID).secret(passwordEncoder.encode("123456")).scopes("all").authorizedGrantTypes("password", "refresh_token").accessTokenValiditySeconds(3600 * 24).refreshTokenValiditySeconds(3600 * 24 * 7).and().withClient(AuthConstant.PORTAL_CLIENT_ID).secret(passwordEncoder.encode("123456")).scopes("all").authorizedGrantTypes("password", "refresh_token").accessTokenValiditySeconds(3600 * 24).refreshTokenValiditySeconds(3600 * 24 * 7);}/*** 配置授权(authorization)以及令牌(token)*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception{TokenEnhancerChain  enhancerChain = new TokenEnhancerChain();List<TokenEnhancer> delegates     = new ArrayList<>();delegates.add(jwtTokenEnhancer);delegates.add(accessTokenConverter());//配置JWT的内容增强器enhancerChain.setTokenEnhancers(delegates);endpoints.authenticationManager(authenticationManager)//配置加载用户信息的服务.userDetailsService(userDetailsService).accessTokenConverter(accessTokenConverter()).tokenEnhancer(enhancerChain);}/*** 允许表单认证*/@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception{security.allowFormAuthenticationForClients();}/*** 使用非对称加密算法对token签名*/@Beanpublic JwtAccessTokenConverter accessTokenConverter(){JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();//or 设置对称签名//jwtAccessTokenConverter.setSigningKey("2430B31859314947BC84697E70B3D31F");jwtAccessTokenConverter.setKeyPair(keyPair());return jwtAccessTokenConverter;}/*** 从classpath下的密钥库中获取密钥对(公钥+私钥)*/@Beanpublic KeyPair keyPair(){//从classpath下的证书中获取秘钥对KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"),"123456".toCharArray());return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());}
}
jwt 增强
  • 如果你想往 JWT 中添加自定义信息的话,比如说 登录用户的ID,可以自己实现 TokenEnhancer 接口
/*** JWT 内容增强器*/
@Component
public class JwtTokenEnhancer implements TokenEnhancer
{@Overridepublic OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication){SecurityUserDetails securityUser = (SecurityUserDetails) authentication.getPrincipal();//把用户名设置到JWT中Map<String, Object> info = new HashMap<>();info.put("user_name", securityUser.getUsername());info.put("client_id", securityUser.getClientId());((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);return accessToken;}
}
公钥获取接口
  • 由于的网关服务需要 RSA 的公钥来验证签名是否合法,所以认证服务需要有个接口把公钥暴露出来
/*** 获取RSA公钥接口*/
@RestController
public class KeyPairController
{@Autowiredprivate 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();}
}

安全配置

  • 不要忘了还需要配置 Spring Security,允许获取公钥接口的访问
/*** SpringSecurity 安全配置*/
@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();}/***  如果不配置 SpringBoot 会自动配置一个 AuthenticationManager 覆盖掉内存中的用户*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception{return super.authenticationManagerBean();}@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
}

资源角色映射缓存

  • 创建一个资源服务 ResourceServiceImpl,初始化的时候把资源与角色匹配关系缓存到 Redis 中,方便网关服务进行鉴权的时候获取
/*** 资源与角色匹配关系管理业务类* <p>* 初始化的时候把资源与角色匹配关系缓存到Redis中,方便网关服务进行鉴权的时候获取*/
@Service
public class ResourceServiceImpl
{@Autowiredprivate RedisTemplate<String, Object> redisTemplate;private Map<String, List<String>> resourceRolesMap;@PostConstructpublic void initData(){resourceRolesMap = new TreeMap<>();resourceRolesMap.put("/admin/hello", CollUtil.toList("ADMIN"));resourceRolesMap.put("/admin/user/currentUser", CollUtil.toList("ADMIN", "TEST"));redisTemplate.opsForHash().putAll(AuthConstant.RESOURCE_ROLES_MAP_KEY, resourceRolesMap);}
}

如果资源权限存储到数据库,也可以直接使用 SQL 语句形成结果集,如:

security-oauth2-gateway 鉴权

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

pom 依赖

  • pom.xml 中添加相关依赖,主要是 Gateway、Oauth2 和 JWT 相关依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!--lb:// need--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</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></dependency>
</dependencies>
  • application.yml 中添加相关配置,主要是路由规则的配置、Oauth2中RSA公钥的配置及路由白名单的配置
server:port: 9201spring:main:#springcloudgateway 的内部是通过 netty+webflux 实现的#webflux 实现和 spring-boot-starter-web 依赖冲突web-application-type: reactiveprofiles:active: devapplication:name: security-oauth2-gatewaycloud:nacos:discovery:server-addr: localhost:8848username: nacospassword: nacosgateway:routes: #配置路由路径- id: oauth2-api-routeuri: lb://security-oauth2-apipredicates:- Path=/admin/**filters:- StripPrefix=1- id: oauth2-auth-routeuri: lb://security-oauth2-authpredicates:- Path=/auth/**filters:- StripPrefix=1discovery:locator:#开启从注册中心动态创建路由的功能enabled: true#使用小写服务名,默认是大写lower-case-service-id: truesecurity:oauth2:resourceserver:jwt:#配置RSA的公钥访问地址jwk-set-uri: 'http://localhost:9401/rsa/publicKey'redis:host: 192.168.123.22port: 6379password: Hacfin_Redis8timeout: 6000mssecure:ignore:#配置白名单路径urls:- "/actuator/**"- "/auth/oauth/token"

资源服务器配置

  • 对网关服务进行配置安全配置,由于 Gateway 使用的是 WebFlux,所以需要使用 @EnableWebFluxSecurity注解开启
/*** 资源服务器配置*/
@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());//自定义处理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();}/*** @linkhttps://blog.csdn.net/qq_24230139/article/details/105091273* ServerHttpSecurity 没有将 jwt 中 authorities 的负载部分当做 Authentication* 需要把 jwt 的 Claim 中的 authorities 加入* 方案:重新定义 ReactiveAuthenticationManager 权限管理器,默认转换器 JwtGrantedAuthoritiesConverter*/@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);}
}

鉴权管理器

  • WebFluxSecurity 中自定义鉴权操作需要实现 ReactiveAuthorizationManager 接口
@Component
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {@Autowiredprivate RedisTemplate<String,Object> redisTemplate;@Overridepublic Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {//从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());//认证通过且角色匹配的用户可访问当前路径return mono.filter(Authentication::isAuthenticated).flatMapIterable(Authentication::getAuthorities).map(GrantedAuthority::getAuthority).any(authorities::contains).map(AuthorizationDecision::new).defaultIfEmpty(new AuthorizationDecision(false));}
}

过滤器

  • 这里还需要实现一个全局过滤器 AuthGlobalFilter,当鉴权通过后将 JWT 令牌中的用户信息解析出来,然后存入请求的 Header 中,这样后续服务就不需要解析 JWT 令牌了,可以直接从请求的 Header 中获取到用户信息
/*** 将登录用户的JWT转化成用户信息的全局过滤器*/
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered
{@Autowiredprivate RedisTemplate redisTemplate;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){//认证信息从Header 或 请求参数 中获取ServerHttpRequest serverHttpRequest = exchange.getRequest();String token = serverHttpRequest.getHeaders().getFirst(AuthConstant.JWT_TOKEN_HEADER);if (Objects.isNull(token)){token = serverHttpRequest.getQueryParams().getFirst(AuthConstant.JWT_TOKEN_HEADER);}if (StrUtil.isEmpty(token)){return chain.filter(exchange);}try{//从token中解析用户信息并设置到Header中去String    realToken = token.replace(AuthConstant.JWT_TOKEN_PREFIX, "");JWSObject jwsObject = JWSObject.parse(realToken);String    userStr   = jwsObject.getPayload().toString();// 黑名单token(登出、修改密码)校验JSONObject jsonObject = JSONUtil.parseObj(userStr);String     jti        = jsonObject.getStr("jti");Boolean isBlack = redisTemplate.hasKey(AuthConstant.TOKEN_BLACKLIST_PREFIX + jti);if (isBlack){}// 存在token且不是黑名单,request写入JWT的载体信息ServerHttpRequest request = serverHttpRequest.mutate().header(AuthConstant.USER_TOKEN_HEADER, userStr).build();exchange = exchange.mutate().request(request).build();}catch (ParseException e){e.printStackTrace();}return chain.filter(exchange);}@Overridepublic int getOrder(){return 0;}
}

security-oauth2-api

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

pom 依赖

  • pom.xml中添加相关依赖,就添加了一个web依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>

登录信息接口

  • 创建一个 LoginUserHolder 组件,用于从请求的 Header 中直接获取登录用户信息
/*** 获取登录用户信息*/
@Component
public class LoginUserHolder
{public UserDto getCurrentUser(HttpServletRequest request){String userStr = request.getHeader(AuthConstant.USER_TOKEN_HEADER);JSONObject userJsonObject = new JSONObject(userStr);UserDto userDTO = new UserDto();userDTO.setUserName(userJsonObject.getStr("user_name"));userDTO.setClientId(userJsonObject.getStr("client_id"));userDTO.setRoles(Convert.toList(String.class, userJsonObject.get(AuthConstant.AUTHORITY_CLAIM_NAME)));return userDTO;}
}
  • 创建一个获取当前用户信息的接口
@RestController
@RequestMapping("/user")
public class UserController{@Autowiredprivate LoginUserHolder loginUserHolder;@GetMapping("/currentUser")public UserDTO currentUser() {return loginUserHolder.getCurrentUser();}
}

功能演示

接下来来演示下微服务系统中的统一认证鉴权功能,所有请求均通过网关访问

  • 启动 Nacos 和 Redis 服务

  • 启动 security-oauth2-authsecurity-oauth2-gatewaysecurity-oauth2-api 服务

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

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

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

参考

Spring Cloud Gateway + Oauth2 实现统一认证和鉴权

Spring Cloud Gateway + Spring Security OAuth2 + JWT 实现微服务统一认证授权鉴权

Oauth2 自定义处理结果的最佳方案

听说你的JWT库用起来特别扭,推荐这款贼好用的!

「springcloud 2021 系列」Spring Cloud Gateway + OAuth2 + JWT 实现统一认证与鉴权相关推荐

  1. 「springcloud 2021 系列」sentinel实现熔断与限流 原来这么简单

    Sentinel 简介 随着微服务的流行,服务和服务之间的稳定性变得越来越重要.Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制.熔断降级.系统自适应保护等多个维度 ...

  2. 「springcloud 2021 系列」RocketMQ 如何快速实现微服务消息机制

    RocketMQ 介绍 详解了解可以查看如下文档:rocketmq 基础知识 RocketMQ 是一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的.高可靠的消息发布与订阅服务.同时,广 ...

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

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

  4. 「面试必背」Spring Cloud面试题(2022最新版)

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

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

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

  6. 大话微服务:Spring Cloud gateway+OAuth2 实现单点登录和权限控制(二) OAuth2.0 四种模式的通俗理解

    一. 概述 OAuth2.0的规范要求,就是客户端(即通常是各个应用程序)要访问资源所有者时,经过资源所有者同意后,由Oauth向这个客户端颁发令牌.为了满足互联网不同的场景 ,规定了四种获得令牌的流 ...

  7. 「春招系列」30张图理解HTTP在面试中所有会出现的题

    前言 又是一年金三银四,春招与跳槽热闹的开展着,而在面试过程中,HTTP 被提问的概率还是非常高的. 我搜集了 5 大类 HTTP 面试常问的题目,同时这 5 大类题跟 HTTP 的发展和演变关联性是 ...

  8. spring cloud gateway Unhandled failure: Only one connection receive subscriber allowed.

    https://gist.github.com/WeirdBob/b25569d461f0f54444d2c0eab51f3c48 ToUppercaseGlobalFilter.java https ...

  9. 【Spring Cloud Alibaba 实战 | 总结篇】Spring Cloud Gateway + Spring Security OAuth2 + JWT 实现微服务统一认证授权和鉴权

    一. 前言 hi,大家好~ 好久没更文了,期间主要致力于项目的功能升级和问题修复中,经过一年时间这里只贴出关键部分代码的打磨,[有来]终于迎来v2.0版本,相较于v1.x版本主要完善了OAuth2认证 ...

最新文章

  1. android values-v21 style 报错,Android 4.4 以上实现透明导航栏和状态栏 Translucent system bar...
  2. SpringBoot中@EnableAutoConfiguration注解的作用
  3. [转载]秀脱linux实战笔记linux-kernel-3.0.3实战篇
  4. XAML Namespace http://schemas.microsoft.com/expression/blend/2008 is not resolved
  5. POI操作EXCEL2007,报javax.xml.stream.XMLEventFactory.newFactory()错误!
  6. ​苹果官网出现价格Bug:千元产品变百元;阿里云量子模拟平台“太章2.0”正式开源;Vant 3.0发布|极客头条...
  7. 《Python数据科学实践指南》——1.2 Python解释器
  8. 电脑全能工具箱,400+工具免费用
  9. Java中list转map的常用方法
  10. java导出excel 自定义表头
  11. Android 图文数据JSON解析,金山词霸每日一句API的调用
  12. Java手机号码工具类(判断运营商、获取归属地)
  13. 如何拍摄一部优秀的广告片——表现手法,特殊创意
  14. 从用户文件到系统驱动,全面清理c盘
  15. SQL 标题: 连接到服务器 ------------------------------ 无法连接
  16. 金仓数据库KingBaseES V7安装指南
  17. 时间服务器【chrony】小练习
  18. 盘古团队发布 iOS 9.3.3 越狱工具,不但能“越狱”还能“回监狱”
  19. windows nao naoqi SDK 配置
  20. android开发之仿QQ拖拽界面效果(侧滑面板)

热门文章

  1. 员工跳槽面试美团,两次面试通过却被offer审核放鸽子,结果蒙了
  2. java modbus lrc,C#实现modbus基于ASCII的LRC校验
  3. js截取指定字符前面或后面的内容
  4. 面试经常问的问题:线性电源VS开关电源
  5. CSS overflow 属性
  6. UE5 Lyra中的UI层级与资产管理
  7. 【Selenium】弹出框处理
  8. 舞蹈教学app开发定制有哪些基本功能
  9. 论文阅读:Noise-Resilient Training Method for Face Landmark Generation From Speech
  10. 如何零基础学习“人工智能”?