Spring Cloud實戰系列(九) - 服務認證授權Spring Cloud OAuth 2.0
OAuth 2.0
是介於 使用者資源 和 第三方應用 之間的一個 中間層 ,它把 資源 和 第三方應用 隔開,使得 第三方應用 無法直接訪問 資源 ,從而起到 保護資源 的作用。為了訪問這種 受限資源 , 第三方應用 (客戶端)在訪問的時候需要 提供憑證 。
正文
1. OAuth 2.0簡介
在 認證 與 授權 的過程中,主要包含以下 3
種角色:
-
服務提供方: Authorization Server
-
資源持有者: Resource Server
-
客戶端: Client
OAuth 2.0
的 認證流程 如圖所示,具體如下:

-
使用者( 資源持有者 )開啟 客戶端 , 客戶端 詢問 使用者授權 。
-
使用者同意授權。
-
客戶端向 授權伺服器 申請授權。
-
授權伺服器對 客戶端 進行認證,也包括 使用者資訊 的認證,認證成功後授權給予 令牌 。
-
客戶端獲取令牌後, 攜帶令牌 向 資源伺服器 請求資源。
-
資源伺服器確認令牌正確無誤,向 客戶端 發放資源。
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驗證
- 註冊一個使用者,返回註冊成功資訊

- 基於密碼模式從認證伺服器獲取
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 OAuth2
和 JWT
一起使用,來實現 Spring Cloud
微服務系統的 認證 和 授權 。