没品星人
没品星人
9天前 · 3 人阅读

相关

  1. Spring Cloud实战系列(一) - 服务注册与发现Eureka

  2. Spring Cloud实战系列(二) - 客户端调用Rest + Ribbon

  3. Spring Cloud实战系列(三) - 声明式客户端Feign

  4. Spring Cloud实战系列(四) - 熔断器Hystrix

  5. Spring Cloud实战系列(五) - 服务网关Zuul

  6. Spring Cloud实战系列(六) - 分布式配置中心Spring Cloud Config

  7. Spring Cloud实战系列(七) - 服务链路追踪Spring Cloud Sleuth

  8. Spring Cloud实战系列(八) - 微服务监控Spring Boot Admin

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

前言

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 导入数据库中。

SETNAMES utf8;
SET FOREIGN_KEY_CHECKS = 0;

-- ------------------------------  Table structure for `clientdetails`-- ----------------------------DROPTABLEIFEXISTS`clientdetails`;
CREATETABLE`clientdetails` (
  `appId`varchar(128) NOTNULL,
  `resourceIds`varchar(256) DEFAULTNULL,
  `appSecret`varchar(256) DEFAULTNULL,
  `scope`varchar(256) DEFAULTNULL,
  `grantTypes`varchar(256) DEFAULTNULL,
  `redirectUrl`varchar(256) DEFAULTNULL,
  `authorities`varchar(256) DEFAULTNULL,
  `access_token_validity`int(11) DEFAULTNULL,
  `refresh_token_validity`int(11) DEFAULTNULL,
  `additionalInformation`varchar(4096) DEFAULTNULL,
  `autoApproveScopes`varchar(256) DEFAULTNULL,
  PRIMARY KEY (`appId`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8;

-- ------------------------------  Table structure for `oauth_access_token`-- ----------------------------DROPTABLEIFEXISTS`oauth_access_token`;
CREATETABLE`oauth_access_token` (
  `token_id`varchar(256) DEFAULTNULL,
  `token`blob,
  `authentication_id`varchar(128) NOTNULL,
  `user_name`varchar(256) DEFAULTNULL,
  `client_id`varchar(256) DEFAULTNULL,
  `authentication`blob,
  `refresh_token`varchar(256) DEFAULTNULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8;


-- ------------------------------  Table structure for `oauth_approvals`-- ----------------------------DROPTABLEIFEXISTS`oauth_approvals`;
CREATETABLE`oauth_approvals` (
  `userId`varchar(256) DEFAULTNULL,
  `clientId`varchar(256) DEFAULTNULL,
  `scope`varchar(256) DEFAULTNULL,
  `status`varchar(10) DEFAULTNULL,
  `expiresAt` datetime DEFAULTNULL,
  `lastModifiedAt` datetime DEFAULTNULL
) ENGINE=InnoDBDEFAULTCHARSET=utf8;

-- ------------------------------  Table structure for `oauth_client_details`-- ----------------------------DROPTABLEIFEXISTS`oauth_client_details`;
CREATETABLE`oauth_client_details` (
  `client_id`varchar(256) NOTNULL,
  `resource_ids`varchar(256) DEFAULTNULL,
  `client_secret`varchar(256) DEFAULTNULL,
  `scope`varchar(256) DEFAULTNULL,
  `authorized_grant_types`varchar(256) DEFAULTNULL,
  `web_server_redirect_uri`varchar(256) DEFAULTNULL,
  `authorities`varchar(256) DEFAULTNULL,
  `access_token_validity`int(11) DEFAULTNULL,
  `refresh_token_validity`int(11) DEFAULTNULL,
  `additional_information`varchar(4096) DEFAULTNULL,
  `autoapprove`varchar(256) DEFAULTNULL,
  PRIMARY KEY (`client_id`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8;

-- ------------------------------  Table structure for `oauth_client_token`-- ----------------------------DROPTABLEIFEXISTS`oauth_client_token`;
CREATETABLE`oauth_client_token` (
  `token_id`varchar(256) DEFAULTNULL,
  `token`blob,
  `authentication_id`varchar(128) NOTNULL,
  `user_name`varchar(256) DEFAULTNULL,
  `client_id`varchar(256) DEFAULTNULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8;

-- ------------------------------  Table structure for `oauth_code`-- ----------------------------DROPTABLEIFEXISTS`oauth_code`;
CREATETABLE`oauth_code` (
  `code`varchar(256) DEFAULTNULL,
  `authentication`blob
) ENGINE=InnoDBDEFAULTCHARSET=utf8;

-- ------------------------------  Table structure for `oauth_refresh_token`-- ----------------------------DROPTABLEIFEXISTS`oauth_refresh_token`;
CREATETABLE`oauth_refresh_token` (
  `token_id`varchar(256) DEFAULTNULL,
  `token`blob,
  `authentication`blob
) ENGINE=InnoDBDEFAULTCHARSET=utf8;


-- ------------------------------  Table structure for `role`-- ----------------------------DROPTABLEIFEXISTS`role`;
CREATETABLE`role` (
  `id`bigint(20) NOTNULL AUTO_INCREMENT,
  `name`varchar(255) NOTNULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3DEFAULTCHARSET=utf8;


-- ------------------------------  Table structure for `user`-- ----------------------------DROPTABLEIFEXISTS`user`;
CREATETABLE`user` (
  `id`bigint(20) NOTNULL AUTO_INCREMENT,
  `password`varchar(255) DEFAULTNULL,
  `username`varchar(255) NOTNULL,
  PRIMARY KEY (`id`),
  UNIQUEKEY`UK_sb8bbouer5wak8vyiiy4pf2bx` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=7DEFAULTCHARSET=utf8;


-- ------------------------------  Table structure for `user_role`-- ----------------------------DROPTABLEIFEXISTS`user_role`;
CREATETABLE`user_role` (
  `user_id`bigint(20) NOTNULL,
  `role_id`bigint(20) NOTNULL,
  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=InnoDBDEFAULTCHARSET=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"?><projectxmlns="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"?><projectxmlns="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:8761eureka:  instance:    hostname:localhost  client:    registerWithEureka:false    fetchRegistry:false    serviceUrl:      defaultZone:http://${eureka.instance.hostname}:${server.port}/eureka/复制代码

4.3. 配置应用启动类

最后在应用的 启动类 上添加 @EnableEurekaServer 注解开启 Eureka Server 的功能。

@EnableEurekaServer@SpringBootApplicationpublicclassEurekaServerApplication{

    publicstaticvoidmain(String[] args){
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}
复制代码

5. 创建Uaa授权服务

5.1. 创建应用模块

新建一个 service-auth 模块,并添加以下依赖,作为 Uaa授权服务),完整的代码如下:

<?xml version="1.0" encoding="UTF-8"?><projectxmlns="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:trueserver:  context-path:/uaa  port:5000security:  oauth2:    resource:      filter-order:3# basic:#   enabled: falseeureka:  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)
publicclassWebSecurityConfigextendsWebSecurityConfigurerAdapter{
    @Autowiredprivate UserServiceDetail userServiceDetail;

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

    @Overrideprotectedvoidconfigure(AuthenticationManagerBuilder auth)throws Exception {
        auth.userDetailsService(userServiceDetail).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Overridepublic@BeanAuthenticationManager authenticationManagerBean()throws Exception {
        returnsuper.authenticationManagerBean();
    }
}
复制代码

UserServiceDetail.java

@ServicepublicclassUserServiceDetailimplementsUserDetailsService{
    @Autowiredprivate UserRepository userRepository;

    @Overridepublic UserDetails loadUserByUsername(String username)throws UsernameNotFoundException {
        return userRepository.findByUsername(username);
    }
}
复制代码

配置表的关系映射类 User,需要实现 UserDetails 接口:

@EntitypublicclassUserimplementsUserDetails, Serializable{
    @Id@GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

    @Columnprivate 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;

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

    // setter getter@OverridepublicbooleanisAccountNonExpired(){
        returntrue;
    }

    @OverridepublicbooleanisAccountNonLocked(){
        returntrue;
    }

    @OverridepublicbooleanisCredentialsNonExpired(){
        returntrue;
    }

    @OverridepublicbooleanisEnabled(){
        returntrue;
    }
}
复制代码

配置表的关系映射类 Role,需要实现 GrantedAuthority 接口:

@EntitypublicclassRoleimplementsGrantedAuthority{
    @Id@GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    // setter getter@Overridepublic String getAuthority(){
        return name;
    }

    @Overridepublic String toString(){
        return name;
    }
}
复制代码

UserRepository.java

publicinterfaceUserRepositoryextendsJpaRepository<User, Long> {
    User findByUsername(String username);
}
复制代码

5.4. 配置Authentication Server

配置 认证服务器,使用 @EnableAuthorizationServer 注解开启 Authorization Server,对外提供 认证授权 的功能。

@Configuration@EnableAuthorizationServerpublicclassOAuth2AuthorizationConfigextendsAuthorizationServerConfigurerAdapter{

    // 将Token存储在内存中// private TokenStore tokenStore = new InMemoryTokenStore();private TokenStore tokenStore = new JdbcTokenStore(dataSource);

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

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

    @Autowiredprivate UserServiceDetail userServiceDetail;

    @Overridepublicvoidconfigure(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");
    }

    @Overridepublicvoidconfigure(AuthorizationServerEndpointsConfigurer endpoints)throws Exception {
        // 配置Token的存储方式
        endpoints.tokenStore(tokenStore)
                // 注入WebSecurityConfig配置的bean
                .authenticationManager(authenticationManager)
                // 读取用户的验证信息
                .userDetailsService(userServiceDetail);
    }

    @Overridepublicvoidconfigure(AuthorizationServerSecurityConfigurer oauthServer)throws Exception {
        // 对获取Token的请求不再拦截
        oauthServer.tokenKeyAccess("permitAll()")
                 // 验证获取Token的验证信息
                .checkTokenAccess("isAuthenticated()");
    }
}
复制代码

5.5. 开启Resource Server

在应用的启动类上,使用 @EnableResourceServer 注解 开启资源服务,应用需要对外暴露获取 tokenAPI 接口。

@EnableEurekaClient@EnableResourceServer@SpringBootApplicationpublicclassServiceAuthApplication{

    publicstaticvoidmain(String[] args){
        SpringApplication.run(ServiceAuthApplication.class, args);
    }
}
复制代码

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

@RestController@RequestMapping("/users")
publicclassUserController{

    @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"?><projectxmlns="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:8762spring:  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:truesecurity:  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)
publicclassResourceServerConfigurerextendsResourceServerConfigurerAdapter{

    @Overridepublicvoidconfigure(HttpSecurity http)throws Exception {
        http.authorizeRequests()
              // 对用户注册的URL地址开放
              .antMatchers("/user/registry").permitAll()
              .anyRequest().authenticated();
    }
}
复制代码

6.4. 配置OAuth2 Client

@Configuration@EnableOAuth2Client
@EnableConfigurationPropertiespublicclassOAuth2ClientConfig{

    @Bean@ConfigurationProperties(prefix = "security.oauth2.client")
    public ClientCredentialsResourceDetails clientCredentialsResourceDetails(){
        // 配置受保护资源的信息returnnew ClientCredentialsResourceDetails();
    }

    @Beanpublic RequestInterceptor oauth2FeignRequestInterceptor(){
        // 配置一个拦截器,对于每一个外来的请求,都会在request域内创建一个AccessTokenRequest类型的bean。returnnew OAuth2FeignRequestInterceptor(
                        new DefaultOAuth2ClientContext(),
                        clientCredentialsResourceDetails());
    }

    @Beanpublic OAuth2RestTemplate clientCredentialsRestTemplate(){
        // 用于向认证服务器服务请求tokenreturnnew OAuth2RestTemplate(clientCredentialsResourceDetails());
    }
}
复制代码

6.5. 创建用户注册接口

service-auth 模块的 User.javaUserRepository.java 拷贝到 service-hi 模块中。创建 UserService 用于 创建用户,并对 用户密码 进行 加密

UserService.java

@ServicepublicclassUserService{
   privatestaticfinal BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

   @Autowiredprivate 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")
publicclassUserController{
    @Autowiredprivate 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. 创建资源服务接口

@RestControllerpublicclassHiController{
    privatestaticfinal 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@SpringBootApplicationpublicclassServiceHiApplication{

    publicstaticvoidmain(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与微服务构建》

欢迎关注技术公众号:零壹技术栈

本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。

收藏 0
后端 spring cloud
评论 ( 0 )