api网关zuul 与 其他服务整合JWT(五)

api网关zuul 与 其他服务整合JWT(五)

上个章节已经做好了认证服务,但是调用服务是从先进入网关,再在网关路由到具体的服务的,现在添加了认证服务,所以在网关中需要一些配置才能让请求到达具体服务
进入mygateway,添加安全配置类

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * http安全配置
     * @param http http安全对象
     * @throws Exception http安全异常信息
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();  // 禁用csrf
    }
}

然后进入application.yml

#服务器配置
server:
  #端口
  port: 8080

#spring配置
spring:
  #应用配置
  application:
    #名称: api网关服务
    name: api-gateway

#服务器发现注册配置
eureka:
  client:
    serviceUrl:
      #配置服务中心(可配置多个,用逗号隔开)
      defaultZone: http://${user.name}:${user.password}@192.168.0.112:8761/eureka
zuul:
  sensitive-headers:     #Cookie,Set-Cookie


oauth2:
  signature-verification:
    public-key-endpoint-uri: http://myuaa/oauth/token_key
    #ttl for public keys to verify JWT tokens (in ms)
    ttl: 3600000
    #max. rate at which public keys will be fetched (in ms)
    public-key-refresh-rate-limit: 10000
  web-client-configuration:
    #keep in sync with UAA configuration
    client-id: web_app
    secret: changeit
    # Controls session expiration due to inactivity (ignored for remember-me).
    # Negative values disable session inactivity expiration.
    session-timeout-in-seconds: 1800

user:
  name: admin
  password: admin_1

其中 zuul是网关配置,我们可以配置路由,规则,这些也可以使用默认的,重点讲sensitive-headers,zuul默认会屏蔽cookie,所以我们在请求头head的数据会被拦截,sensitive-headers配置就是设置网关拦截的,如果为空,代表关闭黑名单,所有的都会往下传,不为空,设置的才会往下传.像上面,我设的空,即是请求所有往下传,当然你们也可以设置cookie往下传,只需将 Cookie,Set-Cookie加上去,即代表cookie往下传,不会拦截认证信息
打开postmen
api网关zuul 与 其他服务整合JWT(五)_第1张图片
上面我用的网关走的请求,192.168.0.112:8080是网关,myuaa是认证服务的项目名

这里要说一下,由于我们用了JWT,拿到的token都是加密的,在myuaa上还好,去了其他的服务是需要解密的:

这里是有两种方案的:
1.所有的请求是在网关解密,在路由到具体服务
2.所有的解密是在资源服务器上,网关只是路由

我这里选择是在资源服务器上解密,减轻网关压力,将压力分发到不同的资源服务器上

进入app-producer-1,创建资源配置类ResourceConfiguration
ResourceConfiguration

@Configuration
@EnableResourceServer
//@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class ResourceConfiguration extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
           
            .authorizeRequests()
            .antMatchers("/api/**").authenticated()
         .and()
            .csrf()
            .disable()
        ;
    }
}

所有/api开头请求需要认证,
添加自定义配置依赖

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>

在配置文件application.yml添加配置

oauth2:
  signature-verification:
    public-key-endpoint-uri: http://myuaa/oauth/token_key
    #ttl for public keys to verify JWT tokens (in ms)
    ttl: 3600000
    #max. rate at which public keys will be fetched (in ms)
    public-key-refresh-rate-limit: 10000
  web-client-configuration:
    #keep in sync with UAA configuration
    client-id: web
    secret: changeit

添加配置文件OAuth2Properties

/**
 * OAuth2属性定义。
 */
@Component
@ConfigurationProperties(prefix = "oauth2", ignoreUnknownFields = false)
public class OAuth2Properties {
    private WebClientConfiguration webClientConfiguration = new WebClientConfiguration();

    private SignatureVerification signatureVerification = new SignatureVerification();

    public WebClientConfiguration getWebClientConfiguration() {
        return webClientConfiguration;
    }

    public SignatureVerification getSignatureVerification() {
        return signatureVerification;
    }

    public static class WebClientConfiguration {
        private String clientId ;
        private String secret ;
 
