Zuul配置OAuth资源服务器
1.分析
我们都知道Zuul是网关,所有的请求都要经过这里,再到指定的资源服务器,但是经过Zuul之后,你携带的token Zuul是不会携带着去访问 指定的资源服务器的,所以会造成一个你携带了token但是还是显示你没有认证
接下来我将展示我的代码,并解释其中的原理
2.
ResourceServerConfig.java
因为所有请求都要经过网关Zuul,所以这里OAuth配置要配置所有的资源服务器以及认证服务器的资源配置,和安全配置
@Configuration
public class ResourceServerConfig {
@Autowired
private TokenStore tokenStore;
@Autowired
private AuthExceptionEntryPoint authExceptionEntryPoint;
//认证服务器的配置
@Configuration
@EnableResourceServer
public class AuthorizationServer extends ResourceServerConfigurerAdapter{
public String RESOURCE_ID = "zuul";
//资源服务安全配置
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources
.tokenStore(tokenStore)//令牌存储验证服务,让资源服务自己验证token
.resourceId(RESOURCE_ID)//资源ID
.stateless(true);//会话机制stateless开启
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/uaa/**")
.permitAll();
}
}
//emplyee资源服务器的配置
@Configuration
@EnableResourceServer
public class ResourceServer extends ResourceServerConfigurerAdapter{
public String RESOURCE_ID = "employee";
//资源服务安全配置
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources
.tokenStore(tokenStore)//令牌存储验证服务,让资源服务自己验证token
.resourceId(RESOURCE_ID)//资源ID
.authenticationEntryPoint(authExceptionEntryPoint)//配置token异常的处理
.stateless(true);//会话机制stateless开启
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/employee/**").authenticated();
}
}
/**
* ......到时加其他资源服务器 再配置
*/
}
AuthFilter.java
当请求携带token经过认证 并放入认证对象中,经过此过滤器,把认证对象的信息明文放入转发给资源服务器请求的请求头中
public class AuthFilter extends ZuulFilter {
//filter类型 随便写但是要有意义的
@Override
public String filterType() {
return "pre";
}
//数值越小 越优先
@Override
public int filterOrder() {
return 0;
}
//是否过滤
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//如果不是 OAuth2Authentication 认证对象 则返回null
if(!(authentication instanceof OAuth2Authentication)){
return null;
}
OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) authentication;
Authentication userAuthentication = oAuth2Authentication.getUserAuthentication();
//取出用户名
String principal = userAuthentication.getName();
/**
* 组装明文token,转发给微服务,放入header,名称为json-token
*/
final List<String> authorities = new ArrayList<>();
userAuthentication.getAuthorities().stream().forEach(s -> authorities.add(((GrantedAuthority)s).getAuthority()));
OAuth2Request oAuth2Request = oAuth2Authentication.getOAuth2Request();
Map<String, String> requestParameters = oAuth2Request.getRequestParameters();
Map<String, Object> jsonToken = new HashMap<>(requestParameters);
if (userAuthentication != null) {
jsonToken.put("principal",principal);
jsonToken.put("authroities",authorities);
}
try {
String jsonStrToken = new ObjectMapper().writeValueAsString(jsonToken);
context.addZuulRequestHeader("json-token", Base64.getEncoder().encodeToString(jsonStrToken.getBytes()));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
ZuulConfig.java
因为所有请求都要经过网关Zuul,在这里配置支持跨域请求的配置以及,配置上面的过滤器
@Configuration
public class ZuulConfig {
@Bean
public AuthFilter authFilter(){
return new AuthFilter();
}
//支持跨域请求配置
@Bean
public FilterRegistrationBean filterRegistrationBean(){
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
source.registerCorsConfiguration("/**",corsConfiguration);
CorsFilter corsFilter = new CorsFilter(source);
FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>(corsFilter);
filterFilterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return filterFilterRegistrationBean;
}
}
WebSecurityConfig.java
这里将放行所有请求,把认证的功能交由资源服务器的认证
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**").permitAll()
.and()
.csrf().disable()
.cors().disable();
}
}
TokenStoreConfig.java
配置token存储策略和token转换器,以便Zuul将请求头中的token解析出来放入认证对象中
@Configuration
public class TokenStoreConfig {
private String SIGNING_KEY = "lzj";
@Bean
public TokenStore tokenStore(){
//JWT令牌存储方案
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}
application.yml
添加以下配置信息到配置文件中
#配置下zuul的超时时间,因zuul启用了ribbon的负载均衡,还需要设置ribbon的超时时间,注意ribbon的超时时间要小于zuul超时时间 。
zuul:
host:
connect-timeout-millis: 15000 #HTTP连接超时要比Hystrix的大
socket-timeout-millis: 60000 #socket超时
#忽略框架默认的服务映射路径
ignored-services: '*'
#不忽略任何头部信息,所有header都转发到下游的资源服务器
sensitive-headers: '*'
retryable: true #zuul的重试机制开启 需要spring-retry依赖
add-host-header: true #加上主机头信息
routes:
oauth-server:
serviceId: oauth-server
path: /uaa/**
employee:
serviceId: employee
path: /employee/**
ribbon:
ReadTimeout: 10000
ConnectTimeout: 10000
可以看到我的配置文件配置了retryable 重试机制,这需要引入一个依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
如果没有配置重新机制,在如果Zuul访问的资源服务器宕机,他不会触发Hystrix,而是返回错误页面,这不是我们想看到的,这时候你配置了重试机制,他就会重新连接一次,连接失败换其他的资源服务。
到此你还需要到资源服务器配置一个过滤器
这个过滤器的作用是,第一个过滤请求,把请求中请求头的明文认证信息放入到认证对象中,以完成资源服务器的认证
TokenAuthenticationFilter.java
public class TokenAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String token = httpServletRequest.getHeader("json-token");
if(token != null){
String jsonStrToken = Base64.getDecoder().decode(token).toString();
Map map = new ObjectMapper().readValue(jsonStrToken, Map.class);
//用户名
String principal = (String) map.get("principal");
//用户权限
List<String> authroities = (List<String>) map.get("authroities");
String[] authroitiesArray = authroities.toArray(new String[authroities.size()]);
//创建authenticationtoken对象
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
= new UsernamePasswordAuthenticationToken(principal, null, AuthorityUtils.createAuthorityList(authroitiesArray));
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
//将对象填充到安全上下文中
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
//放行过滤器
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
大功告成!!!!
测试
一键查询淘宝/拼多多内部优惠券,每日大额外卖红包,购物省钱的宝藏工具