相關
前言
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
微服務系統的 認證 和 授權。
參考
- 方誌朋《深入理解Spring Cloud與微服務構建》
歡迎關注技術公眾號:零壹技術棧
本帳號將持續分享後端技術乾貨,包括虛擬機器基礎,多執行緒程式設計,高效能框架,非同步、快取和訊息中介軟體,分散式和微服務,架構學習和進階等學習資料和文章。