# OAuth2-自定义权限处理器 


### 1、需求场景

OAuth2 协议的 `/oauth2/token` 接口定义了两种获取 `access_token` 的 `grant_type`，分别是：
- `authorization_code`：使用用户授权的授权码获取 access_token。
- `password`：使用用户提交的账号、密码来获取 access_token。

你可以重写内置 `grant_type` 处理器，或添加自定义 `grant_type` 处理器，来支持更多的场景。



--- 


### 2、重写 password 认证模式处理器

当我们按照文档搭建的代码直接测试 password 认证模式时，控制台会得到警告：
``` txt
警告信息：当前 password 认证模式，使用默认实现 (SaOAuth2Strategy.instance.doLoginHandle)，仅供开发测试
正式项目请重写 PasswordGrantTypeHandler 处理器 loginByUsernamePassword 方法
```

这是因为为方便测试，框架内部直接将 password 认证请求转发到了 `SaOAuth2Strategy.instance.doLoginHandle` 来处理，
在真正的项目中需要大家重写 password 认证模式处理器：

``` java
/**
 * 自定义 Password Grant_Type 授权模式处理器认证过程
 */
@Component
public class CustomPasswordGrantTypeHandler extends PasswordGrantTypeHandler {

    @Override
    public PasswordAuthResult loginByUsernamePassword(String username, String password) {
        if("sa".equals(username) && "123456".equals(password)) {
            long userId = 10001;
            return new PasswordAuthResult(userId);
        } else {
            throw new SaOAuth2Exception("无效账号密码");
        }
    }

}
```


### 3、添加自定义 grant_type 处理器

假设有以下需求：通过 手机号+验证码 登录，返回 `access_token`。

#### 3.1、新增验证码发送接口

首先在 oauth2-server 端开放一个接口，为指定手机号发送验证码。

``` java
/**
 * 自定义手机登录接口
 */
@RestController
public class PhoneLoginController {

    @RequestMapping("/oauth2/sendPhoneCode")
    public SaResult sendCode(String phone) {
        String code = SaFoxUtil.getRandomNumber(100000, 999999) + "";
        SaManager.getSaTokenDao().set("phone_code:" + phone, code, 60 * 5);
        System.out.println("手机号：" + phone + "，验证码：" + code + "，已发送成功");
        return SaResult.ok("验证码发送成功");
    }

}
```

真实项目肯定是要对接短信服务商的，此处我们仅做模拟代码，将发送的验证码打印在控制台上。


#### 3.2、自定义 grant_type 处理器

在 oauth2-server 新建 `PhoneCodeGrantTypeHandler` 实现 `SaOAuth2GrantTypeHandlerInterface` 接口：

``` java
/**
 * 自定义 phone_code 授权模式处理器 
 */
@Component
public class PhoneCodeGrantTypeHandler implements SaOAuth2GrantTypeHandlerInterface {

    @Override
    public String getHandlerGrantType() {
        return "phone_code";
    }

    @Override
    public AccessTokenModel getAccessToken(SaRequest req, String clientId, List<String> scopes) {

        // 获取前端提交的参数 
        String phone = req.getParamNotNull("phone");
        String code = req.getParamNotNull("code");
        String realCode = SaManager.getSaTokenDao().get("phone_code:" + phone);

        // 1、校验验证码是否正确
        if(!code.equals(realCode)) {
            throw new SaOAuth2Exception("验证码错误");
        }

        // 2、校验通过，删除验证码
        SaManager.getSaTokenDao().delete("phone_code:" + phone);

        // 3、登录
        long userId = 10001; // 模拟 userId，真实项目应该根据手机号从数据库查询

        // 4、构建 ra 对象
        RequestAuthModel ra = new RequestAuthModel();
        ra.clientId = clientId;
        ra.loginId = userId;
        ra.scopes = scopes;

        // 5、生成 Access-Token
        AccessTokenModel at = SaOAuth2Manager.getDataGenerate().generateAccessToken(ra, true, atm -> atm.grantType = "phone_code");
        return at;
    }
}
```

#### 3.3、为应用添加允许的授权类型

在 `SaOAuth2DataLoader` 实现类中，为 client 的允许授权类型增加自定义的 `phone_code` 

``` java
// Sa-Token OAuth2：自定义数据加载器 
@Component
public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader {
	@Override
	public SaClientModel getClientModel(String clientId) {
		if("1001".equals(clientId)) {
			return new SaClientModel()
					.setClientId("1001")  
					.setClientSecret("aaaa-bbbb-cccc-dddd-eeee")  
					.addAllowRedirectUris("*")    // 所有允许授权的 url
					.addContractScopes("openid", "userid", "userinfo")  
					.addAllowGrantTypes( 
							GrantType.authorization_code, 
							GrantType.implicit, 
							GrantType.refresh_token, 
							GrantType.password, 
							GrantType.client_credentials, 
							"phone_code"  // 重要代码：自定义授权模式 手机号验证码登录
					)
			;
		}
		return null;
	}
	// 其它代码 ... 
}
```

完工，开始测试。


### 4、测试步骤

#### 1、先发送验证码 

``` url
http://sa-oauth-server.com:8000/oauth2/sendPhoneCode?phone=13144556677
```

#### 2、请求 token  

注意 `grant_type` 要填写我们自定义的 `phone_code`，code 的具体值可以在后端的控制台上看到 

``` url
http://sa-oauth-server.com:8000/oauth2/token
    ?grant_type=phone_code
    &client_id=1001
    &client_secret=aaaa-bbbb-cccc-dddd-eeee
	&scope=openid
    &phone=13144556677
	&code={value}
```

返回结果参考如下：

``` js
{
    "code": 200,
    "msg": "ok",
    "data": null,
    "token_type": "bearer",
    "access_token": "pfxRz6KVacwvKNu4IHmDsCJs33kvvARs2z1lTch7stog8nRt6rfVLowtAZ0E",
    "refresh_token": "qcFD6Wo2qZidofXQtWF5oK5ML6ljHKufQ5SbouBxzGnHhnMjUG4VV0iXZhdE",
    "expires_in": 7199,
    "refresh_expires_in": 2591999,
    "client_id": "1001",
    "scope": "openid",
    "openid": "ded91dc189a437dd1bac2274be167d50"
}
```










