Oauth2--- 授权码模式(authorization_code)过程

一、获取授权码Code:

访问授权服务器 /oauth/authorize 端点:(只用于"implicit", "authorization_code")

GET: http://127.0.0.1:8080/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com

参数:

(1)response_type=code  必须指定。

  (2) client_id=client  客户端必须指定。

(3)redirect_uri=http:// 重定向地址 必须与数据表一致


结果:如果同意授权: 返回code=cown42 并跳转到redirect_uri=http://www.baidu.com

过程:比较client_id、及redirect_uri、scope、

涉及到核心类:

扫描二维码关注公众号,回复: 10388827 查看本文章

(1)DefaultRedirectResolver:redirect_uri地址校验。

(2)JdbcAuthorizationCodeServices/InMemoryAuthorizationCodeServices: 生成的code,并保存(code,OAuth2Authentication)在内存或数据库(oauth_code表)

(3)JdbcClientDetailsService: 从表oauth_client_details读取ClientDetails信息,用来校验。

(4)LoginUrlAuthenticationEntryPoint:末登录时,异常由ExceptionTranslationFilter doFilter()---》LoginUrlAuthenticationEntryPoint--》重定向到loginFromUrl页面。

public class AuthorizationEndpoint extends AbstractEndpoint {

//一、相关操作:
	private AuthorizationCodeServices authorizationCodeServices = new InMemoryAuthorizationCodeServices();
	private RedirectResolver redirectResolver = new DefaultRedirectResolver();
	private UserApprovalHandler userApprovalHandler = new DefaultUserApprovalHandler();
	private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();
	private OAuth2RequestValidator oauth2RequestValidator = new DefaultOAuth2RequestValidator();
   //授权确认页面
	private String userApprovalPage = "forward:/oauth/confirm_access";
	private String errorPage = "forward:/oauth/error";

//二、接口:
@RequestMapping(value = "/oauth/authorize")
	public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,SessionStatus sessionStatus, Principal principal) {
 /*1. 通过DefaultOAuth2RequestFactory.createAuthorizationRequest()
    AuthorizationRequest request = new AuthorizationRequest();
从client数据库表加载clietDetails信息
   ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);		
	request.setResourceIdsAndAuthoritiesFromClientDetails(clientDetails);
*/		
AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);

		Set<String> responseTypes = authorizationRequest.getResponseTypes();
        //2.反应类型必须是code
		if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
			throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
		}
        //3.必须指定clientId
		if (authorizationRequest.getClientId() == null) {
			throw new InvalidClientException("A client id must be provided");
		}

		try {
            //4.访问该端点必须认证isAuthenticated = ture,抛出异常,由ExceptionTranslationFilter doFilter()---》LoginUrlAuthenticationEntryPoint--》重定向到loginFromUrl页面。
			if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {
				throw new InsufficientAuthenticationException(
						"User must be authenticated with Spring Security before authorization can be completed.");
			}
            //5.从数据库加载clientDetails
			ClientDetails client = getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());

			//6. 必须指定redirect_uri 而且与数据库里的设置一至。同时认证类型只能:"implicit", "authorization_code"
			String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
			String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client);
			if (!StringUtils.hasText(resolvedRedirect)) {
				throw new RedirectMismatchException(
						"A redirectUri must be either supplied or preconfigured in the ClientDetails");
			}
			authorizationRequest.setRedirectUri(resolvedRedirect);

			// 7。访问范畴Scope比对
			oauth2RequestValidator.validateScope(authorizationRequest, client);

			// 8.是否受权
			authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,
					(Authentication) principal);
			// TODO: is this call necessary?
			boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
			authorizationRequest.setApproved(approved);

			// 10.授权通过后,直接返回数据。
            // (1) Code授权:生成code,同时保存在InMemoryAuthorizationCodeServices(默认)或JdbcAuthorizationCodeServices(表oauth_code)
			if (authorizationRequest.isApproved()) {
				if (responseTypes.contains("token")) {
					return getImplicitGrantResponse(authorizationRequest);
				}
				if (responseTypes.contains("code")) {
					return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,
							(Authentication) principal));
				}
			}

			//11.弹窗口,让用户授权
			model.put("authorizationRequest", authorizationRequest);

			return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);

		}
		catch (RuntimeException e) {
			sessionStatus.setComplete();
			throw e;
		}

	}

二、 获取访问令牌: 访问授权服务器 /oauth/token 端点

确保用户是已经登陆的情况,返回access_token格式.

Post:
http://localhost:8080/oauth/token?
grant_type=authorization_code&code=o4YrCS&client_id=pair &client_secret=secret&redirect_uri=http://baidu.com

参数:

(1) clientId: 客户端id,参数指定同时Principal也必须一致。否则 InvalidClientException(必须)

(2)client_secret:客户端密匙

(2)grant_type:授权类型。(必须)

(3)code: 授权码(必须)

核心类:

