第十四節 springcould zuul邊緣路由的使用
本微服務中使用zuul 服務閘道器作為邊緣路由,在oauth2中同時它本身也是資源服務
-
作為資源服務的部分配置和資源伺服器api-server模組相似,同時也使用https,所以安全配置和security-server配置相似
具體參考如下:
-
生成證書keystore.jks,生成方式可參考:
(安全服務模組中的證書生成部分)[ofollow,noindex">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(){ returnnew 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、以及其他業務服務