Spring Cloud實戰系列(九) - 服務認證受權Spring Cloud OAuth 2.0

相關

  1. Spring Cloud實戰系列(一) - 服務註冊與發現Eureka php

  2. Spring Cloud實戰系列(二) - 客戶端調用Rest + Ribbon java

  3. Spring Cloud實戰系列(三) - 聲明式客戶端Feign mysql

  4. Spring Cloud實戰系列(四) - 熔斷器Hystrix git

  5. Spring Cloud實戰系列(五) - 服務網關Zuul github

  6. Spring Cloud實戰系列(六) - 分佈式配置中心Spring Cloud Configweb

  7. Spring Cloud實戰系列(七) - 服務鏈路追蹤Spring Cloud Sleuthspring

  8. Spring Cloud實戰系列(八) - 微服務監控Spring Boot Adminsql

  9. Spring Cloud實戰系列(九) - 服務認證受權Spring Cloud OAuth 2.0 數據庫

  10. Spring Cloud實戰系列(十) - 單點登陸JWT與Spring Security OAuth apache

前言

OAuth 2.0 是介於 用戶資源第三方應用 之間的一個 中間層,它把 資源第三方應用 隔開,使得 第三方應用 沒法直接訪問 資源,從而起到 保護資源 的做用。爲了訪問這種 受限資源第三方應用(客戶端)在訪問的時候須要 提供憑證

正文

1. OAuth 2.0簡介

認證受權 的過程當中,主要包含如下 3 種角色:

  • 服務提供方: Authorization Server

  • 資源持有者: Resource Server

  • 客戶端: Client

OAuth 2.0認證流程 如圖所示,具體以下:

  1. 用戶資源持有者)打開 客戶端客戶端 詢問 用戶受權

  2. 用戶 贊成受權。

  3. 客戶端受權服務器 申請受權。

  4. 受權服務器客戶端 進行認證,也包括 用戶信息 的認證,認證成功後受權給予 令牌

  5. 客戶端 獲取令牌後,攜帶令牌資源服務器 請求資源。

  6. 資源服務器 確認令牌正確無誤,向 客戶端 發放資源。

OAuth2 Provider 的角色被分爲 Authorization Server受權服務)和 Resource Service資源服務),一般它們不在同一個服務中,可能一個 Authorization Service 對應 多個 Resource ServiceSpring OAuth2.0 需配合 Spring Security 一塊兒使用,全部的請求由 Spring MVC 控制器處理,並通過一系列的 Spring Security 過濾器攔截。

Spring Security 過濾器鏈 中有如下兩個 端點,這兩個節點用於從 Authorization Service 獲取驗證受權

  • 用於 受權 的端點:默認爲 /oauth/authorize

  • 用於獲取 令牌 的端點:默認爲 /oauth/token

2. 新建本地數據庫

客戶端信息 能夠存儲在 數據庫 中,這樣就能夠經過更改 數據庫 來實時 更新客戶端信息 的數據。Spring OAuth2 已經設計好了數據庫的表,且不可變。首先將如下 DDL 導入數據庫中。

SET NAMES utf8;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for `clientdetails`
-- ----------------------------
DROP TABLE IF EXISTS `clientdetails`;
CREATE TABLE `clientdetails` (
  `appId` varchar(128) NOT NULL,
  `resourceIds` varchar(256) DEFAULT NULL,
  `appSecret` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `grantTypes` varchar(256) DEFAULT NULL,
  `redirectUrl` varchar(256) DEFAULT NULL,
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additionalInformation` varchar(4096) DEFAULT NULL,
  `autoApproveScopes` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`appId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for `oauth_access_token`
-- ----------------------------
DROP TABLE IF EXISTS `oauth_access_token`;
CREATE TABLE `oauth_access_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  `authentication` blob,
  `refresh_token` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


-- ----------------------------
-- Table structure for `oauth_approvals`
-- ----------------------------
DROP TABLE IF EXISTS `oauth_approvals`;
CREATE TABLE `oauth_approvals` (
  `userId` varchar(256) DEFAULT NULL,
  `clientId` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `status` varchar(10) DEFAULT NULL,
  `expiresAt` datetime DEFAULT NULL,
  `lastModifiedAt` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for `oauth_client_details`
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
  `client_id` varchar(256) NOT NULL,
  `resource_ids` varchar(256) DEFAULT NULL,
  `client_secret` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `authorized_grant_types` varchar(256) DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) DEFAULT NULL,
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additional_information` varchar(4096) DEFAULT NULL,
  `autoapprove` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for `oauth_client_token`
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_token`;
CREATE TABLE `oauth_client_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for `oauth_code`
-- ----------------------------
DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code` (
  `code` varchar(256) DEFAULT NULL,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for `oauth_refresh_token`
-- ----------------------------
DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `oauth_refresh_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


-- ----------------------------
-- Table structure for `role`
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;


-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `password` varchar(255) DEFAULT NULL,
  `username` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UK_sb8bbouer5wak8vyiiy4pf2bx` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;


-- ----------------------------
-- Table structure for `user_role`
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
  `user_id` bigint(20) NOT NULL,
  `role_id` bigint(20) NOT NULL,
  KEY `FKa68196081fvovjhkek5m97n3y` (`role_id`),
  KEY `FK859n2jvi8ivhui0rl0esws6o` (`user_id`),
  CONSTRAINT `FK859n2jvi8ivhui0rl0esws6o` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`),
  CONSTRAINT `FKa68196081fvovjhkek5m97n3y` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


