之前配置了Jwt令牌和JDBC存储,今天准备集成Eureka注册中心和gateway路由网管。
前面两篇文章
OAuth2 和 SpringSecurity完成授权认证中心(一) 搭建授权认证中心
OAuth2 和 SpringSecurity完成授权认证中心(二) 通过jwt令牌,以及JDBC存储
gitee地址
创建 Eureka 注册中心
pom.xml
文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>distributed-security</artifactId>
<groupId>cn.fllday.security</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>distributed-security-discovery</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
application.yml
spring:
application:
name: distributed-discovery
server:
port: 53000 #启动端口
eureka:
server:
enable-self-preservation: false #关闭服务器自我保护,客户端心跳检测15分钟内错误达到80%服务会保护,导致别人还认为是好用的服务
eviction-interval-timer-in-ms: 10000 #清理间隔(单位毫秒,默认是60*1000)5秒将客户端剔除的服务在服务注册列表中剔除#
shouldUseReadOnlyResponseCache: true #eureka是CAP理论种基于AP策略,为了保证强一致性关闭此切换CP 默认不关闭 false关闭
client:
register-with-eureka: false #false:不作为一个客户端注册到注册中心
fetch-registry: false #为true时,可以启动,但报异常:Cannot execute request on any known server
instance-info-replication-interval-seconds: 10
serviceUrl:
defaultZone: http://localhost:${server.port}/eureka/
instance:
hostname: ${spring.cloud.client.ip-address}
prefer-ip-address: true
instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${spring.application.instance_id:${server.port}}
启动类
DiscoverServer.java
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryServer {
public static void main(String[] args) {
SpringApplication.run(DiscoveryServer.class,args);
}
}
创建网关模块
distribute-dsecurity-gateway
pom.xml
文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>distributed-security</artifactId>
<groupId>cn.fllday.security</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>distributed-security-gateway</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
application.properties
spring.application.name=gateway-server
server.port=53010
spring.main.allow-bean-definition-overriding = true
logging.level.root = info
logging.level.org.springframework = info
zuul.retryable = true
zuul.ignoredServices = *
zuul.add-host-header = true
zuul.sensitiveHeaders = *
zuul.routes.uaa-service.stripPrefix = false
zuul.routes.uaa-service.path = /uaa/**
zuul.routes.order-service.stripPrefix = false
zuul.routes.order-service.path = /order/**
eureka.client.serviceUrl.defaultZone = http://localhost:53000/eureka/
eureka.instance.preferIpAddress = true
eureka.instance.instance-id = ${spring.application.name}:${server.port}
management.endpoints.web.exposure.include = refresh,health,info,env
feign.hystrix.enabled = true
feign.compression.request.enabled = true
feign.compression.request.mime-types[0] = text/xml
feign.compression.request.mime-types[1] = application/xml
feign.compression.request.mime-types[2] = application/json
feign.compression.request.min-request-size = 2048
feign.compression.response.enabled = true
feign.httpclient.connection-timeout=10000
因为统一认证服务(uaa)与同意用户服务都是在网关下微服务,需要在网关上新增路由配置:
zuul.routes.uaa-service.stripPrefix = false
zuul.routes.uaa-service.path = /uaa/**
zuul.routes.order-service.stripPrefix = false
zuul.routes.order-service.path = /order/**
上面配置了网关接受的请求url若符合/order/**表达式,那么将被转发到order-service 服务
启动类
@SpringBootApplication
@EnableZuulProxy
@EnableDiscoveryClient
public class GatewayServer {
public static void main(String[] args) {
SpringApplication.run(GatewayServer.class,args);
}
}
配置token配置类
TokenConfig.java
@Configuration
public class TokenConfig {
private String SIGNING_KEY = "uaa123";
@Bean
public TokenStore tokenStore() {
//JWT令牌存储方案
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY); //对称秘钥,资源服务器使用该秘钥来验证
return converter;
}
}
资源配置服务
在ResouceServerConfig中定义资源服务配置,主要配置的内容就是定义一些匹配规则,描述某个客户端需要什么样的权限才能访问某个微服务。
ResouceServerConfig.java
@Configuration
public class ResouceServerConfig {
public static final String RESOURCE_ID = "res1";
//uaa资源服务配置
@Configuration
@EnableResourceServer
public class UAAServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources){
resources.tokenStore(tokenStore).resourceId(RESOURCE_ID)
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/uaa/**").permitAll();
}
}
//order资源
//uaa资源服务配置
@Configuration
@EnableResourceServer
public class OrderServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources){
resources.tokenStore(tokenStore).resourceId(RESOURCE_ID)
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/order/**").access("#oauth2.hasScope('ROLE_API')");
}
}
//配置其它的资源服务..
}
UAAServerConfig 指定了如果请求为/uaa/**
网关不进行拦截
OrderServerConfig 制定了如果请求匹配/order/**
, 也就是访问同意用户服务,接入客户端需要有scope中包含read, 并且authorities全险种需要包括,ROLE_USER
。
由于res1 这个接入客户端,read 包括ROLE_ADMIN,ROLE_USER,ROLE_API
这三个权限
安全配置:
WebSecurityConfig.java
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**").permitAll()
.and().csrf().disable();
}
}
转发明文token 给微服务
AuthFilter.java
public class AuthFilter extends ZuulFilter {
@Override
public boolean shouldFilter() {
return true;
}
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
//从安全上下文中拿 到用户身份对象
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(!(authentication instanceof OAuth2Authentication)){
return null;
}
OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) authentication;
Authentication userAuthentication = oAuth2Authentication.getUserAuthentication();
//取出用户身份信息
String principal = userAuthentication.getName();
//取出用户权限
List<String> authorities = new ArrayList<>();
//从userAuthentication取出权限,放在authorities
userAuthentication.getAuthorities().stream().forEach(c->authorities.add(((GrantedAuthority) c).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("authorities",authorities);
}
//把身份信息和权限信息放在json中,加入http的header中,转发给微服务
ctx.addZuulRequestHeader("json-token", EncryptUtil.encodeUTF8StringBase64(JSON.toJSONString(jsonToken)));
return null;
}
}
配置AuthFilter
ZuulConfig.java
@Configuration
public class ZuulConfig {
@Bean
public AuthFilter preFileter() {
return new AuthFilter();
}
@Bean
public FilterRegistrationBean corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setMaxAge(18000L);
source.registerCorsConfiguration("/**", config);
CorsFilter corsFilter = new CorsFilter(source);
FilterRegistrationBean bean = new FilterRegistrationBean(corsFilter);
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
修改order-jwt服务
添加 TokenAuthenticationFilter
TokenAuthenticationFilter.java
@Component
public class TokenAutnenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
//解析出头中的token
String token = httpServletRequest.getHeader("json-token");
if(token!=null){
String json = EncryptUtil.decodeUTF8StringBase64(token);
//将token转成json对象
JSONObject jsonObject = JSON.parseObject(json);
//用户身份信息
UserDTO userDTO = new UserDTO();
String principal = jsonObject.getString("principal");
userDTO.setUsername(principal);
// UserDTO userDTO = JSON.parseObject(jsonObject.getString("principal"), UserDTO.class);
//用户权限
JSONArray authoritiesArray = jsonObject.getJSONArray("authorities");
String[] authorities = authoritiesArray.toArray(new String[authoritiesArray.size()]);
//将用户信息和权限填充 到用户身份token对象中
UsernamePasswordAuthenticationToken authenticationToken
= new UsernamePasswordAuthenticationToken(userDTO,null, AuthorityUtils.createAuthorityList(authorities));
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
//将authenticationToken填充到安全上下文
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
filterChain.doFilter(httpServletRequest,httpServletResponse);
}
}
修改application.properties
application.properties
spring.application.name=order-service
server.port=53022
spring.main.allow-bean-definition-overriding=true
logging.level.root=debug
logging.level.org.springframework.web=info
spring.http.encoding.enabled=true
spring.http.encoding.charset=utf-8
spring.http.encoding.force=true
server.tomcat.remote-ip-header=x-forwarded-for
server.tomcat.protocol-header=x-forwarded-proto
server.use-forward-headers=true
server.servlet.context-path=/order
spring.freemarker.enabled=true
spring.freemarker.suffix=.html
spring.freemarker.request-context-attribute=rc
spring.freemarker.content-type=text/html
spring.freemarker.charset=utf-8
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
eureka.client.service-url.defaultZone=http://localhost:53000/eureka/
eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=${spring.application.name}:${server.port}
management.endpoints.web.exposure.exclude=refresh,health,info,env
集成完成之后进行测试
顺序启动 DiscoveryServer,GatewayServer,UaaJwtServer,OrderJwtServer
。
注意 ,请求的是网关 不是 微服务
查看eureka页面。 注册了三个服务,一个网关,一个授权,一个order
访问
http://localhost:53010/uaa/oauth/authorize?client_id=c1&response_type=code&scope=ROLE_ADMIM ROLE_USER ROLE_API &redirect_uri=http://www.baidu.com
获取token
访问http://localhost:53010/uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=authorization_code&code=z5gy63&redirect_uri=http://www.baidu.com
访问资源r1
localhost:53010/order/r/r1
访问r2localhost:53010/order/r/r2localhost:53010/order/r/r2
基本上就完成了。