OAuth2

官网文档

OAuth2 boot

可以先下载我的项目(一个授权服务器,一个资源服务器)跑一下,看下效果。因没有整合前端,所以只能用postman测试
GItHub项目链接

获取token,我这里采用的是密码授权类型
默认是:http://localhost:8080/oauth/token

因yml文件中配置了上下文路径为 uaa
访问路径: http://localhost:8080/uaa/oauth/token

请求参数Value
client_idc1
client_secretsecret
grant_typepassword
usernameadmin
password123456

请求示例:
请求的结果
获取到token后访问资源服务器,示例:
在这里插入图片描述
将获取的token写入Headers 里
Bearer 是固定的,一定要写,空格后面跟你请求来的token。我这里是测试scope报的错,下面具体会讲。

基于token的认证方式,服务端不用存储认证数据,易维护扩展性强, 客户端可以把token 存在任意地方,并且可
以实现web和app统一认证机制。其缺点也很明显,token由于自包含信息,因此一般数据量较大,而且每次请求
都需要传递,因此比较占带宽。另外,token的签名验签操作也会给cpu带来额外的处理负担。

基于token的认证方式,它的优点是:

1、适合统一认证的机制,客户端、一方应用、三方应用都遵循一致的认证机制。
2、token认证方式对第三方应用接入更适合,因为它更开放,可使用当前有流行的开放协议Oauth2.0、JWT等。
3、一般情况服务端无需存储会话信息,减轻了服务端的压力。

OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不
需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。OAuth2.0是OAuth协议的延续版本,但不向
后兼容OAuth 1.0即完全废止了OAuth1.0。

OAauth2.0包括以下角色:

1、客户端
本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源,比如:Android客户端、Web客户端(浏览器端)、微信客户端等。
2、资源拥有者
通常为用户,也可以是应用程序,即该资源的拥有者。
3、授权服务器(也称认证服务器)
用于服务提供商对资源拥有的身份进行认证、对访问资源进行授权,认证成功后会给客户端发放令牌(access_token),作为客户端访问资源服务器的凭据。本例为微信的认证服务器。
4、资源服务器
存储资源的服务器

OAauth2.0主要是通过配置 AuthorizationServerConfigurerAdapter 实现的
源码如下

public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {

	//定义令牌端点上的安全约束
	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
	}
	
	//定义客户端详细信息服务的配置程序。可以初始化客户详细信息
	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
	}
	//定义授权和令牌端点以及令牌服务。
	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
	}
}

三个方法体解释:
ClientDetailsServiceConfigurer ,可以定义一个内存中或JDBC实现的客户端。
其重要属性是:

	clientId:(必填)客户端ID。
	secret:(对于受信任的客户端是必需的)客户端密钥(如果有)。
	scope:客户端受限制的范围。如果范围未定义或为空(默认值),则客户端不受范围的限制。
	authorizedGrantTypes:授权客户使用的授权类型。默认值为空。这里共有四种:authorization_code,password,client_credentials,implicit
	authorities:授予客户端的权限(常规的Spring Security权限)。
	resourceId:资源服务器id

可以通过直接访问底层存储(例如的情况下为数据库表JdbcClientDetailsService)或通过ClientDetailsManager接口(这两种实现都ClientDetailsService可以实现)来更新正在运行的应用程序中的客户端详细信息。

AuthorizationServerEndpointsConfigurer 配置令牌端点以及令牌服务。

配置授权类型(Grant Types)

AuthorizationServerEndpointsConfigurer 通过设定以下属性决定支持的授权类型(Grant Types):
		1、authenticationManager:认证管理器,当你选择了资源所有者密码(password)授权类型的时候,请设置
		这个属性注入一个 AuthenticationManager 对象。
		2、userDetailsService:如果你设置了这个属性的话,那说明你有一个自己的 UserDetailsService 接口的实现,
		或者你可以把这个东西设置到全局域上面去(例如 GlobalAuthenticationManagerConfigurer 这个配置对
		象),当你设置了这个之后,那么 "refresh_token" 即刷新令牌授权类型模式的流程中就会包含一个检查,用
		来确保这个账号是否仍然有效,假如说你禁用了这个账户的话。
		3、authorizationCodeServices:这个属性是用来设置授权码服务的(即 AuthorizationCodeServices 的实例对
		象),主要用于 "authorization_code" 授权码类型模式。
		4implicitGrantService:这个属性用于设置隐式授权模式,用来管理隐式授权模式的状态。
		5、tokenGranter:当你设置了这个东西(即 TokenGranter 接口实现),那么授权将会交由你来完全掌控,并
		且会忽略掉上面的这几个属性,这个属性一般是用作拓展用途的,即标准的四种授权模式已经满足不了你的
		需求的时候,才会考虑使用这个。

