第十四节 springcould zuul边缘路由的使用
2018-10-14 本文已影响4人
勃列日涅夫
本微服务中使用zuul 服务网关作为边缘路由,在oauth2中同时它本身也是资源服务
- 作为资源服务的部分配置和资源服务器api-server模块相似,同时也使用https,所以安全配置和security-server配置相似
具体参考如下:
- 生成证书keystore.jks,生成方式可参考:
(安全服务模块中的证书生成部分)[https://www.jianshu.com/p/6e11e6ab0a85] - application.yml配置
#ssl配置
server:
ssl:
key-store: classpath:keystore.jks
key-store-password: password
key-password: password
port: 8765
compression:
enabled: true
#oauth2 配置
security:
oauth2:
clientId: client
clientSecret: password
userAuthorizationUri: https://localhost:9001/oauth/authorize
grant-type: password
scope: apiAccess
access-token-uri: https://localhost:9001/oauth/token
resource:
userInfoUri: https://localhost:9001/user
authorization:
checkTokenAccess: http://localhost:9001/oauth/check_token
basic:
enabled: false
# zuul路由配置
zuul:
ignoredServices: "*"
routes:
restaurantapi:
path: /api/**
serviceId: api-service
stripPrefix: true
resource:
path: /api/**
url: http://localhost:9000
user:
path: /user/**
url: https://localhost:9001/user
### 其他配置略。。。
- Security和配置
@Configuration
@EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private BaseUserDetailService baseUserDetailService;
//Spring Security 4.x -> 5.x 會無法直接注入AuthenticationManager,下面解決
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 用户验证
* @param auth
*/
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
auth.userDetailsService(baseUserDetailService) .passwordEncoder(passwordEncoder());
}
/**
* @param http
* WebSecurityConfigurerAdapter和ResourceServerConfigurerAdapter二者是分工协作的
* @throws Exception
* WebSecurityConfigurerAdapter不拦截oauth要开放的资源
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http // 配置登陆页/login并允许访问
.formLogin().permitAll()
// 登出页
.and().logout().logoutUrl("/logout").logoutSuccessUrl("/")
// 其余所有请求全部需要鉴权认证
.and().authorizeRequests().anyRequest().authenticated()
// 由于使用的是JWT,我们这里不需要csrf
.and().csrf().disable();
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
// 设置userDetailsService
provider.setUserDetailsService(baseUserDetailService);
// 禁止隐藏用户未找到异常
provider.setHideUserNotFoundExceptions(false);
// 使用BCrypt(BCryptPasswordEncoder方法采用SHA-256 +随机盐+密钥)进行密码的hash
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
- Resource配置
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//spring.security.oauth2.client
// @Autowired
// private OAuth2ClientProperties oAuth2ClientProperties;
// @Autowired
// private AuthorizationServerProperties authorizationServerProperties;
@Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.tokenServices(tokenServices());//.resourceId(SPARKLR_RESOURCE_ID);
}
@Bean
public ResourceServerTokenServices tokenServices() {
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
//这里配置远程校验token的地址,(也可以使用其他方式,例如本地校验)
remoteTokenServices.setCheckTokenEndpointUrl("https://security-service/oauth/check_token");
//为方便测试使用硬编码,要和security-server配置的相同(security服务会校验客户端信息)
remoteTokenServices.setClientId("client");
remoteTokenServices.setClientSecret("password");
remoteTokenServices.setRestTemplate(restTemplate());
//使用默认令牌数据的存储
remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
return remoteTokenServices;
}
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
//httpRequestFactory()
RestTemplate restTemplate = new RestTemplate();
List<HttpMessageConverter<?>> converters = restTemplate.getMessageConverters();
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter jsonConverter = (MappingJackson2HttpMessageConverter) converter;
jsonConverter.setObjectMapper(new ObjectMapper());
jsonConverter.setSupportedMediaTypes(ImmutableList.of(new MediaType("application", "json", MappingJackson2HttpMessageConverter.DEFAULT_CHARSET), new MediaType("text", "javascript", MappingJackson2HttpMessageConverter.DEFAULT_CHARSET)));
}
}
return restTemplate;
}
@Bean
public AccessTokenConverter accessTokenConverter() {
return new DefaultAccessTokenConverter();
}
}
- 配置完基本已经可用,但作为边缘路由,它还具备其他很多有用的功能,比如可对请求前后过滤操作如下:
@Component
public class MyFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(MyFilter.class);
@Override
public String filterType() {
// 前置过滤器
return "pre";
}
@Override
public int filterOrder() {
// 优先级为0,数字越大,优先级越低
return 0;
}
@Override
public boolean shouldFilter() {
// 是否执行该过滤器,此处为true,说明需要过滤
return true;
}
//执行过程方法
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString()));
Object accessToken = request.getHeader("Authorization");
// Object accessToken = request.getParameter("token");
if(accessToken == null) {
log.warn("token is empty");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try {
ctx.getResponse().getWriter().write("token is empty");
}catch (Exception e){}
return null;
}
log.info("ok");
return null;
}
}
- 只需要继承ZuulFilter 覆写run方法作为要过滤执行的方法,filterType方法表示这个过滤器的类型(前置还是后置等。。)
其他具体配置及功能可参考源码zuul服务源码
需要说明需要启动本zuul项目,需要依赖eureka server、security-server、以及其他业务服务