OAuth2 和 SpringSecurity完成授權認證中心(三) 集成Eureka註冊中心和gateway路由網關(有項目源碼)

項目gitee 地址

之前配置了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
在這裏插入圖片描述
基本上就完成了。

項目最終結構

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章