# oauth2 管理

使用 oauth2 功能首先需要按照 安全管理 的步骤开启 spring security 的相关功能。

快速启动

1 在项目中加入 oauth 相关的依赖

<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.yishuifengxiao.common</groupId>
    <artifactId>common-spring-boot-starter</artifactId>
    <version>4.2.1</version>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

2 在项目加入以下代码

    @Configuration
	public class CustomOauth2Config extends Oauth2Config{

	}

3 加上@EnableResourceServer@EnableAuthorizationServer注解

完全开启示例代码如下:

@Configuration
@EnableWebSecurity
@EnableResourceServer
@EnableAuthorizationServer
public class SecurityConfig extends AbstractSecurityConfig {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		super.configure(http);
	}

	@Configuration
	public class CustomOauth2Config extends Oauth2Config{

	}

}

4 实现ClientDetailsService接口,完成自己的认证逻辑

完成自定义实现后,按照名字customClientDetailsService向spring中注入一个ClientDetailsService实例

【特别注意】在用户未按照(4)中的步骤配置自己的授权逻辑时,组件会默认进行一个缺省实现。在缺省实现的情况下,用户能使用任意用户名配合密码(12345678)进行登录




# 四种授权方式

# 密码模式

POST /oauth/token HTTP/1.1
     Host: oauth2.yishuifengxiao.com
     Authorization: Basic fdsfdsfdsfds
     Content-Type: application/x-www-form-urlencoded

     grant_type=password&username=johndoe&password=A3ddj3w

在请求中,各参数的含义如下

  • grant_type:表示授权类型,此处的值固定为"password",必选项。
  • username:表示用户名,必选项。
  • password:表示用户的密码,必选项。
  • scope:表示权限范围,可选项。
  • Authorization: 请求头参数 ,值是 clientId:clientSecret 经过 base64 编码后的值

下面是一个响应的例子

{
    "access_token": "BDF867DE69F05143C709",
    "token_type": "bearer",
    "refresh_token": "d7cda8fb15714209a9f9f3b039a0034f",
    "expires_in": 43199,
    "scope": "read write trust",
    "client_id": "yishui"
}

# 客户端模式

POST /oauth/token HTTP/1.1
     Host: oauth2.yishuifengxiao.com
     Authorization: Basic fdsfdsfdsfds
     Content-Type: application/x-www-form-urlencoded

     grant_type=client_credentials

在本请求中,各参数的含义如下

  • grant_type:表示授权类型,此处的值固定为"client_credentials",必选项。
  • Authorization: 请求头参数 ,值是 clientId:clientSecret 经过 base64 编码后的值

下面是一个响应的例子

{
    "access_token": "BDF867DE69F05143D3BF",
    "token_type": "bearer",
    "expires_in": 43199,
    "scope": "read write trust",
    "client_id": "yishui"
}

同密码模式相比,客户端模式的响应中缺少了 refresh_token 参数

# 授权码模式

授权码模式首先需要保证 spring security 的登陆功能正常可用。只有开启 spring security 的登陆功能可用,才能开启授权码功能。

先访问一下请求

GET /oauth/authorize?response_type=code&client_id=yishui&state=xyz
&redirect_uri=http://demo.yishuifengxiao.com/demo HTTP/1.1
Host: oauth2.yishuifengxiao.com

在本请求中,各参数的含义如下:

  • code:表示授权码,必选项。该码的有效期应该很短,通常设为 10 分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端 ID 和重定向 URI,是一一对应关系。
  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
  • client_id: 用户的 client_id

在进行此请求时,假如用户没有登录,spring security 会进行拦截,因此需要用户先进行登录。

在正常情况下,访问以上请求会被重定向到

http://demo.yishuifengxiao.com/demo?code=fsfsdf &state=xyz

服务器回应客户端的 URI,包含以下参数:

  • code:表示授权码,必选项。该码的有效期应该很短,通常设为 10 分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端 ID 和重定向 URI,是一一对应关系。
  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。

通过上面的请求得到了code以后,用户需要使用下面请求获取到授权码

POST /oauth/token HTTP/1.1
Host: oauth2.yishuifengxiao.com
Authorization: Basic fdsfdsfdsfds
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=fsfsdf
&redirect_uri=demo.yishuifengxiao.com/demo

在本请求中,各参数的含义如下:

  • grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code"。
  • code:表示上一步获得的授权码,必选项。
  • redirect_uri:表示重定向 URI,必选项,且必须与 A 步骤中的该参数值保持一致。
  • client_id:表示客户端 ID,必选项。

# 简化模式

GET /oauth/authorize?response_type=token&client_id=yishui&state=xyz
        &redirect_uri=http://demo.com/demo    HTTP/1.1
    Host: server.example.com

在本请求中,各参数的含义如下:

  1. response_type:表示授权类型,此处的值固定为"token",必选项。
  2. client_id:表示客户端的 ID,必选项。
  3. redirect_uri:表示重定向的 URI,可选项。
  4. scope:表示权限范围,可选项。
  5. state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。

