This is the second day of my participation in Gwen Challenge. This article is participating in “Java Theme Month – Java Development in Action”, see the activity link for details.

Around November 2018, Spencer Gibb, co-founder of Spring Cloud, announced on the blog page of Spring’s official website that Alibaba was open source Spring Cloud Alibaba and released the first preview version. This was later announced on Spring Cloud’s official Twitter account.

First, environmental preparation

  • Spring the Boot: 2.1.8

  • Spring Cloud: Greenwich.SR3

  • Spring Cloud Alibaba: 0.9.0.RELEASE

  • Maven: 3.5.4

  • Java 1.8 +

  • Oauth2 (Spring Security 5.1.6 +)

Second, the actual combat

The project module

It includes authentication center, service provider, service consumer, and gateway

In actual combat

In this example, Nacos is used as the registry and configuration center, and its dependencies need to be introduced:

<dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      </dependency>

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
      </dependency>

Copy the code

Oauth2 dependencies:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
Copy the code

At the same time, redis is used to deal with the authentication information storage:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Copy the code

Next you need to prepare the configuration file YAMl:

management: endpoint: restart: enabled: true health: enabled: true info: enabled: true spring: application: name: Oauth-cas cloud: nacos: Discovery: server-addr: 127.0.0.1:8848 config: server-addr: 127.0.0.1:8848 refreshable-dataids: Physical properties, the properties redis: # redis related configuration database: 8 host: 127.0.0.1 port: 6379 password: QWQWSQ jedis: pool: max-active: 8 max-idle: 8 min-idle: 0 timeout: 10000msCopy the code

Note that this configuration file needs to be bootstrap, otherwise it may fail. You can try it for yourself.

Next comes application:

server:
  port: 2000
  undertow:
    accesslog:
      enabled: false
      pattern: combined
  servlet:
    session:
      timeout: PT120M


client:
  http:
    request:
      connectTimeout: 8000
      readTimeout: 30000

