啥是Spring Cloud Security OAuth2?
Spring-Security-OAuth2是對OAuth2的一種實現,並且跟我們之前學習的Spring Security相輔相成,與Spring Cloud體系的集成也非常便利,接下來,我們需要對它進行學習,最終使用它來實現我們設計的分佈式認證授權解 決方案。
OAuth2.0的服務提供方涵蓋兩個服務,即授權服務 (Authorization Server,也叫認證服務) 和資源服務 (Resource Server),使用 Spring Security OAuth2 的時候你可以選擇把它們在同一個應用程序中實現,也可以選擇建立使用 同一個授權服務的多個資源服務。
授權服務 (Authorization Server)應包含對接入端以及登入用戶的合法性進行驗證並頒發token等功能,對令牌 的請求端點由 Spring MVC 控制器進行實現,下面是配置一個認證服務必須要實現的endpoints:
(1)AuthorizationEndpoint 服務於認證請求。默認 URL: /oauth/authorize 。
(2)TokenEndpoint 服務於訪問令牌的請求。默認 URL: /oauth/token 。 資源服務 (Resource Server),應包含對資源的保護功能,對非法請求進行攔截,對請求中token進行解析鑑 權等,下面的過濾器用於實現 OAuth 2.0 資源服務:
(3)OAuth2AuthenticationProcessingFilter用來對請求給出的身份令牌解析鑑權。
本教程分別創建uaa授權服務(也可叫認證服務)和order訂單資源服務。
認證流程如下:
1、客戶端請求UAA授權服務進行認證。
2、認證通過後由UAA頒發令牌。
3、客戶端攜帶令牌Token請求資源服務。
4、資源服務校驗令牌的合法性,合法即返回資源信息。
2、搭建工程
父工程引入的依賴:
<?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>com.oauth.security</groupId>
<artifactId>OAuth2.0</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
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">
<parent>
<artifactId>OAuth2.0</artifactId>
<groupId>com.oauth.security</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>uaa</artifactId>
<dependencies>
<!--
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
uaa的配置文件:
spring.application.name=uaa-service
server.port=53020
spring.main.allow-bean-definition-overriding=true
logging.level.root=info
logging.level.org.springframework.web=info
spring.http.encoding.enabled=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.force=true
server.tomcat.remote_ip_header=x-forwarded-for
server.tomcat.protocol_header=x-forwarded-proto
server.use-forward-headers=true
server.servlet.context-path=/uaa
spring.freemarker.enabled=true
spring.freemarker.suffix=.html
spring.freemarker.request-context-attribute=rc
spring.freemarker.content-type=text/html
spring.freemarker.charset=UTF-8
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
#數據庫連接
spring.datasource.url=jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=utf-8&serverTimezone=CTT
spring.datasource.username=root
spring.datasource.password=111111
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#mybatis-plus配置
mybatis-plus.type-aliases-package=com.oauth.security.entity
mybatis-plus.mapper-locations=classpath:mapper/*Mapper.xml
#eureka.client.serviceUrl.defaultZone=http://localhost:53000/eureka/
# eureka.instance.preferIpAddress=true
# eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ip- address}:${spring.application.instance_id:${server.port}}
management.endpoints.web.exposure.include=refresh,health,info,env
feign.hystrix.enabled=true
feign.compression.request.enabled=true
feign.compression.request.mime-types[0]=text/xml
feign.compression.request.mime-types[1]=application/xml
feign.compression.request.mime-types[2]=application/json
feign.compression.request.min-request-size=2048
feign.compression.response.enabled=true
uaa的啓動類:
package com.oauth.security;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @ClassName UAAServer
* @Description UAA服務啓動類
* @Author 戴書博
* @Date 2020/5/9 20:56
* @Version 1.0
**/
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
@EnableFeignClients(basePackages = {"com.oauth.security"})
@MapperScan("com.oauth.security.dao")
public class UAAServer {
public static void main(String[] args) {
SpringApplication.run(UAAServer.class, args);
}
}
Order的依賴:
<?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">
<parent>
<artifactId>OAuth2.0</artifactId>
<groupId>com.oauth.security</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order</artifactId>
<dependencies>
<!--
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</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-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
Order的啓動類:
package com.oauth.security;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @ClassName OrderServer
* @Description
* @Author
* @Date 2020/5/9 21:11
* @Version 1.0
**/
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServer {
public static void main(String[] args) {
SpringApplication.run(OrderServer.class, args);
}
}
Order的配置文件:
spring.application.name=order-service
server.port=53021
spring.main.allow-bean-definition-overriding=true
logging.level.root=info
logging.level.org.springframework.web=info
spring.http.encoding.enabled=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.force=true
server.tomcat.remote_ip_header=x-forwarded-for
server.tomcat.protocol_header=x-forwarded-proto
server.use-forward-headers=true
server.servlet.context-path=/order
spring.freemarker.enabled=true
spring.freemarker.suffix=.html
spring.freemarker.request-context-attribute=rc
spring.freemarker.content-type=text/html
spring.freemarker.charset=UTF-8
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
#eureka.client.serviceUrl.defaultZone=http://localhost:53000/eureka/
# eureka.instance.preferIpAddress=true
#eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ip- address}:${spring.application.instance_id:${server.port}}
management.endpoints.web.exposure.include=refresh,health,info,env
feign.hystrix.enabled=true
feign.compression.request.enabled=true
feign.compression.request.mime-types[0]=text/xml
feign.compression.request.mime-types[1]=application/xml
feign.compression.request.mime-types[2]=application/json
feign.compression.request.min-request-size=2048
feign.compression.response.enabled=true
3、授權服務配置
(1)配置客戶端詳情:
ClientDetailsServiceConfigurer 能夠使用內存或者JDBC來實現客戶端詳情服務(ClientDetailsService),ClientDetailsService負責查找ClientDetails,而ClientDetails有幾個重要的屬性如下列表:
clientId:(必須的)用來標識客戶的Id。
secret:(需要值得信任的客戶端)客戶端安全碼,如果有的話。
scope:用來限制客戶端的訪問範圍,如果爲空(默認)的話,那麼客戶端擁有全部的訪問範圍。
authorizedGrantTypes:此客戶端可以使用的授權類型,默認爲空。
authorities:此客戶端可以使用的權限(基於Spring Security authorities)。
客戶端詳情(Client Details)能夠在應用程序運行的時候進行更新,可以通過訪問底層的存儲服務(例如將客戶 端詳情存儲在一個關係數據庫的表中,就可以使用 JdbcClientDetailsService)或者通過自己實現 ClientRegistrationService接口(同時你也可以實現 ClientDetailsService 接口)來進行管理。暫時配置如下:
package com.oauth.security.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
/**
* @ClassName AuthorizationServer
* @Description
* @Author
* @Date 2020/5/9 22:00
* @Version 1.0
**/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// clients.withClientDetails(clientDetailService)
clients.inMemory()
.withClient("c1") //client_id
.secret(secret)
.resourceIds("res1")
//該client允許的授權類型 authorization_code,password,refresh_token,implicit,client_credentials
.authorizedGrantTypes("authorization_code", "password","client_credentials","implicit","refresh_token")
.scopes("all")
//授權頁面
.autoApprove(false)
//回調地址
.redirectUris("http://www.baidu.com");
}
}
(2)配置令牌:
AuthorizationServerTokenServices 接口定義了一些操作使得你可以對令牌進行一些必要的管理,令牌可以被用來 加載身份信息,裏面包含了這個令牌的相關權限。
自己可以創建 AuthorizationServerTokenServices 這個接口的實現,則需要繼承 DefaultTokenServices 這個類, 裏面包含了一些有用實現,你可以使用它來修改令牌的格式和令牌的存儲。默認的,當它嘗試創建一個令牌的時 候,是使用隨機值來進行填充的,除了持久化令牌是委託一個 TokenStore 接口來實現以外,這個類幾乎幫你做了 所有的事情。並且 TokenStore 這個接口有一個默認的實現,它就是 InMemoryTokenStore ,如其命名,所有的 令牌是被保存在了內存中。除了使用這個類以外,你還可以使用一些其他的預定義實現,下面有幾個版本,它們都 實現了TokenStore接口:
InMemoryTokenStore:這個版本的實現是被默認採用的,它可以完美的工作在單服務器上(即訪問併發量 壓力不大的情況下,並且它在失敗的時候不會進行備份),大多數的項目都可以使用這個版本的實現來進行 嘗試,你可以在開發的時候使用它來進行管理,因爲不會被保存到磁盤中,所以更易於調試。
JdbcTokenStore:這是一個基於JDBC的實現版本,令牌會被保存進關係型數據庫。使用這個版本的實現時, 你可以在不同的服務器之間共享令牌信息,使用這個版本的時候請注意把"spring-jdbc"這個依賴加入到你的 classpath當中。
JwtTokenStore:這個版本的全稱是 JSON Web Token(JWT),它可以把令牌相關的數據進行編碼(因此對 於後端服務來說,它不需要進行存儲,這將是一個重大優勢),但是它有一個缺點,那就是撤銷一個已經授 權令牌將會非常困難,所以它通常用來處理一個生命週期較短的令牌以及撤銷刷新令牌(refresh_token)。 另外一個缺點就是這個令牌佔用的空間會比較大,如果你加入了比較多用戶憑證信息。JwtTokenStore 不會保存任何數據,但是它在轉換令牌值以及授權信息方面與 DefaultTokenServices 所扮演的角色是一樣的。
package com.oauth.security.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
/**
* @ClassName TokenConfig
* @Description
* @Author
* @Date 2020/5/9 22:13
* @Version 1.0
**/
@Configuration
public class TokenConfig {
@Bean
public TokenStore tokenStore() {
//使用內存存儲令牌
return new InMemoryTokenStore();
}
}
//在AuthorizationServer中
@Autowired
private TokenStore tokenStore;
@Autowired
private ClientDetailsService clientDetailsService;
/**
* 令牌管理服務
*/
@Bean
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service = new DefaultTokenServices();
//配置客戶端詳情服務
service.setClientDetailsService(clientDetailsService);
//支持刷新令牌
service.setSupportRefreshToken(true);
//令牌存儲策略
service.setTokenStore(tokenStore);
// 令牌默認有效期2小時
service.setAccessTokenValiditySeconds(7200);
// 刷新令牌默認有效期3天 return service;
service.setRefreshTokenValiditySeconds(259200);
return service;
}
(3)令牌的訪問端點:
AuthorizationServerEndpointsConfigurer 這個對象的實例可以完成令牌服務以及令牌endpoint配置。
authorizationCodeServices:這個屬性是用來設置授權碼服務的(即 AuthorizationCodeServices 的實例對 象),主要用於 "authorization_code" 授權碼類型模式。
//在AuthorizationServer中添加
@Autowired
private AuthorizationCodeServices authorizationCodeServices;
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)
.authorizationCodeServices(authorizationCodeServices)
.tokenServices(tokenService())
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
然後還要將UserDetailsService、service、mapper等添加到這個項目中,代碼拷貝見https://blog.csdn.net/weixin_44588495/article/details/105918081
如果不願意拷貝也沒關係,最後有項目的地址。
package com.oauth.security.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @ClassName WebSecurityConfig
* @Description
* @Author
* @Date 2020/5/10 9:07
* @Version 1.0
**/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 配置認證管理器
*/
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
//密碼編碼器
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
//安全攔截機制(最重要)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/r/r1").hasAnyAuthority("p1")
.antMatchers("/login*").permitAll()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
(5)令牌端點的安全約束:
/**
* 配置令牌端點(Token Endpoint)的安全約束
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.tokenKeyAccess("permitAll()") //(1)
.checkTokenAccess("permitAll()") //(2)
.allowFormAuthenticationForClients() ;//(3)
}
(1)tokenkey這個endpoint當使用JwtToken且使用非對稱加密時,資源服務用於獲取公鑰而開放的,這裏指這個 endpoint完全公開。 (2)checkToken這個endpoint完全公開 (3) 允許表單認證
下一篇文章我們就來測試一下
源碼[email protected]:Zesystem/oauth2.0_init.git