你是谁,从哪来?能到哪去?
安全框架有两个重要的概念,即认证(Authentication)和授权(Authorization)。认证即确认主体可以访问当前系统的过程; 授权即确定主体通过认证后,检查在当前系统下所拥有的功能权限的过程。 这里的主体既可以是登录系统的用户,也可以是接入的设备或其它系统。
Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.
Spring Security是一个功能强大且高度可定制的身份认证和访问控制框架,是保护基于spring的应用程序的事实上的标准。 Spring Security会根据请求的URI的路径来确定该请求的过滤器链(Filter)以及最终的具体Servlet控制器(Controller)。

从上图可以看到,Spring Security以一个单Filte的(FilterChainProxy)存在于整个过滤链路中。这个FilterChainProxy代理着众多的Spring Security Filter。
OAuth2是一个基于令牌的安全验证和授权框架。 它将安全性分解为以下4个部分:
下面的代码涉及到的spring-security-oauth2的版本:
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>// 校验token
org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter
//从请求中提取token
org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor#extractHeaderToken
一定要记得:Spring Security是基于Web容器的Filter来实现身份认证的。来看下解析token的整体流程:
2.1 从请求中获取token 支持以下三种携带token的方式: 2.1.1 在Header中携带【优先级最高,如果找到,则查找结束】
http://localhost:8080/accounts/me
Request Header:
Authentication:Bearer f732723d-af7f-41bb-bd06-2636ab2be135 //支持此特性的代码实现
request.getHeaders("Authorization")
startsWith(OAuth2AccessToken.BEARER_TYPE.toLowerCase())
2.1.2 拼接在url中作为requestParam
http://localhost:8080/accounts/me?access_token=f732723d-af7f-41bb-bd06-2636ab2be135
// //支持此特性的代码实现,key为access_token的参数
request.getParameter(OAuth2AccessToken.ACCESS_TOKEN);
2.1.3 在form表单中携带http://localhost:8080/accounts/me
form param:
access_token=f732723d-af7f-41bb-bd06-2636ab2be135
// //支持此特性的代码实现,key为access_token的参数
request.getParameter(OAuth2AccessToken.ACCESS_TOKEN);extractToken的完整代码:
//org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor#extractToken
protected String extractToken(HttpServletRequest request) {
// first check the header...
String token = extractHeaderToken(request);
// bearer type allows a request parameter as well
if (token == null) {
logger.debug("Token not found in headers. Trying request parameters.");
token = request.getParameter(OAuth2AccessToken.ACCESS_TOKEN);
if (token == null) {
logger.debug("Token not found in request parameters. Not an OAuth2 request.");
}
else {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, OAuth2AccessToken.BEARER_TYPE);
}
}
return token;
}
/**
* Extract the OAuth bearer token from a header.
*
* @param request The request.
* @return The token, or null if no OAuth authorization header was supplied.
*/
protected String extractHeaderToken(HttpServletRequest request) {
Enumeration<String> headers = request.getHeaders("Authorization");
while (headers.hasMoreElements()) { // typically there is only one (most servers enforce that)
String value = headers.nextElement();
//token前的bearer type是不区分大小写的
if ((value.toLowerCase().startsWith(OAuth2AccessToken.BEARER_TYPE.toLowerCase()))) {
//在请求头中传token时 bearer type和access_token之前的空格不限制:0个、多个都可以
String authHeaderValue = value.substring(OAuth2AccessToken.BEARER_TYPE.length()).trim();
// Add this here for the auth details later. Would be better to change the signature of this method.
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE,
value.substring(0, OAuth2AccessToken.BEARER_TYPE.length()).trim());
int commaIndex = authHeaderValue.indexOf(',');
if (commaIndex > 0) {
authHeaderValue = authHeaderValue.substring(0, commaIndex);
}
return authHeaderValue;
}
}
returnnull;
}
2.2 校验token是否合法
2.2.1 校验通过 将token中解析出来的数据放入SecurityContextHolder.getContext(),然后执行Filter链的下一个
// org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter#doFilter
Authentication authResult = authenticationManager.authenticate(authentication);
if (debug) {
logger.debug("Authentication success: " + authResult);
}
eventPublisher.publishAuthenticationSuccess(authResult);
SecurityContextHolder.getContext().setAuthentication(authResult);2.2.2 校验不通过 请求执行结束,返回invalid token
{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}
OAuth2AuthenticationProcessingFilter摘要:
// org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter#doFilter
catch (OAuth2Exception failed) {
SecurityContextHolder.clearContext();
if (debug) {
logger.debug("Authentication request failed: " + failed);
}
eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
// 将failed.getMessage()的封闭为返回的JSON
authenticationEntryPoint.commence(request, response,
new InsufficientAuthenticationException(failed.getMessage(), failed));
return;
}


// org.springframework.security.oauth2.provider.error.DefaultOAuth2ExceptionRenderer#writeWithMessageConverters 将org.springframework.security.oauth2.common.exceptions.InvalidTokenException convert为上述格式
// org.springframework.security.oauth2.common.exceptions.OAuth2ExceptionJackson2Serializer#serialize
@org.codehaus.jackson.map.annotate.JsonSerialize(using = OAuth2ExceptionJackson1Serializer.class)
@org.codehaus.jackson.map.annotate.JsonDeserialize(using = OAuth2ExceptionJackson1Deserializer.class)
@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = OAuth2ExceptionJackson2Serializer.class)
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = OAuth2ExceptionJackson2Deserializer.class)
public class OAuth2Exception extends RuntimeException {
...
//
@Override
public String toString() {
return getSummary();
}
/**
* @return a comma-delimited list of details (key=value pairs)
*/
public String getSummary() {
StringBuilder builder = new StringBuilder();
String delim = "";
String error = this.getOAuth2ErrorCode();
if (error != null) {
builder.append(delim).append("error=\"").append(error).append("\"");
delim = ", ";
}
String errorMessage = this.getMessage();
if (errorMessage != null) {
builder.append(delim).append("error_description=\"").append(errorMessage).append("\"");
delim = ", ";
}
Map<String, String> additionalParams = this.getAdditionalInformation();
if (additionalParams != null) {
for (Map.Entry<String, String> param : additionalParams.entrySet()) {
builder.append(delim).append(param.getKey()).append("=\"").append(param.getValue()).append("\"");
delim = ", ";
}
}
return builder.toString();
}

// org.springframework.security.oauth2.common.exceptions.OAuth2ExceptionJackson2Serializer
@Override
public void serialize(OAuth2Exception value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeStartObject();
jgen.writeStringField("error", value.getOAuth2ErrorCode());
String errorMessage = value.getMessage();
if (errorMessage != null) {
errorMessage = HtmlUtils.htmlEscape(errorMessage);
}
jgen.writeStringField("error_description", errorMessage);
if (value.getAdditionalInformation()!=null) {
for (Entry<String, String> entry : value.getAdditionalInformation().entrySet()) {
String key = entry.getKey();
String add = entry.getValue();
jgen.writeStringField(key, add);
}
}
jgen.writeEndObject();
}小结:
如果传了token,
org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter#doFilter会进行解析。
解析失败时会使用
authenticationEntryPoint.commence(request, response,new InsufficientAuthenticationException(failed.getMessage(), failed));来convert为一个固定的格式:
{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}解析成功时会将token中的信息放入SpringSecurity约定的一个上下文中:
SecurityContextHolder.getContext().setAuthentication(authResult);