7
写在前面

一直在写springCloud项目,每次都是新建项目然后从零开始写配置,现在写一个尽量通用的项目,方便后续搭建框架的时候直接拿过去使用。

  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模块为例子详细介绍.

  • eureka

我们知道,在为服务里面,所有模块需要被注册到一个注册中心,持续的向注册中心发送心跳以保证连接的存活。而springcloud的注册中心有consul和eureka,这里我选用的是eureka.
eureka的代码很简单,只需要在配置文件里面配置好注册地址与密码(可不设置,生产上强烈建议设置),并标识好自己不向自己注册,不被自己发现即可。

maven坐标:

 <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>

主类,不用做任何配置

@SpringBootApplication
@EnableEurekaServer
public class CrawlerEurekaApplication {

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


yml配置文件:

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坐标:

<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,还标识是网关

@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-zuul

  • 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模块框架涉及以及涉及的主要技术点)


crawler
327 声望79 粉丝

专注技术多年,曾任职京东,汉得等公司主研