上一节在认证服务器里,将token 由uuid改造成了JWT,之前在网关上拿到令牌access_token后,需要去认证服务器校验令牌,将令牌信息转换为用户信息。

现在有了jwt后,由于jwt是自包含的,已经包含了用户的身份信息,所以在网关上不需要去认证服务器验令牌了。

 

 

之前在网关上所做的这些去认证服务器验令牌信息,转换为用户信息,去认证服务器做权限的判断,这些其实SpringSecurity-OAuth都已经实现好了的。之前之所以手写是为了理解SpringSecurity-OAuth内部的实现。

1,删掉网关上filter包里的过滤器

 

 

 

2,在网关项目里加上依赖

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

3,配置获取jwt验签的key的uri
认证服务器生成Jwt的时候,是进行了签名的,有一个签名的key;解析Jwt,需要验签,也需要这个key值,所以需要告诉网关,去哪里获取这个签名的key。
在网关的配置文件里配置: (这个uri具体在org.springframework.security.oauth2.provider.endpoint.TokenKeyEndpoint里处理)
资源服务器启动的时候,就会去认证服务器拿这个key,所以启动网关前必须要保证认证服务器是启动的。
security:
oauth2:
resource:
jwt:
key-uri: http://auth.nb.com:9090/oauth/token_key #获取解析jwt,验签名key的路径
client:
client-id: gateway #获取验签key需要身份认证,这里是网关的clientId
client-secret: 123456 #获取验签key需要身份认证,这里是网关的secret

4,网关作为一个资源服务器,配置其安全配置

/**
 * 作为一个资源服务器存在
 */
@Configuration
@EnableResourceServer
public class GatewaySecurityConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/token/**").permitAll() //放过/token开头的请求,是在申请令牌
            .anyRequest().authenticated();
    }
}
5,各个微服务的改造
  
之前各个微服务需要用到用户信息的时候,在网关上,网关从认证服务器解析令牌,获取到用户信息后,是将用户名以明文的方式放在了请求头里,各个微服务从请求头里获取到明文的username参数,这样是有安全问题的。
  此时,各个微服务也需要解析jwt,获取用户信息。所以各个微服务就需要跟网关一样,解析jwt,所以也需要从认证服务器获取验签的key,故需要做和网关一样的配置。
  在订单微服务,引入 SpringSecurity-OAuth依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

 订单微服务也作为资源服务器存在,所以需要给订单微服务打上资源服务器的标记    @EnableResourceServer

 

 

 

在订单微服务,获取用户信息 也要用注解 @AuthenticationPrincipal String username 。
实验:

1,先启动 认证服务器,因为各个微服务启动的时候就会去认证服务器拿jwt验签的key。
2.启动网关。
发现报如下异常 Caused by: org.springframework.web.client.HttpClientErrorException$NotFound: 404 null

 

这个异常的意思是,在拿jwt验签key的时候,找不到该服务,为什么呢?在认证服务器上,配置jwt tokenStore的时候,需要做一下特殊处理:
需要将
JwtAccessTokenConverter 暴露为Spring的Bean,而且必须为public的。

 


 

再次重启认证服务器,重启网关,正常启动。

postman调用网关,获取令牌 http://localhost:9070/token/oauth/token

客户端信息表

 

 

 

 

 通过网关,拿access_token去订单服务创建订单  http://localhost:9070/order/orders

 

 

 处理办法:

1,在oauth_client_details表里的orderApp应用的resource_ids 字段里,配置上网关的resourceId,前提是网关代码里也配置了resourceId。这样安全性更高,只是需要维护这些资源服务器id。

     

调用成功

 

 

 

 

 2,如果对安全性要求不高,可以把oauth_client_details表里 orderApp的resource_ids 字段设置为空,这样给orderApp客户端发出的jwt就可以访问任何的微服务了。

 在微服务之间传递jwt令牌信息

比如订单微服务调用了加个微服务,那么订单微服务怎么把用户信息传递给价格微服务呢?之前的做法是,将用户信息放在请求头里,进行传递。

其实SpringSecurity-OAuth已经替你把这些事情做好了。可以在微服务之间传递jwt令牌,传过去的jwt会被解析为用户信息。

下面改造订单微服务和价格微服务,在他们之间传递用户信息。

价格微服务也作为资源服务器存在

@SpringBootApplication
@EnableResourceServer//作为资源服务器
public class NbPriceApiApplication {

    public static void main(String[] args) {
        SpringApplication.run(NbPriceApiApplication.class, args);
        System.err.println("============= Price Api 启动完成 ============");
    }

}

价格微服务也加入SpringCloud和SpringSecurity-OAuth的依赖

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
dependencyManagement里:
<!--spring cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>

价格微服务获取用户信息 :   @AuthenticationPrincipal String username

能在微服务之间自动传递jwt令牌信息, 主角就是:OAuth2RestTemplate,它能从请求上下文中拿到jwt 令牌,然后将其放入请求头,在其他微服务里,就可以通过@AuthenticationPrincipal 注解来获得用户信息了。

在订单微服务里配置OAuth2RestTemplate

@Configuration
@SpringBootApplication
@EnableResourceServer//作为资源服务器存在
public class NbOrderApiApplication {

    //声名OAuth2RestTemplate
    //会从请求的上下文里拿到令牌,放到请求头里,发出去。需要两个参数,springboot会自动出入进来
    @Bean
    public OAuth2RestTemplate oAuth2RestTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context){
        return new OAuth2RestTemplate(resource,context);
    }

    public static void main(String[] args) {
        SpringApplication.run(NbOrderApiApplication.class, args);
        System.err.println("============= Order Api 启动完成 ============");
    }

}

 

 

 

 

随便用某个客户端生成一个jwt令牌

 

 

 

 然后通过网关调用创建订单服务器

 

 

 

 查看订单服务和价格服务里,是否打印了用户名

订单服务日志,网关已经把jwt传递给了订单服务,而且订单服务把jwt解析成了用户信息

 

 

 查看价格微服务,看订单服务是否把jwt传给了价格服务

 

 

 至此,在网关上已经实现了由SpringSecurity-OAuth替我们实现各种认证啊、授权的过滤器。前面系列文章说的都是自己实现这些过滤器,自己实现是为了了解其中的原理。

目前的架构,前面文章说的两个问题:

1,在网关上再去认证服务器验令牌,认证服务器压力变大

  解决:token 信息是 jwt,已经自包含身份信息。不用再去认证服务器验令牌。减少了一次请求,网关、认证服务器的压力减小了。

2,明文在微服务之间的请求头里传递用户信息

  解决:JWT是自包含身份信息的,用OAuth2RestTemplate发请求,SpringSecurity-OAuth会自动从请求上下文拿到jwt信息,放进请求头,下游微服务拿到后会解析jwt。

到目前来说,各个微服务(包括网关),都是资源服务器,需要在它们配置类上打上 @EnableResourceServer 注解,使其成为资源服务器。而且各个资源服务器需要引入SpringCloud的依赖以及 spring-cloud-starter-oauth2 依赖。

目前的架构图是这样的

 

 

代码:https://github.com/lhy1234/springcloud-security/tree/chapt-6-2-jwt02

欢迎关注个人公众号一起交流学习: