OAuth授權方式
客戶端必須得到用戶的授權(authorization grant),才能獲得令牌(access token)。OAuth 2.0定義了四種授權方式。
- 授權碼模式(authorization code)
- 簡化模式(implicit)
- 密碼模式(resource owner password credentials)
- 客戶端模式(client credentials)
授權碼模式
它的步驟如下:
(A)用戶訪問客戶端,後者將前者導向認證服務器。
(B)用戶選擇是否給予客戶端授權。
(C)假設用戶給予授權,認證服務器將用戶導向客戶端事先指定的"重定向URI"(redirection URI),同時附上一個授權碼。
(D)客戶端收到授權碼,附上早先的"重定向URI",向認證服務器申請令牌。這一步是在客戶端的後臺的服務器上完成的,對用戶不可見。
(E)認證服務器覈對了授權碼和重定向URI,確認無誤後,向客戶端發送訪問令牌(access token)和更新令牌(refresh token)。
A步驟中,客戶端申請認證的URI,包含以下參數:
- response_type:表示授權類型,必選項,此處的值固定爲"code"
- client_id:表示客戶端的ID,必選項
- redirect_uri:表示重定向URI,可選項
- scope:表示申請的權限範圍,可選項
- state:表示客戶端的當前狀態,可以指定任意值,認證服務器會原封不動地返回這個值。
下面是一個例子:
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
C步驟中,服務器迴應客戶端的URI,包含以下參數:
- code:表示授權碼,必選項。該碼的有效期應該很短,通常設爲10分鐘,客戶端只能使用該碼一次,否則會被授權服務器拒絕。該碼與客戶端ID和重定向URI,是一一對應關係。
- state:如果客戶端的請求中包含這個參數,認證服務器的迴應也必須一模一樣包含這個參數。
下面是一個例子:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz
D步驟中,客戶端向認證服務器申請令牌的HTTP請求,包含以下參數:
- grant_type:表示使用的授權模式,必選項,此處的值固定爲"authorization_code"。
- code:表示上一步獲得的授權碼,必選項。
- redirect_uri:表示重定向URI,必選項,且必須與A步驟中的該參數值保持一致。
- client_id:表示客戶端ID,必選項。
下面是一個例子:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
E步驟中,認證服務器發送的HTTP回覆,包含以下參數:
- access_token:表示訪問令牌,必選項。
- token_type:表示令牌類型,該值大小寫不敏感,必選項,可以是bearer類型或mac類型。
- expires_in:表示過期時間,單位爲秒。如果省略該參數,必須其他方式設置過期時間。
- refresh_token:表示更新令牌,用來獲取下一次的訪問令牌,可選項。
- scope:表示權限範圍,如果與客戶端申請的範圍一致,此項可省略。
下面是一個例子:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
springboot2.0+ 和OAuth認證服務器(自動配置)
1、在Springboot2.0+以後在自動配置中已經沒有OAuth了,所以要啓用認證服務器需要增加如下依賴
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
同時定義類,在類上增加@EnableAuthorizationServer註解,爲什麼這樣具體可以看
OAuth2AutoConfiguration -> OAuth2AuthorizationServerConfiguration類
@Configuration
@EnableAuthorizationServer
public class SSOAuthenticationServerConfig {
}
2、實現攔截器(因爲Springboot2.0+以後沒有對/oauth/authorize路徑攔截,)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
}
3、配置認證服務器配置
@Configuration
@EnableAuthorizationServer
public class SSOAuthenticationServerConfig {
@Bean
public BaseClientDetails clientDetail() {
BaseClientDetails clientDetail = new BaseClientDetails();
clientDetail.setClientId("gz");
clientDetail.setClientSecret("gz123");
clientDetail.setRegisteredRedirectUri(new HashSet<String>(Arrays.asList("http://localhost:8083/hello")));
clientDetail.setAuthorizedGrantTypes(Arrays.asList("authorization_code",
"password", "client_credentials", "implicit", "refresh_token"));
clientDetail.setAuthorities(
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
return clientDetail;
}
}
或者在配置文件中
security:
oauth2:
client:
client-id: gz
client-secret: gz123
registered-redirect-uri: http://localhost:8083/hello
grant-type: authorization_code,password,client_credentials,implicit,refresh_token
scope: all
注:自動配置只能配置單個clientId,不能配置多個,如果要配置多個請看(springboot2.0+ 和OAuth認證服務器(手動配置))
4、在瀏覽器中調用http://localhost:8083/oauth/authorize?response_type=code&client_id=gz&state=xyz&redirect_uri=http://localhost:8083/hello
完成步驟C獲取Code信息
5、code獲取token
5.1 輸入url
5.2 生成basic auth,Username->clientId;Password->clientSecret
5.4 填寫請求參數參數
響應結果
6.
7. 使用JWT生成token
@Configuration
@EnableAuthorizationServer
public class SSOAuthenticationServerConfig {
@Bean
public BaseClientDetails clientDetail() {
BaseClientDetails clientDetail = new BaseClientDetails();
clientDetail.setClientId("gz");
clientDetail.setClientSecret("gz123");
clientDetail.setRegisteredRedirectUri(new HashSet<String>(Arrays.asList("http://localhost:8083/hello")));
clientDetail.setAuthorizedGrantTypes(Arrays.asList("authorization_code",
"password", "client_credentials", "implicit", "refresh_token"));
clientDetail.setAuthorities(
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER1"));
clientDetail.setScope(Arrays.asList("all"));
return clientDetail;
}
// 只需要增加如下bean
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtConverter = new JwtAccessTokenConverter();
jwtConverter.setSigningKey("gz");
return jwtConverter;
}
}
springboot2.0+ 和OAuth認證服務器(手動配置)
參考springboot2.0+ 和OAuth認證服務器(自動配置) 將步驟3改成如下
@Configuration
@EnableAuthorizationServer
public class SSOAuthenticationServerConfig2 extends AuthorizationServerConfigurerAdapter{
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("gz")
.secret("gz123")
.authorizedGrantTypes("authorization_code",
"password", "client_credentials", "implicit", "refresh_token")
.redirectUris("http://localhost:8083/hello")
.scopes("all")
.and()
.withClient("gz1")
.secret("gz123")
.authorizedGrantTypes("authorization_code",
"password", "client_credentials", "implicit", "refresh_token")
.redirectUris("http://localhost:8084/hello")
.scopes("all");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.accessTokenConverter(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtConverter = new JwtAccessTokenConverter();
jwtConverter.setSigningKey("gz");
return jwtConverter;
}
@SuppressWarnings("deprecation")
@Override
public void configure(AuthorizationServerSecurityConfigurer security)
throws Exception {
// 必須配置,否則在BasicAuthenticationFilter認證時會報錯
security.passwordEncoder(NoOpPasswordEncoder.getInstance());
}
}
注意:
- springboot2.0+必須配置ClientDetailsServiceConfigurer 屬性及redirectUris,scopes,authorizedGrantTypes
- springboot2.0+ 和OAuth認證服務器(手動配置)中,要麼在public void configure(AuthorizationServerSecurityConfigurer security)方法中注入不加密的密碼解析器,要麼做如下配置clients.inMemory().secret("{noop}gz123")增加{noop}
- springboot2.0+ 和OAuth認證服務器(自動配置)步驟1中和資源服務器整合時,不能用
http.formLogin()
, - springboot2.0+ 和OAuth認證服務器(手動配置)中利用@EnableOAuth2Sso,啓用Jwt必須將其註冊到Spring容器中,不能new
@Configuration
@EnableAuthorizationServer
public class SSOAuthenticationServerConfig2 extends AuthorizationServerConfigurerAdapter{
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("gz")
.secret("{noop}gz123")
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
.redirectUris("http://localhost:8083/hello")
.scopes("all")
.and()
.withClient("gz1")
.secret("{noop}gz123")// 重要配置
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
.redirectUris("http://localhost:8085/hello")
.scopes("all");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.accessTokenConverter(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtConverter = new JwtAccessTokenConverter();
jwtConverter.setSigningKey("gz");
return jwtConverter;
}
密碼模式
採用密碼模式,在禁用Springboot Oauth自動配置後,訪問會報如下錯誤,是因爲缺少authenticationManager
具體請看AuthorizationServerEndpointsConfigurer.getDefaultTokenGranters()方法
private List<TokenGranter> getDefaultTokenGranters() {
ClientDetailsService clientDetails = clientDetailsService();
AuthorizationServerTokenServices tokenServices = tokenServices();
AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
OAuth2RequestFactory requestFactory = requestFactory();
List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,
requestFactory));
tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
tokenGranters.add(implicit);
tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
// 如果authenticationManager 不爲空,則啓用密碼模式
if (authenticationManager != null) {
tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,
clientDetails, requestFactory));
}
return tokenGranters;
}
密碼模式使用(手動配置)
注:密碼模式自動配置不需要再定義AuthenticationManager
1、增加AuthenticationManager bean
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
// 重點
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
2、在AuthorizationServerEndpointsConfigurer 中增加AuthenticationManager
@Configuration
@EnableAuthorizationServer
public class SSOAuthenticationServerConfig extends AuthorizationServerConfigurerAdapter{
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("gz")
.secret("{noop}gz123")
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
.redirectUris("http://localhost:8084/hello")
.scopes("all");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 增加.authenticationManager(authenticationManager);
endpoints.accessTokenConverter(jwtAccessTokenConverter()).authenticationManager(authenticationManager);
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtConverter = new JwtAccessTokenConverter();
jwtConverter.setSigningKey("gz");
return jwtConverter;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security)
throws Exception {
security.tokenKeyAccess("isAuthenticated()");
}
}
3、編寫UserDetailsService類(必須配置)
@Component
public class ConstumUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User("gz","{noop}123456",true,true,true,true, AuthorityUtils.createAuthorityList("admin"));
}
}
4、修改application.yml配置文件
server:
port: 8083
logging:
level:
root: INFO
5、測試