        private int sessionTimeoutInSeconds ;
     
        private String cookieDomain;

        public String getClientId() {
            return clientId;
        }

        public void setClientId(String clientId) {
            this.clientId = clientId;
        }

        public String getSecret() {
            return secret;
        }

        public void setSecret(String secret) {
            this.secret = secret;
        }

        public int getSessionTimeoutInSeconds() {
            return sessionTimeoutInSeconds;
        }

        public void setSessionTimeoutInSeconds(int sessionTimeoutInSeconds) {
            this.sessionTimeoutInSeconds = sessionTimeoutInSeconds;
        }

        public String getCookieDomain() {
            return cookieDomain;
        }

        public void setCookieDomain(String cookieDomain) {
            this.cookieDomain = cookieDomain;
        }
    }

    public static class SignatureVerification {
    
        private long publicKeyRefreshRateLimit ;
 
        private long ttl ;
   
        private String publicKeyEndpointUri = "http://uaa/oauth/token_key";

        public long getPublicKeyRefreshRateLimit() {
            return publicKeyRefreshRateLimit;
        }

        public void setPublicKeyRefreshRateLimit(long publicKeyRefreshRateLimit) {
            this.publicKeyRefreshRateLimit = publicKeyRefreshRateLimit;
        }

        public long getTtl() {
            return ttl;
        }

        public void setTtl(long ttl) {
            this.ttl = ttl;
        }

        public String getPublicKeyEndpointUri() {
            return publicKeyEndpointUri;
        }

        public void setPublicKeyEndpointUri(String publicKeyEndpointUri) {
            this.publicKeyEndpointUri = publicKeyEndpointUri;
        }
    }
}

我定义的http://myuaa/oauth/token_key这种url是需要restTemplate来请求的,这个/oauth/token_key是拿去JWT公匙的路径,在myuaa的配置中是已经打开的,所以在ResourceConfiguration中定义restTemplate的bean,这种会通过服务名去请求,不需要服务的ip来支持.不理解去这篇链接: springcloud的RestTemplate调用rest接口.

    @Bean
    public RestTemplate loadBalancedRestTemplate(RestTemplateCustomizer customizer) {
        RestTemplate restTemplate = new RestTemplate();
        customizer.customize(restTemplate);
        return restTemplate;
    }

做一个解码器类UaaSignatureVerifierClient(就是拿到JWT公匙,生成一个解码器)

@Component
public class UaaSignatureVerifierClient {
    private final Logger log = LoggerFactory.getLogger(UaaSignatureVerifierClient.class);
    private final RestTemplate restTemplate;
    protected final OAuth2Properties oAuth2Properties;

    public UaaSignatureVerifierClient(DiscoveryClient discoveryClient, RestTemplate restTemplate,
                                      OAuth2Properties oAuth2Properties) {
        this.restTemplate = restTemplate;
        this.oAuth2Properties = oAuth2Properties;
        // Load available UAA servers
        discoveryClient.getServices();
    }

    /**
     * 从uaa获取 公匙
     *
     * @return 他用于验证JWT令牌的公钥; 或者为null。
     */
    public SignatureVerifier getSignatureVerifier() throws Exception {
        try {
            HttpEntity<Void> request = new HttpEntity<Void>(new HttpHeaders());
            String key = (String)restTemplate
                .exchange(getPublicKeyEndpoint(), HttpMethod.GET, request, Map.class).getBody()
                .get("value");
            return new RsaVerifier(key);
        } catch (IllegalStateException ex) {
            log.warn("could not contact UAA to get public key");
            return null;
        }
    }

    /** Returns the configured endpoint URI to retrieve the public key. */
    private String getPublicKeyEndpoint() {
        String tokenEndpointUrl = oAuth2Properties.getSignatureVerification().getPublicKeyEndpointUri();
        if (tokenEndpointUrl == null) {
            throw new InvalidClientException("no token endpoint configured in application properties");
        }
        return tokenEndpointUrl;
    }
}

做一个jwttoken转换器OAuth2JwtAccessTokenConverter继承JwtAccessTokenConverter

