SpringCloud--zuul网关使用

一、简单介绍

Spring Cloud Zuul的作用就是路由转发和过滤, 即将请求转发到微服务或拦截请求; Zuul默认集成了负载均衡功能。
二、简单使用
本项目基于Springboot2.0.3和Spring Cloud Finchley进行部署。

1.添加依赖pom.xml


org.springframework.cloud
spring-cloud-starter-netflix-zuul

2.配置文件application.yml

spring:
application:
name: zuul
server:
port: 8040

3.启动类ZuulApplication.java

启动类添加@EnableZuulProxy,即可支持网关路由

@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}

}

4.测试

用浏览器访问localhost:8040,即可测试是否启动项目。

三、服务化
Zuul注册到eureka server,通过url的映射转化为服务名ServiceId进行映射。

前提:

  • 先启动eureka,端口号为8761
  • 再启动微服务account,端口号为9090,注册到eureka,并存在请求localhost:9090/account/test

1.添加依赖pom.xml

添加组建服务eureka注册、发现


org.springframework.cloud
spring-cloud-starter-netflix-eureka-server

2.配置文件application.yml

注册到eureka,并且配置路由转发。

serviceId为微服务名称,path为路由地址

stripPrefix为false时,则保留account前缀,再转发url到微服务

eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
zuul:
routes:
account:
path: /account/**
serviceId: account
stripPrefix: false

3.测试

用浏览器访问localhost:8040/account/test,如果通过zuul路由,能访问account服务(端口是9090)的请求,则为成功。

网关zuul服务和account服务注册到eureka,如下图:

[图片上传失败...(image-ecc08a-1553260424830)]

四、跨域问题
转发过滤器前,在HttpServletResponse添加跨域头

1.创建CorsFilter .java

@Component
public class CorsFilter extends ZuulFilter {

@Override
public String filterType() {
return "pre"; // 可以在请求被路由之前调用
}

@Override
public int filterOrder() {
return 0;// filter执行顺序,通过数字指定 ,优先级为0,数字越大,优先级越低
}

@Override
public boolean shouldFilter() {
return true;// 是否执行该过滤器,此处为true,说明需要过滤
}

@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletResponse res= ctx.getResponse();
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE ,PUT");
res.setHeader("Access-Control-Max-Age", "3600");
res.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since,"
+ " Pragma, Last-Modified, Cache-Control, Expires, Content-Type, "
+ "X-E4M-With,userId,token,Authorization,deviceId,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Access-Control-Allow-Methods");
res.setHeader("Access-Control-Allow-Credentials", "true");
return null;
}
}

2.启动类ZuulApplication.java,添加如下

@Bean
public CorsFilter corsFilter(){
return new CorsFilter();
}

3.测试

重新启动,即可解决跨域问题。[图片上传失败...(image-4533bd-1553260424830)]

4.注意

  • 如果zuul配置了cors跨域问题,则不能在微服务下面在配置cors跨域,不然会继续存在跨域问题。
  • Filter是Zuul的核心,用来实现对外服务的控制。Filter的生命周期有4个,分别是“pre”、“routing”、“post”、“error”。具体请看教程:springcloud(十一):服务网关Zuul高级篇
五、网关权限

核心框架为Spring Cloud security。所有微服务经过网关zuul,进行权限控制,其中包括登陆和注销。

account微服务的接口前提:

  • GET /account/user/username/{username} 为根据用户名获取当前用户信息
  • GET /account/menu/user/{id} 为根据用户id获取当前用户权限菜单
  • POST /account/password/check 为根据输入密码和加密密码进行对比
  • GET /account/token/{token} 为根据token,获取当前用户
  • POST /account/token 为保存token和用户信息
  • POST /account/token/check/{token} 为检测token是否存在
  • DELETE /account/token/{token} 删除token

1.添加依赖pom.xml,feign用于访问微服务account


org.springframework.cloud
spring-cloud-starter-security


org.springframework.cloud
spring-cloud-starter-openfeign

2.添加配置

创建全局权限类:WebSecurityConfig.java

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}

/**
*不添加权限的url过滤请求,其中开启swagger
**/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.POST, "/login", "/logout")
.antMatchers("/swagger-ui.html", "/webjars/**", "/v2/**", "/swagger-resources/**")
.antMatchers("/actuator/**");

}
/**
*security基础配置
**/
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置customAccessDecisionManager
http.authorizeRequests()
//.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor() {
@Override
public  O postProcess(O fsi) {
fsi.setAccessDecisionManager(customAccessDecisionManager());
return fsi;
}
});
//替换传统的session,用token代替
http.securityContext().securityContextRepository(tokenSecurityContextRepository());
//异常信息处理
http.exceptionHandling()//
.authenticationEntryPoint(goAuthenticationEntryPoint())
.accessDeniedHandler(goAccessDeniedHandler());
//关闭csrf
http.csrf().disable();
}

@Bean
public AccessDecisionManager customAccessDecisionManager() {
return new CustomAccessDecisionManager();
}

@Bean
public GoAuthenticationEntryPoint goAuthenticationEntryPoint() {
return new GoAuthenticationEntryPoint();
}