(1)AuthorizationServerTokenServices/DefaultTokenServices:由这类产生、刷新、获取token.

  (2) TokenStore:由用户自己提供实现代码。

(3)类包装关系: AbstractEndpoint----》AuthorizationServerTokenServices(DefaultTokenServices)---》TokenStore

@FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint {

	private OAuth2RequestValidator oAuth2RequestValidator = new DefaultOAuth2RequestValidator();

    /**
    * 获取access_token
    **/
	@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
	public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
	Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
        //1.必须是认证通过的用户,否则会抛出异常,异常由ExceptionTranslationFilter doFilter()---》LoginUrlAuthenticationEntryPoint--》重定向到loginFromUrl页面。
		if (!(principal instanceof Authentication)) {
			throw new InsufficientAuthenticationException(
					"There is no client authentication. Try adding an appropriate authentication filter.");
		}
        //2.读取数据表加载ClientDetails信息。
		String clientId = getClientId(principal);
		ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);

		TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
        //2.检查clientId是否正确
		if (clientId != null && !clientId.equals("")) {		
			if (!clientId.equals(tokenRequest.getClientId())) {
				throw new InvalidClientException("Given client ID does not match authenticated client");
			}
		}
        //3.检查Scope
		if (authenticatedClient != null) {
			oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
		}
        //4.检查GrantType: 且不能为implicit
		if (!StringUtils.hasText(tokenRequest.getGrantType())) {
			throw new InvalidRequestException("Missing grant type");
		}
		if (tokenRequest.getGrantType().equals("implicit")) {
			throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
		}
        //5.如果是authorization_code类型,检查AuthCode:AuthCode != null && grant_type = authorization_code
		if (isAuthCodeRequest(parameters)) {
			// The scope was requested or determined during the authorization step
			if (!tokenRequest.getScope().isEmpty()) {
				logger.debug("Clearing scope of incoming token request");
				tokenRequest.setScope(Collections.<String> emptySet());
			}
		}
        // 授权是刷新token: grant_type = refresh_token
		if (isRefreshTokenRequest(parameters)) {
			// A refresh token has its own default scopes, so we should ignore any added by the factory here.
			tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
		}
        //5.生成 token,TokenGranter(用户指定)生成一个OAuth2AccessToken。

		OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
		if (token == null) {
			throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
		}

		return getResponse(token);

	}
public abstract class AbstractTokenGranter implements TokenGranter {
	
    /**
    * 核心类:提供token所有相关操作(最终由DefaultTokenServices实现类实现功能)
    **/
	private final AuthorizationServerTokenServices tokenServices;
	private final ClientDetailsService clientDetailsService;
	private final OAuth2RequestFactory requestFactory;
	private final String grantType;
}
OAuth2AccessToken {
   public static String BEARER_TYPE = "Bearer";
   public static String OAUTH2_TYPE = "OAuth2";
   public static String ACCESS_TOKEN = "access_token";
   public static String TOKEN_TYPE = "token_type";
   public static String EXPIRES_IN = "expires_in";
   public static String REFRESH_TOKEN = "refresh_token";
   public static String SCOPE = "scope";
}
/**
 * 1.TokenStore:核心类,所有token相关的操作由它提供
 * 2.核心功能:(1)提供accessToken 的创建、刷新服务 (2)loadAuthentication()accessToken获取用户信息过程
 * 3.核心类:
 *   (1)AuthorizationServerTokenServices:授权服务器的提供accessToken 的创建、刷新服务。
 *   (2)ResourceServerTokenServices:授权服务器的提供accessToken 获取用户信息,提供给资源服务器去tokenString---> OAuth2Authentication
 */
