# oauth2 入门教程

# 一 快速启动

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

需要 导入 易水公共组件spring-security-oauth2-autoconfigure两个依赖

<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 {
		// 调用父类中的默认配置
		applyAuthenticationConfig(http);
	}

	@Configuration
	public class CustomOauth2Config extends Oauth2Config{

	}

}

# 4 实现自定义认证逻辑

实现UserDetailsService接口 和 ClientDetailsService接口,完成自己的认证逻辑,并将其注入到 spring 的上下文中

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

需要特别注意的是,在下面的授权方式之中,ClientDetailsService负责校验clientIdclientSecret是否正确,UserDetailsService负责校验usernamepassword是否正确。

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


@Component("customClientDetailsService")
public class ClientDetailsServiceImpl implements ClientDetailsService {
   /**
    * 默认的允许的认证类型
    */
   private final static String DEFAULT_GRANT_TYPE = "password,authorization_code,refresh_token,implicit,client_credentials";
   /**
    * 默认的token有效时间,60分钟
    */
   private final static int TOKEN_VALID_TIME = 60 * 60;
   /**
    * 默认的刷新token有效时间,默认为12小时
    */
   private final static int TOKEN_REDRESH_TIME = 60 * 60 * 12;
   /**
    * 默认的scope
    */
   private final static String DEFAULT_SCOPE = "read,write,trust";
   /**
    * 默认同意的自动授权域
    */
   private final static String DEFAULT_APPROVE_SCOPE = "true";
   /**
    * 默认的授权机构
    */
   // private final static String DEFAULT_AUTHORITY =
   // "ROLE_CLIENT,ROLE_TRUSTED_CLIENT";

   private final static String DEFAULT_URL = "http://localhost:8080/";

   @Autowired
   private PasswordEncoder passwordEncoder;

   @Override
   public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
   	// 查询出客户端
   	BaseClientDetails clientDetails = new BaseClientDetails();

   	// @formatter:off

   	// 用于唯一标识每一个客户端(client); 在注册时必须填写(也可由服务端自动生成).
   	// 对于不同的grant_type,该字段都是必须的. 在实际应用中的另一个名称叫appKey,与client_id是同一个概念.
   	clientDetails.setClientId(clientId);

   	// 用于指定客户端(client)的访问密匙; 在注册时必须填写(也可由服务端自动生成).
   	// 对于不同的grant_type,该字段都是必须的. 在实际应用中的另一个名称叫appSecret,与client_secret是同一个概念.
   	clientDetails.setClientSecret(passwordEncoder.encode("12345678"));// 这里是加密后的密码

   	// 指定客户端支持的grant_type,可选值包括authorization_code,password,refresh_token,implicit,client_credentials,
   	// 若支持多个grant_type用逗号(,)分隔,如: "authorization_code,password".
   	// 在实际应用中,当注册时,该字段是一般由服务器端指定的,而不是由申请者去选择的,最常用的grant_type组合有:
   	// "authorization_code,refresh_token"(针对通过浏览器访问的客户端);
   	// "password,refresh_token"(针对移动设备的客户端).
   	// implicit与client_credentials在实际中很少使用.
   	clientDetails.setAuthorizedGrantTypes(
   			new HashSet<String>(Arrays.asList(StringUtils.splitByWholeSeparatorPreserveAllTokens(DEFAULT_GRANT_TYPE,
   					","))));

   	// 客户端的重定向URI,可为空, 当grant_type为authorization_code或implicit时,
   	// 在Oauth的流程中会使用并检查与注册时填写的redirect_uri是否一致
   	String[] urls = { DEFAULT_URL };
   	
   	
   	clientDetails.setRegisteredRedirectUri(new HashSet<String>(Arrays.asList(urls)));

   	// 指定客户端所拥有的Spring Security的权限值,可选, 若有多个权限值,用逗号(,)分隔, 如: "ROLE_UNITY,ROLE_USER".
   	// 对于是否要设置该字段的值,要根据不同的grant_type来判断,
   	// 若客户端在Oauth流程中需要用户的用户名(username)与密码(password)的(authorization_code,password),
   	// 则该字段可以不需要设置值,因为服务端将根据用户在服务端所拥有的权限来判断是否有权限访问对应的API.
   	// 但如果客户端在Oauth流程中不需要用户信息的(implicit,client_credentials),
   	// 则该字段必须要设置对应的权限值, 因为服务端将根据该字段值的权限来判断是否有权限访问对应的API.
   	//clientDetails.setAuthorities(AuthorityUtils.createAuthorityList(
   	//		StringUtils.isBlank(client.getAuthorities()) ? DEFAULT_AUTHORITY : client.getAuthorities()));
   	clientDetails.setAuthorities(AuthorityUtils.createAuthorityList("ROLE_USER"));

   	// 指定客户端申请的权限范围,可选值包括read,write,trust;若有多个权限范围用逗号(,)分隔,如: "read,write".
   	// scope的值与security.xml中配置的‹intercept-url的access属性有关系. 如‹intercept-url的配置为
   	// ‹intercept-url pattern="/m/**" access="ROLE_MOBILE,SCOPE_READ"/>
   	// 则说明访问该URL时的客户端必须有read权限范围. write的配置值为SCOPE_WRITE, trust的配置值为SCOPE_TRUST.
   	// 在实际应该中, 该值一般由服务端指定, 常用的值为read,write.
   	clientDetails.setScope(Arrays.asList(StringUtils.splitByWholeSeparatorPreserveAllTokens(DEFAULT_SCOPE,",")));

   	// 设置用户是否自动Approval操作, 默认值为 'false', 可选值包括 'true','false', 'read','write'.
   	// 该字段只适用于grant_type="authorization_code"的情况,当用户登录成功后,若该值为'true'或支持的scope值,则会跳过用户Approve的页面,
   	// 直接授权.
   	// 该字段与 trusted 有类似的功能, 是 spring-security-oauth2 的 2.0 版本后添加的新属性.
   	clientDetails.setAutoApproveScopes(new HashSet<String>(Arrays.asList(DEFAULT_APPROVE_SCOPE)));

   	// 设定客户端的refresh_token的有效时间值(单位:秒),可选, 若不设定值则使用默认的有效时间值(60 * 60 * 24 * 30, 30天).
   	// 若客户端的grant_type不包括refresh_token,则不用关心该字段 在项目中,
   	// 可具体参考DefaultTokenServices.java中属性refreshTokenValiditySeconds.

   	// 在实际应用中, 该值一般是由服务端处理的, 不需要客户端自定义.
   	clientDetails.setRefreshTokenValiditySeconds(TOKEN_VALID_TIME);

   	// 设定客户端的access_token的有效时间值(单位:秒),可选, 若不设定值则使用默认的有效时间值(60 * 60 * 12, 12小时).
   	// 在服务端获取的access_token JSON数据中的expires_in字段的值即为当前access_token的有效时间值.
   	// 在项目中, 可具体参考DefaultTokenServices.java中属性accessTokenValiditySeconds
   	clientDetails.setAccessTokenValiditySeconds(TOKEN_REDRESH_TIME);

   	// @formatter:on
   	return clientDetails;
   }


}

完成前面的步骤之后,一个简单的 oauth2 认证服务器就搭建完成了。

# 二 四种授权方式

# 密码模式

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不支持此功能

Last Updated: 12/24/2019, 9:20:28 PM