@Bean
public GoAccessDeniedHandler goAccessDeniedHandler() {
return new GoAccessDeniedHandler();
}

@Bean
public TokenSecurityContextRepository tokenSecurityContextRepository() {
return new TokenSecurityContextRepository();
}

}

添加自定义验证类CustomAccessDecisionManager.java,用于对url过滤

/**
* 自定义验证权限
*/
@Component
public class CustomAccessDecisionManager implements AccessDecisionManager {

//decide 方法是判定是否拥有权限的决策方法
@Override
public void decide(Authentication authentication, Object object, Collection configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
String url, method;
AntPathRequestMatcher matcher;
for (GrantedAuthority ga : authentication.getAuthorities()) {
if (ga instanceof CustomGrantedAuthority) {
CustomGrantedAuthority urlGrantedAuthority = (CustomGrantedAuthority) ga;
url = urlGrantedAuthority.getUrl();
method = MethodEnum.getNameByCode(urlGrantedAuthority.getMethod());
matcher = new AntPathRequestMatcher(url);
if (matcher.matches(request)) {
//当权限表权限的method为ALL时表示拥有此路径的所有请求方式权利。
if (method.equals(request.getMethod()) || "ALL".equals(method)) {
return;
}
}
}
}
throw new AccessDeniedException("denied.error.message");
}


@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}

@Override
public boolean supports(Class clazz) {
return true;
}
}

创建自定义权限实体类CustomGrantedAuthority.java,自定义权限url

/**
* 自定义权限实体(Spring security)
*/
@Data
public class CustomGrantedAuthority implements GrantedAuthority {

private String url;
private Integer method;
private String authority;


@Override
public String getAuthority() {
return this.url + ";" + MethodEnum.getNameByCode(this.method);
}
}

创建自定义用户实体(Spring Security)

/**
* 自定义用户实体(Spring Security)
*/
@Data
public class CustomUserDetails implements UserDetails {
private Integer id;
private String userName;
private String password;
private String email;
private String fullName;
private Integer usable;
private List authorities;
private String token;
//重写
@Override
public Collection getAuthorities() {
return this.authorities;
}

@Override
public String getPassword() {
return this.password;
}

@Override
public String getUsername() {
return this.userName;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}
}

创建token管理类TokenSecurityContextRepository.java,重写SecurityContext(Spring Security),token代替传统session

@Component
public class TokenSecurityContextRepository implements SecurityContextRepository {

@Autowired
AccountFeignClient accountFeignClient;

/**
* 加载用户对象
*
* @param httpRequestResponseHolder
* @return
*/
@Override
public SecurityContext loadContext(HttpRequestResponseHolder httpRequestResponseHolder) {
String token = SystemUtil.getToken(httpRequestResponseHolder.getRequest());
if (StringUtils.isEmpty(token)) {
return this.generateNewContext();
}
SigmaResponse response = accountFeignClient.getToken(token);
CustomUser customUser = response.getData();
if (customUser == null) {
return this.generateNewContext();
}
CustomUserDetails user = new CustomUserDetails();
user.setUserName(user.getUsername());
BeanUtils.copyProperties(customUser, user);
List authorities = new ArrayList<>();
for (MenuAuth menuAuth : customUser.getAuthorities()) {
CustomGrantedAuthority authority = new CustomGrantedAuthority();
authority.setMethod(menuAuth.getMethod());
authority.setUrl(menuAuth.getUrl());
authorities.add(authority);
}
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null,
authorities);
return new SecurityContextImpl(authentication);
}

/**
* 保存用户对象
*
* @param securityContext
* @param httpServletRequest
* @param httpServletResponse
*/
@Override
public void saveContext(SecurityContext securityContext, HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) {
}

/**
* 验证用户对象
*
* @param httpServletRequest
* @return
*/
@Override
public boolean containsContext(HttpServletRequest httpServletRequest) {
String token = SystemUtil.getToken(httpServletRequest);
if (StringUtils.isEmpty(token)) {
return false;
}
SigmaResponse response = accountFeignClient.checkToken(token);
return response.getData();
}

/**
* 初始化用户对象
*
* @return
*/
protected SecurityContext generateNewContext() {
return SecurityContextHolder.createEmptyContext();
}
}

创建自定义创建返回异常权限类和未登录类GoAccessDeniedHandler.java和GoAuthenticationEntryPoint.java

/**
* 授权异常,返回信息
* 未登录,会返回
*/
@Component
public class GoAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Autowired
MessageService messageService;

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException {
response.setHeader("Content-Type", "application/json;charset=utf-8");
SigmaResponse sigmaResponse = new SigmaResponse<>();
sigmaResponse.setHeader(new SigmaResponseHeader("-1", messageService.getMessage("nologin.error.message",
request), null));
response.getWriter().print(new ObjectMapper().writeValueAsString(sigmaResponse));
response.getWriter().flush();
}
}
/**
* 权限异常,返回信息
* 无菜单权限,跨角色的权限操作会报进入此类
*/
@Component
public class GoAccessDeniedHandler implements AccessDeniedHandler {
@Autowired
MessageService messageService;

@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException exception) throws IOException {
response.setHeader("Content-Type", "application/json;charset=utf-8");
SigmaResponse sigmaResponse = new SigmaResponse<>();
sigmaResponse.setHeader(new SigmaResponseHeader("-2",
messageService.getMessage("denied.error.message", request), null));
response.getWriter().print(new ObjectMapper().writeValueAsString(sigmaResponse));
response.getWriter().flush();
}
}

