從0手寫springCloud項目(模塊搭建)

寫在前面

一直在寫springCloud項目,每次都是新建項目而後從零開始寫配置,如今寫一個儘可能通用的項目,方便後續搭建框架的時候直接拿過去使用。mysql

  1. 須要搭建的組件(模塊)有:
    eureka(認證),zuul(網關),auth(認證),config(配置中心),user(用戶),order(訂單),pay(支付),feign...
  2. 這邊主要想涉及到的框架技術有:springcloud,springboot2,oauth2,springSecurity,liquibase,lcn(5.0.2),mybatisplus,logback,redis,mysql,swagger2,poi
  3. 須要搭建、支持的技術
    github,jenkins(自動發佈),maven私服,nginx,redis,mysql5.7,jdk1.8,swagger2,rabbitmq
一 須要搭建的組件

須要搭建的組件主要有7個模塊(feign會集成到具體模塊),這邊我回詳細記錄eureka,zuul,auth,config,user.由於前四者是springCloud的配置。須要詳細介紹,而具體的業務邏輯代碼會在具體模塊,這裏我將以user模塊爲例子詳細介紹.nginx

  • eureka

咱們知道,在爲服務裏面,全部模塊須要被註冊到一個註冊中心,持續的向註冊中心發送心跳以保證鏈接的存活。而springcloud的註冊中心有consul和eureka,這裏我選用的是eureka.
eureka的代碼很簡單,只須要在配置文件裏面配置好註冊地址與密碼(可不設置,生產上強烈建議設置),並標識好本身不向本身註冊,不被本身發現便可。git

maven座標:github

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--我是用的springboot2.1.3若是是springboot1.5.x請不用這個-->
    <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-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

主類,不用作任何配置web

@SpringBootApplication
@EnableEurekaServer
public class CrawlerEurekaApplication {

    public static void main(String[] args) {
        SpringApplication.run(CrawlerEurekaApplication.class, args);
    }
}

yml配置文件:redis

spring:
  application:
    name: crawler-eureka

server:
  host: http://localhost
  port: 9990
eureka:
  client:
    fetch-registry: false
    register-with-eureka: false
    service-url:
      defaultZone: ${server.host}:${server.port}/eureka/
  instance:
    prefer-ip-address: true
  • zuul

上面咱們把註冊中心搭建好了,訪問localhost:9990就能夠看到eureka的控制檯。可是咱們看不到一個服務註冊上去了。如今咱們搭建一個網關,由於在實際項目中,咱們會有不少個微服務模塊,而服務器只會向外暴露一個端口,其餘的經過相對路徑轉發。這樣也是爲了安全和方便管理,有點nginx的感受。
網關的配置也不復雜:pom座標:spring

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <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-zuul</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

主類除了標識爲eureka-client,還標識是網關sql

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

yml配置安全

server:
  port: 9996
spring:
  application:
    name: crawler-zuul
  redis:
    host: localhost
    port: 6379
    password: 123456

zuul:
  routes:
    feign-auth:
      path: /auth/**
      serviceId: crawler-auth
      strip-prefix: true
      custom-sensitive-headers: true
    feign-user:
      path: /user/**
      serviceId: crawler-goddess
      sensitiveHeaders:

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:9990/eureka/
  instance:
    prefer-ip-address: true

logging:
  level:
    ROOT: info
    org.springframework.web: info

ribbon:
  ReadTimeout: 6000000
  SocketTimeout: 6000000
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 600000

啓動項目,再次打開localhost:9990能夠發現多了一個crawler-zuulspringboot

  • auth
    auth模塊主要是供其餘模塊接口進入時提供認證。這裏使用的是oauth2.3.3和springSecurity5.1.5(security5與以前的版本略有區別,接下來會介紹到)
    pom:

    <dependencies>
    
       <!--common裏面有了絕大部分的pom座標-->
       <dependency>
           <groupId>cn.iamcrawler</groupId>
           <artifactId>crawler_common</artifactId>
           <version>1.0.1</version>
       </dependency>
    
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
    
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
           <version>2.1.0.RELEASE</version>
       </dependency>
    
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-openfeign</artifactId>
           <version>2.1.0.RELEASE</version>
       </dependency>
    
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-actuator</artifactId>
       </dependency>
    
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-security</artifactId>
           <exclusions>
               <!--舊版本 redis操做有問題-->
               <exclusion>
                   <artifactId>spring-security-oauth2</artifactId>
                   <groupId>org.springframework.security.oauth</groupId>
               </exclusion>
           </exclusions>
       </dependency>
    
    
    
     <dependency>
           <groupId>org.springframework.security.oauth</groupId>
           <artifactId>spring-security-oauth2</artifactId>
           <version>2.3.3.RELEASE</version>
    </dependency>
    </dependencies>

    yml:

    server:
         port: 9992
     spring:
         datasource:
           url: jdbc:mysql://www.iamcrawler.cn:3306/goddess?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=UTC
           username: root
           password: 123456
           driver-class-name:  com.mysql.jdbc.Driver
         redis:
           host: www.iamcrawler.cn
           port: 6379
           password:
           database: 0
         application:
           name: crawler-auth
       eureka:
         client:
           service-url:
             defaultZone: http://localhost:9990/eureka/
       management:
         endpoints:
           web:
             exposure:
               include: health,info,loggers

下面介紹一下主要的兩個配置類(sercurity+oauth2)和重寫的userDetailService,通常其餘人的教程裏會把oauth2的配置又分爲資源服務器和認證服務器兩個,我這裏寫在同一個配置裏面了,道理都是同樣的。

/**
     * Created by liuliang
     * on 2019/6/19
     */