# 刷新 token

POST /oauth/token HTTP/1.1
     Host: oauth2.yishuifengxiao.com
     Authorization: Basic fdsfdsfdsfds
     Content-Type: application/x-www-form-urlencoded

     grant_type=refresh_token&refresh_token=sdff

请求中个参数的含义:

  • granttype:表示使用的授权模式,此处的值固定为"refreshtoken",必选项。
  • refresh_token:表示早前收到的更新令牌,必选项。
  • scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。



# access_token 使用

通过 前面的方法获取到 access_token 之后,一般有两种使用方法

  • 将 access_token 做为请求参数携带在 url 参数上

http://demo.yishuifengxiao.com/user/123?access_token=获得到的access_token

  • 将 access_token 做为请求参数放在请求头中

在所有需要授权的请求的请求头里都携带上参数 Authorization=Bearer 获得到的 access_token

在通用组件中,由于对access_token进行了深度处理,因此用户可以access_token通过易水工具包里的 DES 工具饭解析出 token 里携带的信息。

在解密时需要使用的密钥由【安全管理】中设置的yishuifengxiao.security.secret-key属性值决定。

下面是一个 access_token 的解密信息示例

{
    "username": "yishui",
    "clientId": "admin",
    "roles": [
        "ROLE_USER",
        "admin"
    ],
    "grantType": "password"
}

解密信息的各参数的解释:

  • username: 用户登录时使用到的用户名(在客户端模式下该值为空)
  • clientId: 用户登录时使用的 clientId(在简化模式下该值为空)
  • roles: 此登录用户拥有的角色(即此用户的 authorities)
  • grantType:access_token 对应的授权类型

access_token反解析出用户信息仅限于本组件,原生的 oauth2 的access_token不支持此功能

在本组件中,每次访问后都会重置令牌的自动过期时间




# 异常转换

在本组件中,默认由Auth2ResponseExceptionTranslator实现 oauth2 中的异常转换。如果需要自定义异常转换过程,只需要在 spring 的上下文中注入一个名为auth2ResponseExceptionTranslatorWebResponseExceptionTranslator实例即可。

示例代码如下:

	@Bean("auth2ResponseExceptionTranslator")
	public WebResponseExceptionTranslator auth2ResponseExceptionTranslator() {
		return new Auth2ResponseExceptionTranslator();
	}




# token生成策略

在一般情况下,只需要使用系统默认的 token 生成策略即可。但是在某些需求的情况下,如需要在 oauth2 模式下限制多终端登录等,即需要自定义 token 生成策略了,此时,只需要在 spring 的上下文注入一个名为authorizationServerTokenServicesAuthorizationServerTokenServices示例即可。

示例代码如下:

    /**
     * 自定义token生成规则
     *
     * @param tokenStore
     * @param clientDetailsService
     * @param accessTokenEnhancer
     * @param authenticationManager
     * @return
     */
    @Bean("authorizationServerTokenServices")
    public AuthorizationServerTokenServices authorizationServerTokenServices(TokenStore tokenStore,
    		ClientDetailsService clientDetailsService, TokenEnhancer accessTokenEnhancer,
    		AuthenticationManager authenticationManager) {
    	DefaultTokenServices tokenServices = new DefaultTokenServices();
    	tokenServices.setTokenStore(tokenStore);
    	tokenServices.setClientDetailsService(clientDetailsService);
    	tokenServices.setTokenStore(tokenStore);
    	tokenServices.setAuthenticationManager(authenticationManager);
    	return tokenServices;
    }

DefaultTokenServices中,用户就可以自定义 token 生成策略。




# token存储策略

在组件中,token的存储方式有三种

  1. 在系统中没有配置reids时默认存储在内存中,由 InMemoryTokenStore负责处理
  2. 在系统中配置有redis连接时默认存储在redis里,由RedisTokenStore负责处理
  3. 自定义token存储策略

在自定义token存储策略,只需要向spring 上下文中注入一个名为 tokenStoreTokenStore实例即可。 示例如下:

	@Bean("tokenStore")
	public TokenStore tokenStore() {
		return new InMemoryTokenStore();
	}



# token提取器

在默认情况下,系统使用的是组件提供的默认token提取器从请求中提取出来token信息。从请求中提取token的策略如下:

  1. 先从请求头中获取名为Authorization的参数的值,且该值以Bearer开头
  2. 如果上步骤中没有获取到信息,就从请求参数中提取名为access_token的参数的值
  3. 如果上步骤中还没有获取到信息,就从session中获取获取名为yishuifengxiao.session.token的session的值

如果用户想要自定义token提取方式,只需要向spring security中注入一个名为tokenExtractorTokenExtractor实例即可。

	/**
	 * 自定义token提取器
	 * 
	 * @return
	 */
	@Bean("tokenExtractor")
	@ConditionalOnMissingBean(name = "tokenExtractor")
	public TokenExtractor tokenExtractor() {
		return new CustomTokenExtractor();
	}



# token前置验证

