Spinrg Security原理 ------OAuth使用認證服務器(三)

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、測試

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

Spinrg Security原理 ------OAuth原理(一)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章