@
OAuth2.0系列博客:
- OAuth2.0系列之基本概念和運作流程(一)
- OAuth2.0系列之授權碼模式實踐教程(二)
- OAuth2.0系列之簡化模式實踐教程(三)
- OAuth2.0系列之密碼模式實踐教程(四)
- OAuth2.0系列之客戶端模式實踐教程(五)
- OAuth2.0系列之信息數據庫存儲教程(六)
- OAuth2.0系列之信息Redis存儲教程(七)
- OAuth2.0系列之JWT令牌實踐教程(八)
- OAuth2.0系列之集成JWT實現單點登錄
1、文章前言介紹
在前面文章中我們學習了OAuth2的一些基本概念,對OAuth2有了基本的認識,也對OAuth2.0的令牌、授權碼等進行了jdbc方式的數據存儲,接着本文再寫一遍Redis版本的
IDEA中,Ctrl+Alt+B,可以看到TokenStore的實現,有如下幾種:
ok,其實對於token存儲有如上方式,對於其它比如授權碼code等的存儲也基本如上幾種,分別進行介紹:
- InMemoryTokenStore,默認存儲,保存在內存
- JdbcTokenStore,access_token存儲在數據庫
- JwtTokenStore,JWT這種方式比較特殊,這是一種無狀態方式的存儲,不進行內存、數據庫存儲,只是JWT中攜帶全面的用戶信息,保存在jwt中攜帶過去校驗就可以
- RedisTokenStore,將 access_token 存到 redis 中。
- JwkTokenStore,將 access_token 保存到 JSON Web Key。
ClientDetailsService的實現只有兩種,並沒有redis存儲的:
2、典型例子實踐
實驗環境准備:
- IntelliJ IDEA
- Maven3.+版本
新建SpringBoot Initializer項目,可以命名oauth2-redis-store
主要加入如下配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Cloud Oauth2-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- Spring Cloud Security-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
pom加入redis配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
redis配置:
server.port=8888
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
令牌存儲配置:
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Bean
TokenStore tokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
OAuth2.0配置類:
package com.example.springboot.oauth2.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
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;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
/**
* <pre>
* OAuth2.0配置類
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改后版本: 修改人: 修改日期: 2020/06/16 17:34 修改內容:
* </pre>
*/
@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
private static final String CLIENT_ID = "cms";
private static final String SECRET_CHAR_SEQUENCE = "{noop}secret";
private static final String SCOPE_READ = "read";
private static final String SCOPE_WRITE = "write";
private static final String TRUST = "trust";
private static final String USER ="user";
private static final String ALL = "all";
private static final int ACCESS_TOKEN_VALIDITY_SECONDS = 2*60;
private static final int FREFRESH_TOKEN_VALIDITY_SECONDS = 2*60;
// 密碼模式授權模式
private static final String GRANT_TYPE_PASSWORD = "password";
//授權碼模式
private static final String AUTHORIZATION_CODE = "authorization_code";
//refresh token模式
private static final String REFRESH_TOKEN = "refresh_token";
//簡化授權模式
private static final String IMPLICIT = "implicit";
//指定哪些資源是需要授權驗證的
private static final String RESOURCE_ID = "resource_id";
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
// 使用內存存儲
.inMemory()
//標記客戶端id
.withClient(CLIENT_ID)
//客戶端安全碼
.secret(SECRET_CHAR_SEQUENCE)
//為true 直接自動授權成功返回code
.autoApprove(true)
.redirectUris("http://127.0.0.1:8084/cms/login") //重定向uri
//允許授權范圍
.scopes(ALL)
//token 時間秒
.accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
//刷新token 時間 秒
.refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS)
//允許授權類型
.authorizedGrantTypes(GRANT_TYPE_PASSWORD , AUTHORIZATION_CODE , REFRESH_TOKEN , IMPLICIT);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager)
//支持獲取token方式
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST,HttpMethod.PUT,HttpMethod.DELETE,HttpMethod.OPTIONS)
//刷新token
.reuseRefreshTokens(true);
}
/**
* 認證服務器的安全配置
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
// 開啟/oauth/token_key驗證端口認證權限訪問
.tokenKeyAccess("isAuthenticated()")
// 開啟/oauth/check_token驗證端口認證權限訪問
.checkTokenAccess("isAuthenticated()")
//允許表單認證 在授權碼模式下會導致無法根據code獲取token
.allowFormAuthenticationForClients();
}
@Bean
TokenStore tokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
}
SpringSecurity配置:
package com.example.springboot.oauth2.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* <pre>
* Spring Security配置類
* </pre>
*
* <pre>
* @author mazq
* 修改記錄
* 修改后版本: 修改人: 修改日期: 2020/06/15 10:39 修改內容:
* </pre>
*/
@Configuration
@EnableWebSecurity
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { //auth.inMemoryAuthentication()
auth.inMemoryAuthentication()
.withUser("nicky")
.password("{noop}123")
.roles("admin");
}
@Override
public void configure(WebSecurity web) throws Exception {
//解決靜態資源被攔截的問題
web.ignoring().antMatchers("/asserts/**");
web.ignoring().antMatchers("/favicon.ico");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http // 配置登錄頁並允許訪問
.formLogin().permitAll()
// 配置Basic登錄
//.and().httpBasic()
// 配置登出頁面
.and().logout().logoutUrl("/logout").logoutSuccessUrl("/")
.and().authorizeRequests().antMatchers("/oauth/**", "/login/**", "/logout/**").permitAll()
// 其余所有請求全部需要鑒權認證
.anyRequest().authenticated()
// 關閉跨域保護;
.and().csrf().disable();
}
}
3、功能簡單測試
訪問授權鏈接,在瀏覽器訪問就可以,授權碼模式response_type參數傳code:
http://localhost:8888/oauth/authorize?client_id=cms&client_secret=secret&response_type=code
因為沒登錄,所以會返回SpringSecurity的默認登錄頁面,具體代碼是 http .formLogin().permitAll();
,如果要彈窗登錄的,可以配置http.httpBasic();
,這種配置是沒有登錄頁面的,自定義登錄頁面可以這樣配置http.formLogin().loginPage("/login").permitAll()
如圖,輸入SpringSecurity配置的數據庫密碼
登錄成功,返回redirect_uri,拿到授權碼
重定向回redirect_uri,http://localhost:8084/cms/login?code=???
配置一下請求頭的授權參數,用Basic Auth方式,username即client_id,password即client_secret
拿到授權碼之后去獲取token,本教程使用授權碼方式
例子代碼下載:code download