创建自定义登陆和注销类LoginController.java

@RestController
@RequestMapping
@Api(value = "login & logout", tags = "login")
public class LoginController {
@Autowired
MessageService messageService;
@Autowired
AccountFeignClient accountFeignClient;

@ApiOperation(value = "user login")
@PostMapping("/login")
public SigmaResponse login(@RequestBody ReqUserLoginParam userLoginParam) {
if (userLoginParam == null
|| StringUtil.isEmpty(userLoginParam.getUsername())
|| StringUtil.isEmpty(userLoginParam.getPassword())) {
throw new MessageException("login.error.message");
}
SigmaResponse response = accountFeignClient.getUserByUsername(userLoginParam.getUsername());
UserFindResultBO user = response.getData();
//登陆验证
if (user == null) {
throw new MessageException("login.error.message");
}
SigmaResponse checkResult = accountFeignClient.checkPassword(
new ReqPasswordCheckParam(userLoginParam.getPassword(), user.getPassword()));
if (checkResult.getData() == null || checkResult.getData() == false) {
throw new MessageException("login.error.message");
}
if (user.getUsable() != 1) {
throw new MessageException("login.enableError.message");
}
SigmaResponse> menuListForLogin = accountFeignClient.getMenuListForLogin(user.getId());
List menuList = menuListForLogin.getData();
//菜单权限配置(Security框架)
List menuAuths = new ArrayList<>();
for (MenuListForLoginBO menu : menuList) {
if (menu != null && menu.getName() != null && menu.getIsMenu() == 0) {
MenuAuth authority = new MenuAuth();
authority.setUrl(menu.getUrl());
authority.setMethod(menu.getMethod());
menuAuths.add(authority);
}
}
//token生成
String token = SystemUtil.createToken();
//自定义保存redis的用户对象
CustomUser customUser = CopyUtil.copyObject(user, CustomUser.class);
customUser.setAuthorities(menuAuths);
customUser.setToken(token);
//保存到redis中
accountFeignClient.saveToken(customUser);

//自定义返回user对象
RespUserLoginParam respUserLoginParam = CopyUtil.copyObject(customUser, RespUserLoginParam.class);
// List boList = CopyUtil.copyList(menuList, MenuListResultBO.class);
// List treeJson = MenuUtil.createTreeJson(boList, true);
// respUserLoginParam.setMenus(treeJson);
//返回response对象
SigmaResponse respUserFindParamSigmaResponse = new SigmaResponse<>();
respUserFindParamSigmaResponse.setHeader(new SigmaResponseHeader("0",
messageService.getMessage("login.seccess.message"), null));
respUserFindParamSigmaResponse.setData(respUserLoginParam);
return respUserFindParamSigmaResponse;
}

@ApiOperation(value = "user logout")
@PostMapping("/logout")
public SigmaResponse login(HttpServletRequest request) {
String token = SystemUtil.getToken(request);
accountFeignClient.deleteToken(token);
SigmaResponse respUserFindParamSigmaResponse = new SigmaResponse<>();
respUserFindParamSigmaResponse.setHeader(new SigmaResponseHeader("0",
messageService.getMessage("logout.success.message"), null));
return respUserFindParamSigmaResponse;
}
}

创建Feign请求类:AccountFeignClient.java,用于访问account微服务

@FeignClient("account")
public interface AccountFeignClient {
//登陆验证
@RequestMapping(value = "/account/user/username/{username}", method = RequestMethod.GET)
SigmaResponse getUserByUsername(@PathVariable("username") String username);

@RequestMapping(value = "/account/menu/user/{id}", method = RequestMethod.GET)
SigmaResponse> getMenuListForLogin(@PathVariable("id") Integer id);

@RequestMapping(value = "/account/password/check", method = RequestMethod.POST)
SigmaResponse checkPassword(@RequestBody ReqPasswordCheckParam reqPasswordCheckParam);

//token验证
@RequestMapping(value = "/account/token/{token}", method = RequestMethod.GET)
SigmaResponse getToken(@PathVariable("token") String token);

@RequestMapping(value = "/account/token", method = RequestMethod.POST)
SigmaResponse saveToken(@RequestBody CustomUser customUser);

@RequestMapping(value = "/account/token/check/{token}", method = RequestMethod.POST)
SigmaResponse checkToken(@PathVariable("token") String token);

@RequestMapping(value = "/account/token/{token}", method = RequestMethod.DELETE)
SigmaResponse deleteToken(@PathVariable("token") String token);
}

3.测试

首先登陆localhost:8040/login,获取当前token,再去访问account微服务。[图片上传失败...(image-74079-1553260424830)][图片上传失败...(image-9602af-1553260424830)]

你可能感兴趣的:(SpringCloud--zuul网关使用)