在OAuth2.0裏面有以下這幾種認證方式。
- authorization_code 授權碼認證
- client_credentials 客戶端認證
- password 密碼認證
- implicit 隱式授權認證
- refresh_token 刷新密鑰
一般我們會在認證請求的grant_type參數中指定採用何種方式去進行認證。比如下面的一個鏈接,通過grant_type=authorization_code指定採用授權碼認證。
http://localhost:8080/oauth/token?grant_type=authorization_code&client_id=client&client_secret=123456&code=jRvetc&redirect_uri=http://www.baidu.com
spring-oauth2 (bearer)是基於spring-security的驗證機制,
對於第三方訪問受限資源時通過token機制來驗證
驗證steps:
通過時序圖來看一下,驗證方式:
1、發送username, password, client_id, client_secret, grant_type到server
2、server返回包括access_token, token_type, refresh_token, expires_in
3、客戶端向資源服務器發起請求後,資源服務器會首先拿接受的token去權限服務器驗證,如果驗證通過,才繼續執行請求。
下面是一些默認的端點 URL:
/oauth/authorize:授權端點
/oauth/token:令牌端點
/oauth/confirm_access:用戶確認授權提交端點
/oauth/error:授權服務錯誤信息端點
/oauth/check_token:用於資源服務訪問的令牌解析端點
/oauth/token_key:提供公有密匙的端點,如果你使用JWT令牌的話
授權端點的 URL 應該被 Spring Security 保護起來只供授權用戶訪問
下面看看具體的項目案例:
一、創建auth2_server工程
注意:名稱中不能有大寫字母
選擇依賴:
選擇創建工程的名稱和保存的目錄:
項目新建完成。
二、配置認證中心AuthorizationServer
核心是繼承AuthorizationServerConfigurerAdapter
主要是實現三個配置方法:
1、配置認證相關屬性
configure(AuthorizationServerSecurityConfigurer security)
2.配置客戶端詳情
configure(ClientDetailsServiceConfigurer clients)
3、配置認證服務的端點Endpoints相關屬性
configure(AuthorizationServerEndpointsConfigurer endpoints)
實現如下:
在實現的AuthorizationServerConfig配置類上加上@EnableAuthorizationServer註解,說明該工程作爲認證中心。
/**
* @program: oauth2_server
* @description: AuthorizationServer配置
* @author: wanli
* @create: 2019-05-22 16:00
**/
@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
// accessToken有效期
private int accessTokenValiditySeconds = 7200; // 兩小時
private int refreshTokenValiditySeconds = 7200; // 兩小時
/**
* 配置認證客戶端ClientDetailsService
* @param clients
* @throws Exception
*/
@Override
public void configure( ClientDetailsServiceConfigurer clients ) throws Exception {
//這裏主要配置的是客戶端的信息,而不是認證用戶的信息
//添加客戶端信息
clients.inMemory() // 使用in-memory存儲客戶端信息
.withClient("client") // client_id
.secret("{noop}123456") // client_secret
.redirectUris("http://www.baidu.com")
.authorizedGrantTypes("authorization_code","password","client_credentials","refresh_token","implicit")// 該client允許的授權類型
.scopes("app") // 允許的授權範圍
.accessTokenValiditySeconds(accessTokenValiditySeconds) //有效期時間
.refreshTokenValiditySeconds(refreshTokenValiditySeconds)
;
}
/**
* 配置認證服務 oauthServer
* @param oauthServer
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
// 允許表單認證
oauthServer.allowFormAuthenticationForClients();
// 允許check_token訪問
oauthServer.checkTokenAccess("permitAll()");
}
/**
* 配置訪問端口endpoints
* @param endpoints
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager())
//允許get,post方法訪問 (默認獲取token只能post方法)
// .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST)
;
}
@Bean
AuthenticationManager authenticationManager() {
AuthenticationManager authenticationManager = new AuthenticationManager() {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return daoAuhthenticationProvider().authenticate(authentication);
}
};
return authenticationManager;
}
@Bean
public AuthenticationProvider daoAuhthenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService());
daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
daoAuthenticationProvider.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
return daoAuthenticationProvider;
}
// 設置添加用戶信息,正常應該從數據庫中讀取
@Bean
UserDetailsService userDetailsService() {
InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
userDetailsService.createUser(User.withUsername("user1").password("{noop}123456")
.authorities("ROLE_USER").build());
userDetailsService.createUser(User.withUsername("user2").password("{noop}123456")
.authorities("ROLE_USER").build());
return userDetailsService;
}
}
啓動報錯:
'authorizationEndpoint' threw exception; nested exception is java.lang.NoClassDefFoundError: javax/servlet/ServletException
原因:
沒有加入spring-boot-starter-web依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
啓動成功,默認端口8080
授權碼認證 authorization_code
出現如下異常:
User must be authenticated with Spring Security before authorization can be completed.
原因是:
進行oauth2認證前,必須先經過Spring Security的認證。
添加 Spring Security認證
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// 攔截所有請求,使用httpBasic方式登陸
@Override
protected void configure(HttpSecurity http) throws Exception {
//攔截所有請求 通過httpBasic進行認證
http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic();
}
}
用戶名,密碼是在UserDetailsService 中配置:
user1
123456
登錄成功,出現授權頁面:
選擇授權,重定向到百度,並在鏈接後面加上了授權碼:
使用授權碼去獲取token
使用postman發起請求:
http://localhost:8080/oauth/token?grant_type=authorization_code&client_id=client&client_secret=123456&code=jRvetc&redirect_uri=http://www.baidu.com
返回結果:
客戶端認證client_credentials
密碼認證password
刷新祕鑰refresh_token
post請求
http://localhost:8080/oauth/token?grant_type=refresh_token&client_id=client&client_secret=123456&refresh_token=4695c05c-de3b-4d1d-b98f-b0df204df46b
一直出現如下異常:
後臺的異常信息爲:
Handling error: IllegalStateException, UserDetailsService is required.
解決:
刷新祕鑰refresh_token 的認證必須在AuthorizationServerEndpointsConfigurer配置中指定UserDetailsService 。不指定,對其他四種認證獲取token是沒有影響的。
隱式授權類型 implicit
適合直接在前端應用獲取token的應用
注意:implicit和authorization_code授權請求的區別主要是
implicit的response_type=token
authorization_code的response_type = code
指定然後類型是token,請求成功後,直接將token信息鏈接到重定向地址後面。所以這個認證方式的安全級別相對較低。
一、get請求:
http://localhost:8080/oauth/authorize?client_id=client2&client_secret=123456&response_type=token&redirect_uri=http://www.baidu.com
二、在界面上,授權允許訪問
三、根據配置的redirect_uri進行重定向,並在鏈接後面直接返回access_token
請求結果:
https://www.baidu.com/#access_token=d2addcfb-e29a-44ec-95c3-e4d5f2e80663&token_type=bearer&expires_in=6967&scope=app
OAuth客戶端運行在瀏覽器中(Javascript、Flash等)
瀏覽器絕對可信,因爲該類型可能會將訪問令牌泄露給惡意用戶或應用程序。
流程剖析:
其他說明:
1、如果添加spring-cloud-starter-security的依賴後,什麼都不配置,默認攔截所有請求
示例,添加/hello測試方法
@SpringBootApplication
@RestController
public class Oauth2ServerApplication {
public static void main(String[] args) {
SpringApplication.run(Oauth2ServerApplication.class, args);
}
@ResponseBody
@RequestMapping("hello")
public String hello(){
return "hello";
}
}
請求http://localhost:8080/hello,會自動重定向到http://localhost:8080/login
默認用戶是:user
密碼是項目啓動隨機生成的。
Using generated security password: bd661305-b0bb-44d8-95ae-9655a649a5f0
2、如果在屬性文件中配置security的用戶屬性,那麼就不會生成默認用戶user信息
spring.security.user.name=admin
spring.security.user.password=123456
3、在WebSecurityConfig配置類中添加默認用戶
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.inMemoryAuthentication()
.withUser(“user”).password("{noop}123456").roles(“USER”)
.and()
.withUser(“admin”).password("{noop}123456").roles(“USER”,“ADMIN”);
}
(1)注意密碼的加密方式,這裏採用的{noop},不加密
(2)只要配置了WebSecurityConfig配置類,那麼認證失敗重定向就會從頁面,變成小彈窗
(3)在AuthorizationServerConfig和WebSecurityConfig兩個配置類中,不要出現重複的配置屬性,
比如,針對http是否攔截的配置,針對AuthenticationManager 的Bean的配置
github源碼:
https://github.com/StarlightWANLI/oauth2.git
如果你還有其他OAuth2.0認證方面的想了解的內容,可以在底下評論留言。我會盡力抽空調研下。
最後,如果我寫的文章對您能有些許幫助,請幫忙點贊、關注。
下面是我的微信公衆號,也會不定期更新一些java方面的內容,歡迎大家來撩。