SET FOREIGN_KEY_CHECKS = 1;
複製代碼

3. 新建Maven項目

採用 Maven 的多 Module 的項目結構,新建一個 空白的 Maven 工程,並在 根目錄pom.xml 文件中配置 Spring Boot 的版本 1.5.3.RELEASESpring Cloud 的版本爲 Dalston.RELEASE,完整的代碼以下:

<?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">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
        <relativePath/>
    </parent>

    <modules>
        <module>eureka-server</module>
        <module>service-auth</module>
        <module>service-hi</module>
    </modules>

    <groupId>io.github.ostenant.springcloud</groupId>
    <artifactId>spring-cloud-oauth2-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-cloud-oauth2-example</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Dalston.RELEASE</spring-cloud.version>
    </properties>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
複製代碼

4. 建立Eureka Server

4.1. 建立應用模塊

新建一個 eureka-server 模塊,並添加 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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>io.github.ostenant.springcloud</groupId>
    <artifactId>eureka-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-server</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>io.github.ostenant.springcloud</groupId>
        <artifactId>spring-cloud-oauth2-example</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
複製代碼

4.2. 配置application.yml

eureka-server 模塊的配置文件 application.yml 中配置 Eureka Server 的信息:

server:
 port: 8761
eureka:
 instance:
 hostname: localhost
 client:
 registerWithEureka: false
 fetchRegistry: false
 serviceUrl:
 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
複製代碼

4.3. 配置應用啓動類

最後在應用的 啓動類 上添加 @EnableEurekaServer 註解開啓 Eureka Server 的功能。

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}
複製代碼

5. 建立Uaa受權服務

5.1. 建立應用模塊

新建一個 service-auth 模塊,並添加如下依賴,做爲 Uaa受權服務),完整的代碼以下:

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>io.github.ostenant.springcloud</groupId>
    <artifactId>service-auth</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>service-auth</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>io.github.ostenant.springcloud</groupId>
        <artifactId>spring-cloud-oauth2-example</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</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-eureka</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
複製代碼

打開 spring-cloud-starter-oauth2 依賴包能夠看到,它已經整合瞭如下 3起步依賴

  • spring-cloud-starter-security

  • spring-security-oauth2

  • spring-security-jwt

5.2. 配置application.yml

service-oauth 模塊中的 application.yml 完成以下配置:

spring:
 application:
 name: service-auth
 datasource:
 driver-class-name: com.mysql.jdbc.Driver
 url: jdbc:mysql://localhost:3306/spring-cloud-auth?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
 username: root
 password: 123456
 jpa:
 hibernate:
 ddl-auto: update
 show-sql: true

server:
 context-path: /uaa
 port: 5000

security:
 oauth2:
 resource:
 filter-order: 3
# basic:
# enabled: false

eureka:
 client:
 serviceUrl:
 defaultZone: http://localhost:8761/eureka/
複製代碼

配置 security.oauth2.resource.filter-order3,在 Spring Boot 1.5.x 版本以前,能夠省略此配置。

5.3. 配置安全認證

因爲 auth-service 須要對外暴露檢查 TokenAPI 接口,因此 auth-service 其實也是一個 資源服務,須要在 auth-service 中引入 Spring Security,並完成相關配置,從而對 auth-service資源 進行保護。

WebSecurityConfig.java

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserServiceDetail userServiceDetail;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http.authorizeRequests().anyRequest().authenticated()
            .and()
            .csrf().disable();
        // @formatter:on
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userServiceDetail).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    public @Bean AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}
複製代碼

UserServiceDetail.java

