OAuth 是一個開放標準,該標準允許用戶讓第三方應用程式存取該用戶在某一網站上儲存的私密資源(如頭像、照片、影片等),而在這個過程中無須將使用者名稱和密碼提供給第三方應用程式。
存取儲存在特定服務提供者中的數據,採用的是使用令牌而非使用者名稱和密碼的方式實作。每一個令牌授權一個特定的網站在特定的時間段內存取特定的資源。
OAuth使用戶可以授權第三方網站靈活地存取儲存在另一些資源伺服器上的特定信息,而不是所有資訊。例如,使用者想透過QQ 登入知乎,這時知乎就是一個第三方應用,知乎要存取使用者的一些基本資訊就需要得到使用者的授權,如果使用者把自己的QQ 使用者名稱和密碼告訴知乎,那麼知乎就能存取使用者的所有數據,而且只有使用者修改密碼才能收回權限,這種授權方式安全隱患很大,如果使用OAuth ,就能很好的解決這一問題。
採用令牌的方式可以讓使用者靈活的對第三方應用授權或收回權限。 OAuth 2 是 OAuth 協定的下一版,不過與 OAuth 1.0 不具備向下相容性。
OAuth 2 專注於客戶端開發者的簡易型,同時為Web應用、桌面應用,行動裝置、客廳裝置提供專門的認證流程。傳統的Web 開發登入認證一般都是基於Session 的,但前後端分離的架構中繼續使用Session 會有許多不便,因為行動裝置(Android、IOS、微信小程式等)要不是不支援Cookie(微信小程式) ,要嘛使用非常不便,對於這些問題,使用OAuth 2 認證都能解決。
先了解OAuth 2 中幾個基本的角色
資源擁有者:即用戶,具有頭像、照片、影片等資源
客戶端:即第三方應用
#授權伺服器:用來驗證使用者提供的資訊是否正確,並傳回一個令牌給第三方應用
資源伺服器:提供給使用者資源的伺服器,例如頭像、照片、影片等資源
一般來說,授權伺服器和資源伺服器可以是同一台伺服器。
步驟01:客戶端(第三方應用程式)向使用者請求授權。
當使用者在服務授權頁面上按一下同意授權按鈕後,服務端會向用戶端傳回授權許可憑證。
步驟03:客戶端拿著授權許可證去授權伺服器申請令牌。
步驟04:授權伺服器驗證資訊無誤後,發放令牌給客戶端。
步驟05:客戶端拿著令牌去資源伺服器存取資源。
步驟06:資源伺服器驗證令牌無誤後開放資源。
OAuth 協定的授權模式共分為4 種,如下
授權碼授權模式是授權方式中功能最齊全、流程最嚴謹的,其授權碼(authorization code)是關鍵。這種模式的特點在於客戶端的伺服器與授權伺服器進行交互,國內常見的第三方平台登入功能基本上都採用了這種方式
簡化模式:簡化模式不需要客戶端伺服器參與,直接在瀏覽器中向授權伺服器申請令牌,一般若是純靜態頁面,則可以採用這種方式
在密碼模式下,使用者直接向客戶端提供自己的使用者名稱和密碼,客戶端則使用這些資訊向授權伺服器請求存取權杖。使用者需要了解客戶端的高度訊息,例如客戶端應用程式和服務提供者是否同一家公司
#在客戶端模式下,服務提供者授權給客戶端而非特定使用者。嚴格來說,客戶端模式並不能算作OAuth 協定要解決的問題的一種解決方案,但是,對於開發者而言,在一些前後端分離應用或為行動端提供的認證授權伺服器上使用這種模式還是非常方便的
4 種模式各有千秋,分別適用於不同的開發場景,開發者根據實際情況進行選擇
此處介紹的是在前後端分離應用(或為行動端、微信小程式等)提供的認證伺服器中如何建構OAuth 服務,因此主要介紹密碼模式。
建立Spring Boot Web 項目,新增以下依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth3</artifactId> <version>2.3.3.RELEASE</version> </dependency>
由於Spring Boot 中的OAuth 協定是在Spring Security 的基礎上完成的,因此首先要添加Spring Security 依賴,要用到OAuth 2,因此添加OAuth 2 相關依賴,令牌可以存儲在Redis 緩存伺服器上,同時Redis 具有過期等功能,很適合令牌的存儲,因此也加入Redis依賴。
設定 application.properties
spring.redis.database=0
spring.redis.host=ip地址
spring.redis.port=6379
spring.redis.password=root
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0
授权服务器和资源服务器可以是同一台服务器,也可以是不同服务器,此处假设是同一台服务器,通过不同的配置分别开启授权服务器和资源服务器,首先是授权服务器:
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired AuthenticationManager authenticationManager; @Autowired RedisConnectionFactory redisConnectionFactory; @Autowired UserDetailsService userDetailsService; @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("password") .authorizedGrantTypes("password", "refresh_token") .accessTokenValiditySeconds(1800) .resourceIds("rid") .scopes("all") .secret("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq"); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory)) .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.allowFormAuthenticationForClients(); } }
代码解释:
自定义类继承自 AuthorizationServerConfigurerAdapter ,完成对授权服务器的配置,然后通过 @EnableAuthorizationServer 注解开启授权服务器
注入 AuthenticationManager 用来支持 password 模式
注入 RedisConnectionFactory 用来完成 Redis 缓存,将令牌信息储存到 Redis 缓存中
注入 UserDetailsService 该对象为刷新 token 提供支持
在 configure(ClientDetailsServiceConfigurer clients) 方法中配置 password 授权模式,authorizedGrantTypes 表示 OAuth 2 中的授权模式为 password 和 refresh_token 两种,在标准的 OAuth 2 协议中,授权模式并不包括 refresh_token ,但是在 Spring Security 的实现中将其归为一种,因此如果要实现 access_token 的刷新,就需要添加这样一种授权模式;accessTokenValiditySeconds 方法配置了 access_token 的过期时间;resourceIds 配置了资源 id;secret 方法配置了加密后的密码,明文是 123
configure(AuthorizationServerEndpointsConfigurer endpoints) 方法配置了令牌的存储,AuthenticationManager 和 UserDetailsService 主要用于支持 password 模式以及令牌的刷新
configure(AuthorizationServerSecurityConfigurer security) 方法配置表示支持 client_id 和 client_secret 做登录认证
@Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.resourceId("rid").stateless(true); } @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("admin") .antMatchers("/user/**").hasRole("user") .anyRequest().authenticated(); } }
代码解释:
自定义类继承自 ResourceServerConfigurerAdapter ,并添加 @EnableResourceServer 注解开启资源服务器配置
resources.resourceId(“rid”).stateless(true); 配置资源 id,这里的资源 id 和授权服务器中的资源 id 一直,然后设置这些资源仅基于令牌认证
configure(HttpSecurity http) 方法配置 HttpSecurity
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean @Override protected UserDetailsService userDetailsService() { return super.userDetailsService(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("admin") .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq") .roles("admin") .and() .withUser("sang") .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq") .roles("user"); } @Override protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/oauth/**").authorizeRequests() .antMatchers("/oauth/**").permitAll() .and().csrf().disable(); } }
这里两个 Bean 将注入授权服务器配置类中使用,另外,这里的 HttpSecurity 配置主要是配置 /oauth/** 模式的 URL ,这一类的请求直接放行。在 Spring Security 配置和资源服务器配置中,一共涉及两个 HttpSecurity ,其中 Spring Security 中的配置优先级高于资源服务器中的配置,即请求地址先经过 Spring Security 的 HttpSecurity ,再经过资源服务器的 HttpSecurity。
首先创建三个简单的请求地址
@RestController public class HelloController { @GetMapping("/admin/hello") public String admin() { return "Hello admin!"; } @GetMapping("/user/hello") public String user() { return "Hello user!"; } @GetMapping("/hello") public String hello() { return "hello"; } }
根据前文的配置,要请求这三个地址,分别需要 admin 角色、user 角色以及登录后访问。
所有都配置完成后,启动 Redis 服务器,再启动 Spring Boot 项目,首先发送一个 POST 请求获取 token,请求地址如下(注意这里是一个 POST 请求,为了显示方便,将参数写在地址栏中):http://localhost:8080/oauth/token?username=sang&password=123&grant_type=password&client_id=password&scope=all&client_secret=123
请求地址中包含的参数有用户名、密码、授权模式、客户端 id 、scope 以及客户端密码,基本就是授权服务器中所配置的数据,请求结果如图
其中 access_token 是获取其它资源时要用的令牌,refresh_token 用来刷新令牌,expires_in 表示 access_token 过期时间,当 access_token 过期后,使用 refresh_token 重新获取新的 access_token (前提是 refresh_token 未过期),请求地址(注意也是POST请求):http://localhost:8080/oauth/token?grant_type=refresh_token&refresh_token=693b0e36-4515-442a-8c5d-90bade3c74d2&client_id=password&client_secret=123
获取新的 access_token 时需要携带上 refresh_token ,同事授权模式设置为 refresh_token ,在获取的结果中 access_token 会变化,同时 access_token 有效期也会变化,如图
接下来访问所有资源,携带上 access_token 参数即可,例如 /user/hello 接口:http://localhost:8080/user/hello?access_token=0497e4bc-df37-460e-8755-b813b9dbf36a,访问结果如图
如果非法存取一個資源,例如sang 使用者存取/admin/hello 接口,結果如圖
到此,一個password 模式的OAuth 認證體係就搭建成功了。
OAuth 中的認證模式有4 中,開發者需要結合自己開發的實際情況來選擇其中一種,此處介紹的是在前後端分離應用中常用的password 模式,其它的授權模式也都有自己的使用場景。
整體來講,Spring Security OAuth 2 的使用還是較複雜的,配置也比較繁瑣,如果開發者的應用場景比較簡單,完全可以按照此處介紹的授權流程自己構建OAuth 2 認證體系。
以上是SpringBoot安全管理之OAuth2框架怎麼使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!