令牌服务
AuthorizationServerTokenServices 分三类实现

	1、InMemoryTokenStore  基于内存
	2、JdbcTokenStore 		基于数据库
	3、JSON Web Token (JWT)  基于正常情况下的jwt

要使用JWT令牌,需要在授权服务器中使用JwtTokenstore。资源服务器还需要能够解码令牌,因此JwtTokenstore依赖于JwtAccessTokenconverter,
并且授权服务器和资源服务器都需要相同的实现。默认情况下,签署的标记和资源服务器也能够验证签名,所以它需要相同的对称(签名)密钥授权服务器(共享密钥,或对称密钥),
或者它需要公钥(匹配键)相匹配的私钥(签名密钥的授权服务器(公私合营或非对称密钥)。公钥(如果可用)由授权服务器在/oauth/token_key 端点上公开,它在默认情况下是安全的,
使用访问规则“denyAll(),可以通过向Authorizationserversecurityconfigurer中注入一个标准SpEL表达式来打开它。“permitAll()”可能足够了,因为它是一个公钥)。
我们这里采用对称密钥

配置授权端点的URL(Endpoint URLs):

AuthorizationServerEndpointsConfigurer 这个配置对象有一个叫做 pathMapping() 的方法用来配置端点URL链
接,它有两个参数:

第一个参数:String 类型的,这个端点URL的默认链接。
第二个参数:String 类型的,你要进行替代的URL链接。

以上的参数都将以 "/" 字符为开始的字符串,框架的默认URL链接如下列表,可以作为这个 pathMapping() 方法的
第一个参数:
/oauth/authorize:授权端点。
/oauth/token:令牌端点。
/oauth/confirm_access:用户确认授权提交端点。
/oauth/error:授权服务错误信息端点。
/oauth/check_token:用于资源服务访问的令牌解析端点。
/oauth/token_key:提供公有密匙的端点,如果你使用JWT令牌的话。
需要注意的是授权端点这个URL应该被Spring Security保护起来只供授权用户访问.

授权服务器配置详情如下

package com.security.demo.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
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.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import javax.sql.DataSource;
import java.util.Arrays;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    // 授权码模式需要
/*    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;*/

    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Autowired
    DataSource dataSource;

    // 设置 jwt 秘钥
    private static final  String SIGNINGKEY= "maimai";

    //配置令牌端点的安全服务
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()") //tokenkey这个endpoint当使用JwtToken且使用非对称加密时,资源服务用于获取公钥而开放的,这里指这个 endpoint完全公开。
                .checkTokenAccess("permitAll()") // )checkToken这个endpoint完全公开
                .allowFormAuthenticationForClients(); // 允许表单认证
    }

    //配置客户端详情服务
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
      /*  clients.inMemory().withClient("c1") //client_id 用来标识客户的id
                .secret(passwordEncoder.encode("secret")) //client_secret 客户端秘钥
                .resourceIds("res1") //设置资源服务器id
                .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token") //客户端可以使用的授权类型,默认为空
                .scopes("all") // 用来限制客户端的访问范围,如果为空(默认)那么客户端拥有全部的访问范围
                .autoApprove(true); //false 如果是授权码模式,必须同意后才发放令牌。true 无需同意,默认自动发放*/
        clients.withClientDetails(clientDetailsService()); //从数据库获取
    }

    //初始化一个 clientDetailsService
    public ClientDetailsService clientDetailsService() {
        ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        ((JdbcClientDetailsService) clientDetailsService).setPasswordEncoder(passwordEncoder);
        return clientDetailsService;
    }


    //配置令牌的访问端点和令牌服务
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager) // 密码模式需要
                //.authorizationCodeServices(authorizationCodeServices) // 授权码模式需要
                .tokenServices(tokenService()) //令牌管理服务
                //.pathMapping("/oauth/token","/getToken") //该方法主要是将系统默认的接口改成自定义的接口
                .allowedTokenEndpointRequestMethods(HttpMethod.POST); //允许post提交
    }

    //令牌存储策略
    @Bean
    public TokenStore tokenStore() {
        //生成jwt令牌
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNINGKEY); //对称秘钥,资源服务器使用该秘钥来验证
        return converter;
    }


    @Bean
    public AuthorizationServerTokenServices tokenService() {
        DefaultTokenServices service = new DefaultTokenServices();
        service.setClientDetailsService(clientDetailsService()); //客户端信息服务
        service.setSupportRefreshToken(true); //是否产产生刷新令牌
        service.setTokenStore(tokenStore()); // 令牌存储策略
        //令牌增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter()));
        service.setTokenEnhancer(tokenEnhancerChain);

        service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
        service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
        return service;
    }


  /*  @Bean
    public AuthorizationCodeServices authorizationCodeServices() { //设置授权码模式的授权码如何存取,暂时采用内存方式
        return new InMemoryAuthorizationCodeServices();
    }*/


}

