>  如果不理解oauth协议的推荐阅读 阮一峰的[理解OAuth 2.0](http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html)

当然，我们也要简单介绍下oauth的运行流程：

```
     +--------+                               +---------------+
     |        |--(A)- Authorization Request ->|   Resource    |
     |        |                               |     Owner     |
     |        |<-(B)-- Authorization Grant ---|               |
     |        |                               +---------------+
     |        |
     |        |                               +---------------+
     |        |--(C)-- Authorization Grant -->| Authorization |
     | Client |                               |     Server    |
     |        |<-(D)----- Access Token -------|               |
     |        |                               +---------------+
     |        |
     |        |                               +---------------+
     |        |--(E)----- Access Token ------>|    Resource   |
     |        |                               |     Server    |
     |        |<-(F)--- Protected Resource ---|               |
     +--------+                               +---------------+
```

运行流程如下图，摘自RFC 6749。



- （A）用户打开客户端以后，客户端要求用户给予授权。
- （B）用户同意给予客户端授权。
- （C）客户端使用上一步获得的授权，向认证服务器申请令牌。
- （D）认证服务器对客户端进行认证以后，确认无误，同意发放令牌。
- （E）客户端使用令牌，向资源服务器申请获取资源。
- （F）资源服务器确认令牌无误，同意向客户端开放资源。



我们是对内的系统，并不需要那么复杂的流程，所以我们看下oauth的授权模式当中的密码模式：

```
     +----------+
     | Resource |
     |  Owner   |
     |          |
     +----------+
          v
          |    Resource Owner
         (A) Password Credentials
          |
          v
     +---------+                                  +---------------+
     |         |>--(B)---- Resource Owner ------->|               |
     |         |         Password Credentials     | Authorization |
     | Client  |                                  |     Server    |
     |         |<--(C)---- Access Token ---------<|               |
     |         |    (w/ Optional Refresh Token)   |               |
     +---------+                                  +---------------+
```

这里的流程相对就比较简单了：

（A）用户向客户端提供用户名和密码。

（B）客户端将用户名和密码发给认证服务器，向后者请求令牌。

（C）认证服务器确认无误后，向客户端提供访问令牌。



现在将简单的转换下思路：

- `Resource Owner`：资源拥有者，拥有订单，购物车等数据的人，既用户

- `Client`：客户端，浏览器

- `Authorization Server`：认证服务器，也就是服务器咯。



在此A、B、C三个流程就变成了：

（A）用户在浏览器输入用户名和密码。

（B）浏览器将用户名和密码发给服务器，向后者请求令牌（token）。

（C）服务器确认无误后，返回token给用户。



但是根据标准的流程，并没有验证码之类的容身之地。而`spring security oauth2` 给我们提供的只能是标准的流程，所以我们对代码进行一些适配，能够适应我们自己的需求。



## spring的部分源码

我们先来看下`spring security oauth2`的部分源码

首先我们直接进行授权的时候，调用的url大概为：`http://localhost:8080/oauth/token?username=user_1&password=123456&grant_type=password&scope=select&client_id=client_2&client_secret=123456`，那么授权肯定是与该链接相关联的。基于这个猜测，我们去寻找源码吧。



在`idea`中使用全局搜索，搜索 字符串`"/oauth/token"`（带着引号），发现了一个类，似乎与这个请求有关 `ClientCredentialsTokenEndpointFilter`

```java
public class ClientCredentialsTokenEndpointFilter extends AbstractAuthenticationProcessingFilter {

	public ClientCredentialsTokenEndpointFilter() {
		this("/oauth/token");
	}
}
```

```
ClientCredentialsTokenEndpointFilter
	---> AbstractAuthenticationProcessingFilter
		---> GenericFilterBean
			---> Filter
```

发现，这个类是一个 `Filter` 也就是过滤器，通过这个过滤器，过滤请求，那么，我们去看看`doFilter`方法咯，`doFilter` 在 `ClientCredentialsTokenEndpointFilter` 的父类 `AbstractAuthenticationProcessingFilter` 上。

我们看看`AbstractAuthenticationProcessingFilter`：

```java
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
        // 如果不是认证的请求，直接下一个filter
        // 这里是怎么判断是否是下一个请求呢？
        // 答：看看url是不是上面ClientCredentialsTokenEndpointFilter 创建时传过来的url，也就是 /oauth/token
		if (!requiresAuthentication(request, response)) {
			chain.doFilter(request, response);
			return;
		}

		Authentication authResult;
		try {
            // 调用attemptAuthentication 方法，返回一个 Authentication 的实现类，也就是认证信息，这个实现类非常重要！！！ 
			authResult = attemptAuthentication(request, response);
            // 如果找不到，那就没了
			if (authResult == null) {
				return;
			}
		}
		// 调用成功的方法
		successfulAuthentication(request, response, chain, authResult);
	}
```

这里最重要的方法`attemptAuthentication` 生成一个授权信息，能够返回，则证明登录已经成功了，所以真正的登录与这里有关。

我们回到`ClientCredentialsTokenEndpointFilter` 这个实现类里面看看`attemptAuthentication`方法吧

```java
	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException, IOException, ServletException {
		
		// ======精简没啥用的方法========
		
		// 构造一个UsernamePasswordAuthenticationToken 
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId,
				clientSecret);
		// 调用认证方法进行认证
		return this.getAuthenticationManager().authenticate(authRequest);

	}
```

我们通过添加断点可以发现 `this.getAuthenticationManager()` 是一个`ProviderManager` 对象，我们看下

`this.getAuthenticationManager().authenticate()` 里面的 `authenticate`

```java
public class ProviderManager{
    public Authentication authenticate(Authentication authentication)
        throws AuthenticationException {
        
        Authentication result = null;
        
        for (AuthenticationProvider provider : getProviders()) {
            // 在一堆的provider中寻找到一个合适的授权提供者
			if (!provider.supports(toTest)) {
				continue;
			}
			// 由授权提供者进行授权
			result = provider.authenticate(authentication);
        }
        if (result != null) {
			return result;
		}
    }
}
```

一路追踪到这里，我们发现，实际上，是通过`provider.supports(toTest)` 寻找一个合适的授权提供者，使用`provider.authenticate(authentication)`就行授权，而`supports` 的依据是通过之前生成的token来判断是否支持：

```java
	public boolean supports(Class<?> authentication) {
		return (UsernamePasswordAuthenticationToken.class
				.isAssignableFrom(authentication));
	}
```



我们整理下这几个流程

```
ClientCredentialsTokenEndpointFilter.doFilter() 
	--> AbstractAuthenticationProcessingFilter.attemptAuthentication()
		--> ProviderManager.authenticate()
			--> AuthenticationProvider.supports()
			--> AuthenticationProvider.authenticate()
```



我们可以看到这里主要就是干了几件事情

- 通过filter 确定登录要过滤的url
- 通过filter 确定生成的`AbstractAuthenticationToken` 比如 `UsernamePasswordAuthenticationToken`
- 通过生成的`AbstractAuthenticationToken` 确定`AuthenticationProvider`
- 通过`AuthenticationProvider` 最后调用 `authenticate()`方法最后进行授权



最后通过`RequestMapping` 返回

```java
@FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint{
    @RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
	public ServerResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
	Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {


		String clientId = getClientId(principal);
		ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);

		TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);

		OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);

		return getResponse(token);
	}
}
```