public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
        ConsumerTokenServices, InitializingBean {

    //Token生成、保存服务。(核心重点)
    private TokenStore tokenStore;
    //ClientDetail 服务
    private ClientDetailsService clientDetailsService;
    //Token信息添加
    private TokenEnhancer accessTokenEnhancer;
    //认证管理:当token刷新时,用于重新认证
    private AuthenticationManager authenticationManager;

    /**
     *  生成 OAuth2AccessToken
     */
    @Transactional
    public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {

        //1. 直接从tokenStore服务获取accessToken
        OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
        OAuth2RefreshToken refreshToken = null;
        //1.tokenStore有accessToken时,检查是否失效。
        if (existingAccessToken != null) {
            if (existingAccessToken.isExpired()) {
                if (existingAccessToken.getRefreshToken() != null) {
                    refreshToken = existingAccessToken.getRefreshToken();
                    tokenStore.removeRefreshToken(refreshToken);
                }
                tokenStore.removeAccessToken(existingAccessToken);
            }
            else {
                tokenStore.storeAccessToken(existingAccessToken, authentication);
                return existingAccessToken;
            }
        }

       //2.生成 accessToken 和 refreshToken 并保存
        refreshToken = createRefreshToken(authentication);
        OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
        tokenStore.storeAccessToken(accessToken, authentication);
        tokenStore.storeRefreshToken(refreshToken, authentication);
        
        return accessToken;

    }

    /**
     * 刷新aceessToken
     */
    @Transactional(noRollbackFor={InvalidTokenException.class, InvalidGrantException.class})
    public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)
            throws AuthenticationException {
        //1. 刷新token
        OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(refreshTokenValue);
        //2.重新认证
        OAuth2Authentication authentication = tokenStore.readAuthenticationForRefreshToken(refreshToken);
        //3.重新生成、保存token
        refreshToken = createRefreshToken(authentication);
        OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
        tokenStore.storeAccessToken(accessToken, authentication);
        tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication);
        
        return accessToken;
    }

    /**
     * 从accessToken 获取到OAuth2Authentication
     */
    public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException,
            InvalidTokenException {
        OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
        OAuth2Authentication result = tokenStore.readAuthentication(accessToken);
        //校验
        if (clientDetailsService != null) {
            String clientId = result.getOAuth2Request().getClientId();
            try {
                clientDetailsService.loadClientByClientId(clientId);
            }
            catch (ClientRegistrationException e) {
                throw new InvalidTokenException("Client not valid: " + clientId, e);
            }
        }
        return result;
    }

    /**
     * 生成RefreshToken就是简单的json字符串
     */
    private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) {
        // OAuth2RefreshToken数据结构: @JsonValue String getValue();
        return new DefaultOAuth2RefreshToken(value);
    }

    /**
     * 生成 OAuth2AccessToken
     */
    private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
        //生成AccessToken
        /**OAuth2AccessToken数据结构
         private String value;
        private Date expiration;
        private String tokenType = BEARER_TYPE.toLowerCase();
        private OAuth2RefreshToken refreshToken;
        private Set<String> scope;
        private Map<String, Object> additionalInformation = Collections.emptyMap();
        */
        
        DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
        token.setRefreshToken(refreshToken);
        token.setScope(authentication.getOAuth2Request().getScope());
        //调用用户指定的accessTokenEnhancer进行数据插入
        return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
    }

    
}

三、访问资源服务器受保护的资源:

根据access_token获取资源, 附上令牌在请求头,**需加上 Bearer **

访问http://localhost:8080/rest/api/ping?access_token=a8ae6a78-289d-4594-a421-9b56aa8f7213

(1)PostMan工具: GET/POST: /xxx/xxxx Header: Authorization Bearer access_token

(2)Curl命令: curl -X GET \ http://localhost:9001/test \ -i -H "Accept: application/json" -H "Authorization: Bearer eyJhbG" \

四、配置AuthorizationServerEndpointsConfigurer:

主要注入想着的ServerBean。

/**
	 * 配置token的保存方式
	 * 1. 密码模式下配置认证管理器 AuthenticationManager
	 * 2. 设置 AccessToken的存储介质tokenStore, 默认使用内存当做存储介质。
	 */
	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

		TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
		endpoints
				.authenticationManager(authenticationManager)
				.userDetailsService(userDetailsService) //MUST:密码模式下需设置一个AuthenticationManager对象,获取 UserDetails信息
				.tokenStore(tokenStore)//token的保存方式
				.tokenEnhancer(tokenEnhancerChain);//token里加点信息
}

//所有Endpoints所用的到服务都在这里注入完成,保存在这个对象
public final class AuthorizationServerEndpointsConfigurer {

	private AuthorizationServerTokenServices tokenServices;

	private ConsumerTokenServices consumerTokenServices;

	private AuthorizationCodeServices authorizationCodeServices;

	private ResourceServerTokenServices resourceTokenServices;

	private TokenStore tokenStore;

	private TokenEnhancer tokenEnhancer;

	private AccessTokenConverter accessTokenConverter;

	private ApprovalStore approvalStore;

	private TokenGranter tokenGranter;

	private OAuth2RequestFactory requestFactory;

	private OAuth2RequestValidator requestValidator;

	private UserApprovalHandler userApprovalHandler;

	private AuthenticationManager authenticationManager;

	private ClientDetailsService clientDetailsService;

	private String prefix;

	private Map<String, String> patternMap = new HashMap<String, String>();

	private Set<HttpMethod> allowedTokenEndpointRequestMethods = new HashSet<HttpMethod>();

	private FrameworkEndpointHandlerMapping frameworkEndpointHandlerMapping;

	private boolean approvalStoreDisabled;

	private List<Object> interceptors = new ArrayList<Object>();

	private DefaultTokenServices defaultTokenServices;

	private UserDetailsService userDetailsService;

	private boolean tokenServicesOverride = false;

	private boolean userDetailsServiceOverride = false;

	private boolean reuseRefreshToken = true;

	private WebResponseExceptionTranslator<OAuth2Exception> exceptionTranslator;

	private RedirectResolver redirectResolver;
}
发布了18 篇原创文章 · 获赞 1 · 访问量 3902

猜你喜欢

转载自blog.csdn.net/silmeweed/article/details/101560289