@Service
public class UserServiceDetail implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByUsername(username);
    }
}
複製代碼

配置表的關係映射類 User,須要實現 UserDetails 接口:

@Entity
public class User implements UserDetails, Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    @Column
    private String password;

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
            inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
    private List<Role> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    // setter getter

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}
複製代碼

配置表的關係映射類 Role,須要實現 GrantedAuthority 接口:

@Entity
public class Role implements GrantedAuthority {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    // setter getter

    @Override
    public String getAuthority() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }
}
複製代碼

UserRepository.java

public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}
複製代碼

5.4. 配置Authentication Server

配置 認證服務器,使用 @EnableAuthorizationServer 註解開啓 Authorization Server,對外提供 認證受權 的功能。

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {

    // 將Token存儲在內存中
    // private TokenStore tokenStore = new InMemoryTokenStore();
    private TokenStore tokenStore = new JdbcTokenStore(dataSource);

    @Autowired
    @Qualifier("dataSource")
    private DataSource dataSource;

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserServiceDetail userServiceDetail;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 將客戶端的信息存儲在內存中
        clients.inMemory()
              // 建立了一個client名爲browser的客戶端
              .withClient("browser")
              // 配置驗證類型
              .authorizedGrantTypes("refresh_token", "password")
              // 配置客戶端域爲「ui」
              .scopes("ui")
              .and()
              .withClient("service-hi")
              .secret("123456")
              .authorizedGrantTypes("client_credentials", "refresh_token","password")
              .scopes("server");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 配置Token的存儲方式
        endpoints.tokenStore(tokenStore)
                // 注入WebSecurityConfig配置的bean
                .authenticationManager(authenticationManager)
                // 讀取用戶的驗證信息
                .userDetailsService(userServiceDetail);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        // 對獲取Token的請求再也不攔截
        oauthServer.tokenKeyAccess("permitAll()")
                 // 驗證獲取Token的驗證信息
                .checkTokenAccess("isAuthenticated()");
    }
}
複製代碼

5.5. 開啓Resource Server

在應用的啓動類上,使用 @EnableResourceServer 註解 開啓資源服務,應用須要對外暴露獲取 tokenAPI 接口。

@EnableEurekaClient
@EnableResourceServer
@SpringBootApplication
public class ServiceAuthApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceAuthApplication.class, args);
    }
}
複製代碼

本例採用 RemoteTokenService 這種方式對 token 進行 驗證。若是 其餘資源服務 須要驗證 token,則須要遠程調用 受權服務 暴露的 驗證 tokenAPI 接口。

@RestController
@RequestMapping("/users")
public class UserController {

    @RequestMapping(value = "/current", method = RequestMethod.GET)
    public Principal getUser(Principal principal) {
        return principal;
    }
}
複製代碼

6. 編寫service-hi資源服務

6.1. 建立應用模塊

新建一個 service-hi 模塊,這個服務做爲 資源服務。在 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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>io.github.ostenant.springcloud</groupId>
    <artifactId>service-hi</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>service-hi</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>io.github.ostenant.springcloud</groupId>
        <artifactId>spring-cloud-oauth2-example</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</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-feign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
複製代碼

6.2. 配置application.yml

application.yml 中配置 service-hiservice-auth 中配置的 OAuth Client 信息:

eureka:
 client:
 serviceUrl:
 defaultZone: http://localhost:8761/eureka/
server:
 port: 8762
spring:
 application:
 name: service-hi
 datasource:
 driver-class-name: com.mysql.jdbc.Driver
 url: jdbc:mysql://localhost:3306/spring-cloud-auth?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
 username: root
 password: 123456
 jpa:
 hibernate:
 ddl-auto: update
 show-sql: true

security:
 oauth2:
 resource:
 user-info-uri: http://localhost:5000/uaa/users/current #獲取當前Token的用戶信息
 client:
 clientId: service-hi
 clientSecret: 123456
 accessTokenUri: http://localhost:5000/uaa/oauth/token #獲取Token
 grant-type: client_credentials,password
 scope: server
複製代碼

6.3. 配置Resource Server

server-hi 模塊做爲 Resource Server資源服務),須要進行 Resource Server 的相關配置,配置代碼以下:

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
              // 對用戶註冊的URL地址開放
              .antMatchers("/user/registry").permitAll()
              .anyRequest().authenticated();
    }
}
複製代碼

6.4. 配置OAuth2 Client

@Configuration
@EnableOAuth2Client
@EnableConfigurationProperties
public class OAuth2ClientConfig {

