转载

Spring Cloud实战系列(九) - 服务认证授权Spring Cloud OAuth 2.0

OAuth 2.0 是介于 用户资源 和 第三方应用 之间的一个 中间层 ,它把 资源 和 第三方应用 隔开,使得 第三方应用 无法直接访问 资源 ,从而起到 保护资源 的作用。为了访问这种 受限资源 , 第三方应用 (客户端)在访问的时候需要 提供凭证 。

正文

1. OAuth 2.0简介

在 认证 与 授权 的过程中,主要包含以下 3 种角色:

  • 服务提供方: Authorization Server

  • 资源持有者: Resource Server

  • 客户端: Client

OAuth 2.0 的 认证流程 如图所示,具体如下:

Spring Cloud实战系列(九) - 服务认证授权Spring Cloud OAuth 2.0
  1. 用户( 资源持有者 )打开 客户端 , 客户端 询问 用户授权 。

  2. 用户同意授权。

  3. 客户端向 授权服务器 申请授权。

  4. 授权服务器对 客户端 进行认证,也包括 用户信息 的认证,认证成功后授权给予 令牌 。

  5. 客户端获取令牌后, 携带令牌 向 资源服务器 请求资源。

  6. 资源服务器确认令牌正确无误,向 客户端 发放资源。

OAuth2 Provider 的角色被分为 Authorization Server ( 授权服务 )和 Resource Service ( 资源服务 ),通常它们不在同一个服务中,可能一个 Authorization Service 对应 多个 Resource Service 。 Spring 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.RELEASE , Spring 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-order 为 3 ,在 Spring Boot 1.5.x 版本之前,可以省略此配置。

5.3. 配置安全认证

由于 auth-service 需要对外暴露检查 Token 的 API 接口,所以 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 注解 开启资源服务 ,应用需要对外暴露获取 token 的 API 接口。

@EnableEurekaClient
@EnableResourceServer
@SpringBootApplication
public class ServiceAuthApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceAuthApplication.class, args);
    }
}
复制代码

本例采用 RemoteTokenService 这种方式对 token 进行 验证 。如果 其他资源服务 需要验证 token ,则需要远程调用 授权服务 暴露的 验证 token 的 API 接口。

@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-hi 在 service-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.java 和 UserRepository.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-service , service-auth 和 service-hi 三个服务。

7. 使用PostMan验证

  • 注册一个用户,返回注册成功信息
Spring Cloud实战系列(九) - 服务认证授权Spring Cloud OAuth 2.0
  • 基于密码模式从认证服务器获取 token
Spring Cloud实战系列(九) - 服务认证授权Spring Cloud OAuth 2.0
  • 访问资源服务 /hi ,不需要权限,只要 token 正确即可
Spring Cloud实战系列(九) - 服务认证授权Spring Cloud OAuth 2.0
  • 访问资源服务 /hello ,提示需要 ROLE_ADMIN 权限
Spring Cloud实战系列(九) - 服务认证授权Spring Cloud OAuth 2.0
  • 访问不成功,修改数据库的 role 表,添加 权限信息 ROLE_ADMIN ,然后在 user_role 表关联下再次访问
Spring Cloud实战系列(九) - 服务认证授权Spring Cloud OAuth 2.0

总结

本案列架构有仍有改进之处。例如在 资源服务器 加一个 登录接口 ,该接口不受 Spring Security 保护。登录成功后, service-hi 远程调用 auth-service 获取 token 返回给浏览器,浏览器以后所有的请求都需要携带该 token 。

这个架构的缺陷就是, 每次请求 都需要由 资源服务 内部 远程调用 service-auth 服务来 验证 token 的正确性,以及该 token 对应的用户所具有的 权限 ,多了一次额外的 内部请求开销 。如果在 高并发 的情况下, service-auth 需要以 集群 的方式部署,并且需要做 缓存处理 。所以最佳方案还是结合 Spring Security OAuth2 和 JWT 一起使用,来实现 Spring Cloud 微服务系统的 认证 和 授权 。

原文  https://juejin.im/post/5c5ae6566fb9a049b3486e38
正文到此结束
Loading...