mybatis:
  mapperLocations: classpath:mapper/*.xml
  typeAliasesPackage: com.damon.*.model
Copy the code

After the configuration is complete, the main function is completed. The authentication server is configured most in the function. Verifying accounts and passwords, storing tokens, checking tokens, refreshing tokens, and so on are the work of the authentication server:

package com.damon;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;


@Configuration
@EnableAutoConfiguration
@ComponentScan(basePackages = {"com.damon"})
@EnableDiscoveryClient
public class CasApp {
  public static void main(String[] args) {
    SpringApplication.run(CasApp.class, args);
  }
}
Copy the code

Next, configure several Oauth2 server configuration classes: AuthorizationServerConfig, ResourceServerConfig, SecurityConfig, RedisTokenStoreConfig, MyRedisTokenStore, UserOAuth2WebRespo NseExceptionTranslator, AuthenticationEntryPointHandle, etc.

One of the most important is the login function, here for the sake of demonstration, the user name, password and role are written in the code, in the formal environment, here should be from the database or other places according to the user name encrypted password and role. Account admin, password 123456, and give this user the role “ROLE_ADMIN” :

@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { logger.info("clientIp is: {} ,username: {}", IpUtil.getClientIp(req), username); logger.info("serverIp is: {}", IpUtil.getCurrentIp()); / / query database operations try {SysUser user = userMapper. GetUserByUsername (username); if (user == null) { logger.error("user not exist"); throw new UsernameNotFoundException("username is not exist"); //throw new UsernameNotFoundException("the user is not found"); } else {// The user role should also be obtained in the database, here simplified String role = ""; if(user.getIsAdmin() == 1) { role = "admin"; } List<SimpleGrantedAuthority> authorities = Lists.newArrayList(); authorities.add(new SimpleGrantedAuthority(role)); //String password = passwordEncoder.encode("123456"); //return new User(username, password, authorities); Return new User(username, user.getPassword(), authorities); } } catch (Exception e) { logger.error("database collect failed"); logger.error(e.getMessage(), e); throw new UsernameNotFoundException(e.getMessage()); }}Copy the code

The loadUserByUsername function validates the database password and authorizes the user to the role.

At this point, the authentication center server is complete. The above uses Nacos as a registry to be discovered by client services and to provide configuration management.

Install Nacos

Download Nacos at github.com/alibaba/nac…

Version: v1.2.1

Perform:

  • Linux/Unix/Mac: sh startup.sh -m standalone

  • Windows:cmd startup.cmd -m standalone

Startup is complete, visit: http://127.0.0.1:8848/nacos/, can enter the Nacos service management page, specific as follows:

The default username and password are both nacos.

After login, open service Management and you can see the list of services registered with Nacos:

You can click Config Management to view the configuration:

If no service configuration is configured, create:

The above description of Nacos as a registry and configuration center is simple enough.

Next we’ll look at the service provider code, starting with the pom.xml file:

     
    <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      </dependency>

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-alibaba-nacos-config</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-netflix-hystrix</artifactId>
        </dependency>
Copy the code

Here, as always, is the introduction of service registration and discovery dependencies that depend on Oauth2 and Nacos.

To configure the bootstrap file, let’s first look at the configuration of the service application itself and the Nacos configuration that the service needs to connect to the registry and configuration center:

management: endpoint: restart: enabled: true health: enabled: true info: enabled: true spring: application: name: Provider-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 config: server-addr: 127.0.0.1:8848 refreshable - dataids: physical properties, the properties of cas - server - the url: http://oauth-cas #http://localhost:2000# Set the address that can be accessedCopy the code

After that, we need to configure Oauth2 for the client, such as: application information, authentication mode in authorization code mode, authentication mode in password mode, whether to enable load balancing, and token authentication when accessing the client after authentication:

Client: client-id: provider-service client-secret: Provider-service-123 user-authorization-URI: ${cas-server-URL}/oauth/authorize # Indicates the access-token-URI required by the authorization code authentication mode. Resource: loadBalanced: true # JWT: # JWT: # JWT: ${cas-server-url}/oauth/token # ${cas-server-url}/oauth/token_key #key-value: test_jwt_sign_key id: Provider: /auth/user user-info-uri: /auth/user ${cas-server-url}/api/user prefer-token-info: false #token-info-uri: authorization: check-token-access: ${cas-server-url}/oauth/check_tokenCopy the code

When the Web server receives the request from the UI client, it needs to take the token in the request to the authentication server for token authentication. It needs to request this interface:

security:
  oauth2:
    authorization:
      check-token-access: ${cas-server-url}/oauth/check_token
Copy the code

Finally, we need some cookie configuration to prevent collisions:

server: port: 2001 undertow: accesslog: enabled: false pattern: combined servlet: session: timeout: PT120M cookie: Name: provider-service-sessionId # Prevent Cookie conflicts, which may cause login authentication failureCopy the code

There are also some Settings for requests, such as request timeout and other timeout times, and some basic database Settings:

client:
  http:
    request:
      connectTimeout: 8000
      readTimeout: 30000

mybatis:
  mapperLocations: classpath:mapper/*.xml
  typeAliasesPackage: com.damon.*.model
Copy the code

In addition, when load balancing is set, service refreshes need to be traversed continuously. When service load is set, polling is adopted, including setting of retry policy:

backend: ribbon: client: enabled: true ServerListRefreshInterval: 5000 ribbon: ConnectTimeout: ReadTimeout: 1000 eagle-load: enabled: true Clients: Request - cas, consumer - service MaxAutoRetries: 1 # service for the first request of retries MaxAutoRetriesNextServer: 1 # to retry the next service the maximum number of (not including the first service) # listOfServers: localhost: 5556, localhost: 5557 # ServerListRefreshInterval: 2000 OkToRetryOnAllOperations: true NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRuleCopy the code

Let’s look at the startup class:

package com.damon; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @EnableAutoConfiguration @ComponentScan(basePackages = {"com.damon"}) @EnableDiscoveryClient @EnableOAuth2Sso public class ProviderApp { public static void main(String[] args) { SpringApplication.run(ProviderApp.class, args); }}Copy the code

Note: The @enableDiscoveryClient and @enableoAuth2SSO annotations are required.

In this case, you also need to configure ResourceServerConfig and SecurityConfig.

If you need a database, you can add:

@Bean(name = "dataSource") public DataSource getDataSource() throws Exception { Properties props = new Properties(); props.put("driverClassName", envConfig.getJdbc_driverClassName()); props.put("url", envConfig.getJdbc_url()); props.put("username", envConfig.getJdbc_username()); props.put("password", envConfig.getJdbc_password()); return DruidDataSourceFactory.createDataSource(props); } @Bean public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean fb = new SqlSessionFactoryBean(); Fb.setdatasource (dataSource); // The following two sentences are only for *.xml files, if the entire persistence layer operation does not need to use XML files (just annotations can be done), Do not add fb. SetTypeAliasesPackage (env. GetProperty (" mybatis. TypeAliasesPackage ")); // Specify base package fb.setmapperlocations (new) PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations"))); PageHelper PageHelper = new PageHelper(); Properties props = new Properties(); // If rationalization is enabled, pageNum<1 will query the first page; if pageNum>pages will query the last page; If pageNum<1 or pageNum>pages returns props. SetProperty ("reasonable", "true"); // specify the database props. SetProperty ("dialect", "mysql"); // Support for passing paging parameters through Mapper interface parameters. SetProperty ("supportMethodsArguments", "true"); SetProperty ("returnPageInfo", "check"); // always returnPageInfo,check if the return type is PageInfo,none returns Page props. SetProperty ("returnPageInfo", "check"); props.setProperty("params", "count=countSql"); pageHelper.setProperties(props); Fb.setplugins (new Interceptor[] {pageHelper}); try { return fb.getObject(); } catch (Exception e) { throw e; } } @Bean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); }Copy the code

For database transactions, Mybatis needs to be configured:

@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) throws Exception{
   return new DataSourceTransactionManager(dataSource);
}
Copy the code

Write a new interface class:


@GetMapping("/getCurrentUser")
@PreAuthorize("hasAuthority('admin')")
public Object getCurrentUser(Authentication authentication) {
  logger.info("test password mode");
    return authentication;
}

Copy the code

It’s basically done in one code. Here’s the test:

Certification:

curl -i -X POST -d "username=admin&password=123456&grant_type=password&client_id=provider-service&client_secret=provider-service-123" http://localhost:5555/oauth-cas/oauth/token
Copy the code

After getting the token:

curl -i -H "Accept: application/json" -H "Authorization:bearer f4a42baa-a24a-4342-a00b-32cb135afce9" -X GET http://localhost:5555/provider-service/api/user/getCurrentUser
Copy the code

Here we use port 5555, which is a gateway service. Well, while we’re at it, let’s look at gateways and introduce dependencies:

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <! Reactive Stream redis --> reactive Stream redis -- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency> --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-commons</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</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-netflix-hystrix</artifactId> </dependency>Copy the code

Nacos is also used to discover services.

In addition, we did not configure routes for each service separately, but used the mode of service discovery automatic registration route. The registration configuration here was changed to:

spring: cloud: gateway: discovery: locator: enabled: true lowerCaseServiceId: true nacos: discovery: server-addr: 127.0.0.1:8848 config: server - addr: 127.0.0.1:8848 refreshable - dataids:. Physical properties, the propertiesCopy the code

Ok, after the gateway is configured, the service can be seen in the Nacos Dashboard after startup, indicating that the service is successfully registered. You can then use it to invoke other services. Curl command

curl -i -H "Accept: application/json" -H "Authorization:bearer f4a42baa-a24a-4342-a00b-32cb135afce9" -X GET http://localhost:5555/consumer-service/api/order/getUserInfo
Copy the code

The authentication center, service provider, service consumer, service registration and discovery, and configuration center have been completed.