    @Bean
    @ConfigurationProperties(prefix = "security.oauth2.client")
    public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
        // 配置受保護資源的信息
        return new ClientCredentialsResourceDetails();
    }

    @Bean
    public RequestInterceptor oauth2FeignRequestInterceptor(){
        // 配置一個攔截器,對於每個外來的請求,都會在request域內建立一個AccessTokenRequest類型的bean。
        return new OAuth2FeignRequestInterceptor(
                        new DefaultOAuth2ClientContext(),
                        clientCredentialsResourceDetails());
    }

    @Bean
    public OAuth2RestTemplate clientCredentialsRestTemplate() {
        // 用於向認證服務器服務請求token
        return new OAuth2RestTemplate(clientCredentialsResourceDetails());
    }
}
複製代碼

6.5. 建立用戶註冊接口

service-auth 模塊的 User.javaUserRepository.java 拷貝到 service-hi 模塊中。建立 UserService 用於 建立用戶,並對 用戶密碼 進行 加密

UserService.java

@Service
public class UserService {
   private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

   @Autowired
   private UserRepository userRepository;

   public User create(String username, String password) {
      User user = new User();
      user.setUsername(username);
      String hash = encoder.encode(password);
      user.setPassword(hash);
      return userRepository.save(user);
   }
}
複製代碼

UserController.java

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping(value = "/registry", method = RequestMethod.POST)
    public User createUser(@RequestParam("username") String username, @RequestParam("password") String password) {
        return userService.create(username,password);
    }
}
複製代碼

6.6. 建立資源服務接口

@RestController
public class HiController {
    private static final Logger LOGGER = LoggerFactory.getLogger(HiController.class);

    @Value("${server.port}")
    private String port;

    /** * 不須要任何權限,只要Header中的Token正確便可 */
    @RequestMapping("/hi")
    public String hi() {
        return "hi : " + ",i am from port: " + port;
    }

    /** * 須要ROLE_ADMIN權限 */
    @PreAuthorize("hasAuthority('ROLE_ADMIN')")
    @RequestMapping("/hello")
    public String hello() {
        return "hello you!";
    }

    /** * 獲取當前認證用戶的信息 */
    @GetMapping("/getPrinciple")
    public OAuth2Authentication getPrinciple(OAuth2Authentication oAuth2Authentication, Principal principal, Authentication authentication){
        LOGGER.info(oAuth2Authentication.getUserAuthentication().getAuthorities().toString());
        LOGGER.info(oAuth2Authentication.toString());
        LOGGER.info("principal.toString()" + principal.toString());
        LOGGER.info("principal.getName()" + principal.getName());
        LOGGER.info("authentication:" + authentication.getAuthorities().toString());

        return oAuth2Authentication;
    }
}
複製代碼

6.6. 配置應用的啓動類

@EnableEurekaClient
@SpringBootApplication
public class ServiceHiApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceHiApplication.class, args);
    }
}
複製代碼

依次啓動 eureka-serviceservice-authservice-hi 三個服務。

7. 使用PostMan驗證

  • 註冊一個用戶,返回註冊成功信息

  • 基於密碼模式從認證服務器獲取 token

  • 訪問資源服務 /hi,不須要權限,只要 token 正確便可

  • 訪問資源服務 /hello,提示須要 ROLE_ADMIN 權限

  • 訪問不成功,修改數據庫的 role 表,添加 權限信息 ROLE_ADMIN,而後在 user_role 表關聯下再次訪問

總結

本案列架構有仍有改進之處。例如在 資源服務器 加一個 登陸接口,該接口不受 Spring Security 保護。登陸成功後,service-hi 遠程調用 auth-service 獲取 token 返回給瀏覽器,瀏覽器之後全部的請求都須要攜帶該 token

這個架構的缺陷就是,每次請求 都須要由 資源服務 內部 遠程調用 service-auth 服務來 驗證 token 的正確性,以及該 token 對應的用戶所具備的 權限,多了一次額外的 內部請求開銷。若是在 高併發 的狀況下,service-auth 須要以 集羣 的方式部署,而且須要作 緩存處理。因此最佳方案仍是結合 Spring Security OAuth2JWT 一塊兒使用,來實現 Spring Cloud 微服務系統的 認證受權

參考

  • 方誌朋《深刻理解Spring Cloud與微服務構建》

歡迎關注技術公衆號:零壹技術棧

零壹技術棧

本賬號將持續分享後端技術乾貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分佈式和微服務,架構學習和進階等學習資料和文章。

相關文章
相關標籤/搜索