寫在前面
一直在寫springCloud項目,每次都是新建項目而後從零開始寫配置,如今寫一個儘可能通用的項目,方便後續搭建框架的時候直接拿過去使用。mysql
一 須要搭建的組件
須要搭建的組件主要有7個模塊(feign會集成到具體模塊),這邊我回詳細記錄eureka,zuul,auth,config,user.由於前四者是springCloud的配置。須要詳細介紹,而具體的業務邏輯代碼會在具體模塊,這裏我將以user模塊爲例子詳細介紹.nginx
咱們知道,在爲服務裏面,全部模塊須要被註冊到一個註冊中心,持續的向註冊中心發送心跳以保證鏈接的存活。而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
上面咱們把註冊中心搭建好了,訪問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 的始終爲空,原來須要在請求的時候,將這個綁定進去:以下截圖
這個須要與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模塊發佈成功
待寫(這個模塊等我把後續文章寫完了,回過頭來補起來。下一篇文章主要介紹user模塊框架涉及以及涉及的主要技術點)