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原理(一)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值