public class OAuth2JwtAccessTokenConverter extends JwtAccessTokenConverter {
    private final Logger log = LoggerFactory.getLogger(OAuth2JwtAccessTokenConverter.class);

    private final OAuth2Properties oAuth2Properties;
    private final UaaSignatureVerifierClient signatureVerifierClient;
    /**
     * When did we last fetch the public key?
     */
    private long lastKeyFetchTimestamp;

    public OAuth2JwtAccessTokenConverter(OAuth2Properties oAuth2Properties, OAuth2SignatureVerifierClient signatureVerifierClient) {
        this.oAuth2Properties = oAuth2Properties;
        this.signatureVerifierClient = signatureVerifierClient;
        tryCreateSignatureVerifier();
    }

    /**
     *       尝试使用当前公钥解码令牌。
     *      *如果失败,请联系OAuth2服务器以获取新的公钥,然后重试。
     *      *我们可能没有首先获取它或它可能已经改变。
     *
     * @param token the JWT token to decode.
     * @return the resulting claims.
     * @throws InvalidTokenException if we cannot decode the token.
     */
    @Override
    protected Map<String, Object> decode(String token) {
        try {
            //check if our public key and thus SignatureVerifier have expired
            long ttl = oAuth2Properties.getSignatureVerification().getTtl();
            if (ttl > 0 && System.currentTimeMillis() - lastKeyFetchTimestamp > ttl) {
                throw new InvalidTokenException("public key expired");
            }
            return super.decode(token);
        } catch (InvalidTokenException ex) {
            if (tryCreateSignatureVerifier()) {
                return super.decode(token);
            }
            throw ex;
        }
    }

    /**
     * Fetch a new public key from the AuthorizationServer.
     *
     * @return true, if we could fetch it; false, if we could not.
     */
    private boolean tryCreateSignatureVerifier() {
        long t = System.currentTimeMillis();
        if (t - lastKeyFetchTimestamp < oAuth2Properties.getSignatureVerification().getPublicKeyRefreshRateLimit()) {
            return false;
        }
        try {
            SignatureVerifier verifier = signatureVerifierClient.getSignatureVerifier();
            if(verifier!=null) {
                setVerifier(verifier);
                lastKeyFetchTimestamp = t;
                log.debug("Public key retrieved from OAuth2 server to create SignatureVerifier");
                return true;
            }
        } catch (Throwable ex) {
            log.error("could not get public key from OAuth2 server to create SignatureVerifier", ex);
        }
        return false;
    }
}

在ResourceConfiguration中定义转换器bean与tokenstore的bean,完整如下

@Configuration
@EnableResourceServer
public class ResourceConfiguration extends ResourceServerConfigurerAdapter {
    private final OAuth2Properties oAuth2Properties;

    public ResourceConfiguration(OAuth2Properties oAuth2Properties) {
        this.oAuth2Properties = oAuth2Properties;
    }


    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/api/**").authenticated()
        .and()
            .csrf()
            .disable();
    }

    @Bean
    public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(OAuth2SignatureVerifierClient signatureVerifierClient) {
        return new OAuth2JwtAccessTokenConverter(oAuth2Properties, signatureVerifierClient);
    }


    @Bean
    public RestTemplate loadBalancedRestTemplate(RestTemplateCustomizer customizer) {
        RestTemplate restTemplate = new RestTemplate();
        customizer.customize(restTemplate);
        return restTemplate;
    }
}

在app-producer-1中HelloResource定义一个新接口

@GetMapping("/world")
    public String getWorld(){
        return "aaaaaa";
    }

打开postmen
192.168.0.112:8080/app-producer-1/api/world
192.168.0.112:8080 网关地址
app-producer-1 项目名
/api/world api路径
api网关zuul 与 其他服务整合JWT(五)_第2张图片
这里我们没有用加token,说我们没有权限
下面我们就加入客户端模式token
api网关zuul 与 其他服务整合JWT(五)_第3张图片
将token加入headers中,发送请求拿到数据
api网关zuul 与 其他服务整合JWT(五)_第4张图片

你可能感兴趣的:(springcloud微服务,java,OAuth2与zuul,OAuth2,JWT)