为了在BasicAuthenticationFilter之前进行前置验证,组件向系统中注入了一个名为tokenEndpointAuthenticationFilterOncePerRequestFilter实例,用来进行前置验证。用户如果有自定义要求,可以注入自定义处理策略。示例代码如下:

	/**
	 * 获取token时在BasicAuthenticationFilter之前增加一个过滤器
	 * 
	 * @return
	 */
	@Bean("tokenEndpointAuthenticationFilter")
	@ConditionalOnMissingBean(name = "tokenEndpointAuthenticationFilter")
	public TokenEndpointAuthenticationFilter tokenEndpointAuthenticationFilter(ApplicationContext contentx,
			ClientDetailsService clientDetailsService, PasswordEncoder passwordEncoder) {
		TokenEndpointAuthenticationFilter tokenEndpointAuthenticationFilter = new TokenEndpointAuthenticationFilter();
		tokenEndpointAuthenticationFilter.setClientDetailsService(clientDetailsService);
		tokenEndpointAuthenticationFilter.setPasswordEncoder(passwordEncoder);
		tokenEndpointAuthenticationFilter.setContentx(contentx);
		return tokenEndpointAuthenticationFilter;
	}



# token工具类

token工具类主要是在oauth2中用于进行各种token操作。token工具类的主要方法的签名如下:

/**
	 * 根据token的值移除存储的登录token
	 * 
	 * @param tokenValue token的
	 * @return 移除成功返回为true,否则为false
	 */
	public boolean removeToken(String tokenValue);

	/**
	 * 根据请求里token信息移除存储的登录token <br/>
	 * token 的提取方式参见 TokenExtractor
	 * 
	 * @param request HttpServletRequest
	 * @return 移除成功返回为true,否则为false
	 */
	public boolean removeToken(HttpServletRequest request) ;

	/**
	 * 根据认证信息生成token
	 * 
	 * @param request
	 * @param username     用户名
	 * @param clientId
	 * @param clientSecret 原始终端密码
	 * @param grantType    授权类型,默认为 custome
	 * @return
	 */
	public OAuth2AccessToken createToken(HttpServletRequest request, String username, String clientId,
			String clientSecret, String grantType) ;

	/**
	 * 根据认证信息生成token
	 * 
	 * @param request
	 * @param username     用户名
	 * @param clientId
	 * @param clientSecret 原始终端密码
	 * @param grantType    授权类型,默认为 custome
	 * @return
	 */
	public OAuth2AccessToken createToken(HttpServletRequest request, String username, String clientId,
			String grantType) ;

	/**
	 * 根据认证信息生成token
	 * 
	 * @param request
	 * @param username      用户名
	 * @param clientDetails 终端信息
	 * @param clientSecret  原始终端密码
	 * @param grantType     授权类型,默认为 custome
	 * @return
	 */
	public OAuth2AccessToken createToken(HttpServletRequest request, String username, ClientDetails clientDetails,
			String grantType);

	/**
	 * 根据认证信息生成token 【请求头中必须包含basic信息】
	 * 
	 * @param request
	 * @param authentication spring security登陆成功后的认证信息
	 * @param grantType      授权类型
	 * @return
	 * @throws IOException
	 */
	public OAuth2AccessToken createToken(HttpServletRequest request, Authentication authentication, String grantType)
			throws IOException ;

	/**
	 * 根据认证信息和客户端信息生成token
	 *
	 * @param authentication spring security登陆成功后的认证信息
	 * @param clientId       clientId
	 * @param clientSecret   原始终端密码
	 * @param grantType      授权类型,默认为custome
	 * @return OAuth2AccessToken
	 */
	public OAuth2AccessToken createToken(Authentication authentication, String clientId, String clientSecret,
			String grantType);

	/**
	 * 根据认证信息和客户端id信息生成token </br>
	 * <b>注意此方法不会校验终端密码,一定要在可信环境下使用</b>
	 * 
	 * @param authentication spring security登陆成功后的认证信息
	 * @param clientId       clientId
	 * @param grantType      授权类型,默认为custome
	 * @return OAuth2AccessToken
	 */
	public OAuth2AccessToken createToken(Authentication authentication, String clientId, String grantType) ;

	/**
	 * 根据认证信息生成token
	 * 
	 * @param authentication spring security登陆成功后的认证信息
	 * @param clientDetails  终端登录成功后的认证信息
	 * @param grantType      授权类型,默认为custome
	 * @return
	 */
	public OAuth2AccessToken createToken(Authentication authentication, ClientDetails clientDetails, String grantType);

	/**
	 * 根据终端id获取到终端的完整信息
	 * 
	 * @param clientId
	 * @return
	 */
	private ClientDetails extracted(String clientId) ;

	/**
	 * 根据用户生成UsernamePasswordAuthenticationToken
	 * 
	 * @param request
	 * @param username 用户名
	 * @return
	 */
	private UsernamePasswordAuthenticationToken extracted(HttpServletRequest request, String username) ;

Last Updated: 12/1/2020, 10:13:36 PM