spring security oauth2授权码接口源码流程
使用spring security oauth2 的 授权码模式
获取授权码
查看源码:
org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint
@RequestMapping({"/oauth/authorize"})
public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters, SessionStatus sessionStatus, Principal principal) {
AuthorizationRequest authorizationRequest = this.getOAuth2RequestFactory().createAuthorizationRequest(parameters);
Set<String> responseTypes = authorizationRequest.getResponseTypes();
if(!responseTypes.contains("token") && !responseTypes.contains("code")) {
throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
} else if(authorizationRequest.getClientId() == null) {
throw new InvalidClientException("A client id must be provided");
} else {
try {
if(principal instanceof Authentication && ((Authentication)principal).isAuthenticated()) {
ClientDetails client = this.getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());
String redirectUriParameter = (String)authorizationRequest.getRequestParameters().get("redirect_uri");
String resolvedRedirect = this.redirectResolver.resolveRedirect(redirectUriParameter, client);
if(!StringUtils.hasText(resolvedRedirect)) {
throw new RedirectMismatchException("A redirectUri must be either supplied or preconfigured in the ClientDetails");
} else {
authorizationRequest.setRedirectUri(resolvedRedirect);
this.oauth2RequestValidator.validateScope(authorizationRequest, client);
authorizationRequest = this.userApprovalHandler.checkForPreApproval(authorizationRequest, (Authentication)principal);
boolean approved = this.userApprovalHandler.isApproved(authorizationRequest, (Authentication)principal);
authorizationRequest.setApproved(approved);
if(authorizationRequest.isApproved()) {
if(responseTypes.contains("token")) {
return this.getImplicitGrantResponse(authorizationRequest);
}
if(responseTypes.contains("code")) {
return new ModelAndView(this.getAuthorizationCodeResponse(authorizationRequest, (Authentication)principal));
}
}
model.put("authorizationRequest", authorizationRequest);
model.put("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", this.unmodifiableMap(authorizationRequest));
return this.getUserApprovalPageResponse(model, authorizationRequest, (Authentication)principal);
}
} else {
throw new InsufficientAuthenticationException("User must be authenticated with Spring Security before authorization can be completed.");
}
} catch (RuntimeException var11) {
sessionStatus.setComplete();
throw var11;
}
}
}
首先可见,response_type参数是必须为code或token(这是别的oauth2模式),client_id是必传的,之后会根据client_id去查询其是否存在,主要是由ClientDetail去操作 ,而clientDetail是我们配置的,在 继承 AuthorizationServerConfigurerAdapter类中重写**configure(AuthorizationServerEndpointsConfigurer endpoints) ** 方法中,可以是jdbc,如下图,也可以使内存,默认是内存,用hashmap装载。
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//MUST:密码模式下需设置一个AuthenticationManager对象,获取 UserDetails信息
//末确认点.userDetailsService(userDetailsService)
endpoints.authenticationManager(authenticationManager);
endpoints.setClientDetailsService(jdbcClientDetailsService());
endpoints.tokenStore(new JdbcTokenStore(dataSource));
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
endpoints.approvalStore(jdbcApprovalStore());
//token增强器,多加点信息在里面
endpoints.tokenEnhancer(tokenEnhancerChain());
endpoints.authorizationCodeServices(jdbcAuthorizationCodeServices());
}
@Bean
public ClientDetailsService jdbcClientDetailsService() {
//从数据库中读取client_id,client_secret
return new JdbcClientDetailsService(dataSource);
}
![](https://img.haomeiwen.com/i23281927/6898884f904f954a.png)
principal 是通过认证了的数据库用户,即spring security的userDetailService中的loadUserDetail方法。
clientDetail就是数据库中oauth_user_dtails表中配置的,里面拿到是access_token有效期,refresh_token有效期,scope、resourceIds等信息,redirectUri必须得配置有,不然会报错,源码有抛异常。
clientDetail类:
/**
* Client details for OAuth 2
*
* @author Ryan Heaton
*/
public interface ClientDetails extends Serializable {
/**
* The client id.
*
* @return The client id.
*/
String getClientId();
/**
* The resources that this client can access. Can be ignored by callers if empty.
*
* @return The resources of this client.
*/
Set<String> getResourceIds();
/**
* Whether a secret is required to authenticate this client.
*
* @return Whether a secret is required to authenticate this client.
*/
boolean isSecretRequired();
/**
* The client secret. Ignored if the {@link #isSecretRequired() secret isn't required}.
*
* @return The client secret.
*/
String getClientSecret();
/**
* Whether this client is limited to a specific scope. If false, the scope of the authentication request will be
* ignored.
*
* @return Whether this client is limited to a specific scope.
*/
boolean isScoped();
/**
* The scope of this client. Empty if the client isn't scoped.
*
* @return The scope of this client.
*/
Set<String> getScope();
/**
* The grant types for which this client is authorized.
*
* @return The grant types for which this client is authorized.
*/
Set<String> getAuthorizedGrantTypes();
/**
* The pre-defined redirect URI for this client to use during the "authorization_code" access grant. See OAuth spec,
* section 4.1.1.
*
* @return The pre-defined redirect URI for this client.
*/
Set<String> getRegisteredRedirectUri();
/**
* Returns the authorities that are granted to the OAuth client. Cannot return <code>null</code>.
* Note that these are NOT the authorities that are granted to the user with an authorized access token.
* Instead, these authorities are inherent to the client itself.
*
* @return the authorities (never <code>null</code>)
*/
Collection<GrantedAuthority> getAuthorities();
/**
* The access token validity period for this client. Null if not set explicitly (implementations might use that fact
* to provide a default value for instance).
*
* @return the access token validity period
*/
Integer getAccessTokenValiditySeconds();
/**
* The refresh token validity period for this client. Null for default value set by token service, and
* zero or negative for non-expiring tokens.
*
* @return the refresh token validity period
*/
Integer getRefreshTokenValiditySeconds();
/**
* Test whether client needs user approval for a particular scope.
*
* @param scope the scope to consider
* @return true if this client does not need user approval
*/
boolean isAutoApprove(String scope);
/**
* Additional information for this client, not needed by the vanilla OAuth protocol but might be useful, for example,
* for storing descriptive information.
*
* @return a map of additional information
*/
Map<String, Object> getAdditionalInformation();
}
//取得数据sql
private static final String CLIENT_FIELDS_FOR_UPDATE = "resource_ids, scope, "
+ "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
+ "refresh_token_validity, additional_information, autoapprove";
private static final String CLIENT_FIELDS = "client_secret, " + CLIENT_FIELDS_FOR_UPDATE;
![](https://img.haomeiwen.com/i23281927/78e10d6012653a51.png)
之后就是验证redirectUri 是否为空,scope是否和传过来的对得上,否的话都会抛异常!
![](https://img.haomeiwen.com/i23281927/61778b8c0ac3bb43.png)
approve是否自动略过自带的批准页面,默认否,即认证成功后,会跳到一个页面,选择approve或者deny,approve后才进入redirectUri的地址。这里如果自动,这根据oauth2请求模式分别操作。
授权码的话就成功跳转到redirectUri,并附带上code,如果前面有传state,还带上state,返回的queryParam就这两个可见源码。
org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint 类中的
private String getSuccessfulRedirect(AuthorizationRequest authorizationRequest, String authorizationCode) {
if (authorizationCode == null) {
throw new IllegalStateException("No authorization code found in the current request scope.");
}
Map<String, String> query = new LinkedHashMap<String, String>();
query.put("code", authorizationCode);
String state = authorizationRequest.getState();
if (state != null) {
query.put("state", state);
}
return append(authorizationRequest.getRedirectUri(), query, false);
}
![](https://img.haomeiwen.com/i23281927/40051c021e34130f.png)