@Configuration
@EnableWebSecurity
//開啓權限認證
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private DomainUserDetailsService userDetailsService;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return charSequence.toString();
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return Objects.equals(charSequence.toString(),s);
            }
        };
    }

    /**
     * 這一步的配置是必不可少的,不然SpringBoot會自動配置一個AuthenticationManager,覆蓋掉內存中的用戶
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        AuthenticationManager manager = super.authenticationManagerBean();
        return manager;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http
                .requestMatchers().anyRequest()
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/*").permitAll()
        ;
    }
}

----------------類分割線----------------------------

  
/**
 * Created by liuliang
 * on 2019/6/19
 */

@Configuration
public class OAuth2ServerConfig {

    private static final String DEMO_RESOURCE_ID = "text";

    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.resourceId(DEMO_RESOURCE_ID).stateless(true);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.headers().frameOptions().disable();
            http
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                    .and()
                    .authorizeRequests()
                    .antMatchers("/loggers/**").permitAll()
                    .and()
                    .authorizeRequests()
                    .antMatchers("/templates/**").permitAll()
            ;

        }
    }


    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

        @Autowired
        AuthenticationManager authenticationManager;
        @Autowired
        RedisConnectionFactory redisConnectionFactory;


        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            //配置兩個客戶端,一個用於password認證一個用於client認證
            clients.inMemory().withClient("client_1")
                    .resourceIds(DEMO_RESOURCE_ID)
                    .authorizedGrantTypes("client_credentials", "refresh_token")
                    .scopes("select")
                    .authorities("client")
                    .secret("{noop}1234567")
                    .and().withClient("client_2")
                    .resourceIds(DEMO_RESOURCE_ID)
                    .authorizedGrantTypes("password", "refresh_token")
                    .scopes("select")
                    .authorities("client")
                    .secret("{noop}1234567");
        }

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints
                    .tokenStore(new RedisTokenStore(redisConnectionFactory))
                    .authenticationManager(authenticationManager);
        }

        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
            //容許表單認證
            oauthServer.allowFormAuthenticationForClients();
        }

    }

}

----------------類分割線----------------------------

  /**
 * Created by liuliang
 * on 2019/6/18
 */
@Component
@Slf4j
public class DomainUserDetailsService implements UserDetailsService {

    @Resource
    private GoddessUserService userService ;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        GoddessUser goddessUser = userService.getOne(new QueryWrapper<GoddessUser>().eq("user_name", s));
        log.info("user:"+goddessUser.getUsername()+" login success!");
        return goddessUser;
    }
}

到這裏主要的配置就寫完了,可是這臉我想多說明幾句。由於以前使用的是springboot1.5.9版本+springSecurity4的版本集成oauth2,如今此次版本升級,使用的是springboot2.1.3+springSecurity5。在代碼沒有任何變化的時候,獲取token老是會拋出各類認證失敗的問題,好比「Encoded password does not look like BCrypt」,「id is null」等問題,百度好久都說明緣由,說security5添加了密碼加密,可是解答的部分都很水,最後我經過debug源碼的方式,發現有一個client_secret 的始終爲空,原來須要在請求的時候,將這個綁定進去:以下截圖

clipboard.png

這個須要與OAuth2ServerConfig類配置文件裏面認證服務類AuthorizationServerConfiguration 的 clients.inMemory()... .secret("{noop}1234567")對應,他是從client這裏取的。
ps:我這裏爲了測試簡單,沒有將密碼加密,即便用的是{noop}模式,建議生產上改一下,加密會比較安全...

好了,如今訪問如上截圖的接口,會返回以下:

{
    "access_token": "1c72d16d-0647-4b57-b63a-eb4e4a197804",
    "token_type": "bearer",
    "refresh_token": "09eddee3-5ce9-4904-8c23-acfe738579c9",
    "expires_in": 43198,
    "scope": "select"
}

至此,auth模塊發佈成功

  • config

待寫(這個模塊等我把後續文章寫完了,回過頭來補起來。下一篇文章主要介紹user模塊框架涉及以及涉及的主要技術點)

相關文章
相關標籤/搜索