以上均是授权服务器的配置,可以测试一下,看看是否能请求到token

接下来需要新建一个项目配置资源服务器,很简单
资源服务器主要是通过配置 ResourceServerConfigurerAdapter 只需重写这两个方法即可

	public class ResourceServerConfigurerAdapter implements ResourceServerConfigurer {

	@Override
	public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
	}

	@Override
	public void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests().anyRequest().authenticated();
	}

	}

可以@EnableResourceServer在@Configuration类上将其打开,并使用进行配置(根据需要)ResourceServerConfigurer。可以配置以下功能:

ResourceServerSecurityConfigurer主要配置

	1、tokenServices:定义令牌服务(自定义实例ResourceServerTokenServices,主要用来校验token,代码中被我注释了)
	2、resourceId:资源的ID(可选,但建议使用,并将由auth服务器验证(如果存在))。
	3、因为授权服务器中我们采用的是jwt格式,所以要在资源服务器中也要配置jwt解码。其实这里可以通过配置授权服务器的/oauth/check_token 去解析令牌,但我们既然用到了jwt所以就让jwt自己解析就可以了,代码中我会体现到

HttpSecurity
主要就是配置一下http访问资源的权限问题
完整代码如下

import org.springframework.context.annotation.Bean;
	import org.springframework.context.annotation.Configuration;
	import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
	import org.springframework.security.config.annotation.web.builders.HttpSecurity;
	import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
	import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
	import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
	import org.springframework.security.oauth2.provider.token.TokenStore;
	import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
	import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

	/**
	 * 资源服务配置
	 */
	@Configuration
	@EnableResourceServer
	@EnableGlobalMethodSecurity(prePostEnabled = true)
	public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

		private static final String RESOURCE_ID = "res1";

		// 设置 jwt 秘钥
		private static final  String SIGNINGKEY= "maimai";

		@Override
		public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
			resources.resourceId(RESOURCE_ID)
					.tokenStore(tokenStore())
					.stateless(true);
	//                .tokenServices(tokenService())

		}

		@Override
		public void configure(HttpSecurity http) throws Exception {
			// hasScope 用来限制客户端的访问范围,如果为空(默认)那么客户端拥有全部的访问范围
			http.authorizeRequests()
					.antMatchers("/**").authenticated()//.access("#oauth2.hasScope('sss')")
					.and().csrf().disable();

		}

		//令牌存储策略
		@Bean
		public TokenStore tokenStore() {
			//生成jwt令牌
			return new JwtTokenStore(accessTokenConverter());
		}

		@Bean
		public JwtAccessTokenConverter accessTokenConverter() {
			JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
			converter.setSigningKey(SIGNINGKEY); //对称秘钥,资源服务器使用该秘钥来验证
			return converter;
		}

		//屏蔽资源 服务原来的令牌服务类 让jwt 自己验证
		//资源服务令牌解析服务
	   /* @Bean
		public ResourceServerTokenServices tokenService() {
			//使用远程服务请求授权服务器校验token,必须指定校验token 的url、client_id,client_secret
			RemoteTokenServices service = new RemoteTokenServices();
			service.setCheckTokenEndpointUrl("http://localhost:8080/uaa/oauth/check_token");
			service.setClientId("c1");
			service.setClientSecret("secret");
			return service;
		}*/

	}

至此所有的基本配置完成

posted @ 2022-01-20 23:22  暮雨寒冬  阅读(185)  评论(0编辑  收藏  举报