上个章节已经做好了认证服务,但是调用服务是从先进入网关,再在网关路由到具体的服务的,现在添加了认证服务,所以在网关中需要一些配置才能让请求到达具体服务
进入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
上面我用的网关走的请求,192.168.0.112:8080是网关,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路径
这里我们没有用加token,说我们没有权限
下面我们就加入客户端模式token
将token加入headers中,发送请求拿到数据