Spring Security Oauth2 学习(客户端)

OAuth

OAuth 2.0 是目前最流行的授权机制,用来授权第三方应用,获取用户数据,关于Oauth2推荐阅读阮一峰的博客

简单总结起来,oauth2 可以分为授权服务器,资源服务器以及Oauth2客户端三部分(其中客户端部分就属于我们常规用来进行登陆的服务,授权服务器跟资源服务器也可以放到一起实现);
本文先介绍spring security Oauth 客户端,从一个github登陆的简单项目减少一下客户端大致的请求过程,后续的文章会介绍子单元服务器跟授权服务器。

一个简单的github登陆项目

项目链接

启动本项目访问localhost:8080/index.html 之后会要求登陆github并授权,授权后就可以看到登陆信息了。

启动项目后可以看到项目中有一些过滤器被加载:

org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@5762658b, 
org.springframework.security.web.context.SecurityContextPersistenceFilter@2be95d31, 
org.springframework.security.web.header.HeaderWriterFilter@788ba63e, 
org.springframework.security.web.csrf.CsrfFilter@536d97f8, 
org.springframework.security.web.authentication.logout.LogoutFilter@2ab26378, 
org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter@54755dd9, 
org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter@7c3e4b1a, 
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4d68b571, 
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@2629d5dc, 
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@2aa7399c, 
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@6c1cfa53, 
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@2596d7f4, 
org.springframework.security.web.session.SessionManagementFilter@68ee3b6d, 
org.springframework.security.web.access.ExceptionTranslationFilter@3289079a, 
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@3d19d85]

比前面文章分析的springsecurity过滤器链多了两个

OAuth2AuthorizationRequestRedirectFilter:根据提供请求重定向到第三方授权页过滤器。
OAuth2LoginAuthenticationFilter:登陆过滤器,处理第三方服务器回调请求

实现 Oauth2 登陆重点也就是在这两个过滤器上。

OAuth2AuthorizationRequestRedirectFilter

OAuth2AuthorizationRequestRedirectFilter 用于处理请求路径为/oauth2/authorization/{registrationId}的请求。

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		try {
			OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);
			if (authorizationRequest != null) {
				// 重定向
				this.sendRedirectForAuthorization(request, response, authorizationRequest);
				return;
			}
		} catch (Exception failed) {
			this.unsuccessfulRedirectForAuthorization(request, response, failed);
			return;
		}

		try {
			filterChain.doFilter(request, response);
		} catch (IOException ex) {
			throw ex;
		} catch (Exception ex) {
			// Check to see if we need to handle ClientAuthorizationRequiredException
			Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
			ClientAuthorizationRequiredException authzEx = (ClientAuthorizationRequiredException) this.throwableAnalyzer
				.getFirstThrowableOfType(ClientAuthorizationRequiredException.class, causeChain);
			if (authzEx != null) {
				try {
					OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request, authzEx.getClientRegistrationId());
					if (authorizationRequest == null) {
						throw authzEx;
					}
					this.sendRedirectForAuthorization(request, response, authorizationRequest);
					this.requestCache.saveRequest(request, response);
				} catch (Exception failed) {
					this.unsuccessfulRedirectForAuthorization(request, response, failed);
				}
				return;
			}

			if (ex instanceof ServletException) {
				throw (ServletException) ex;
			} else if (ex instanceof RuntimeException) {
				throw (RuntimeException) ex;
			} else {
				throw new RuntimeException(ex);
			}
		}
	}

这个过滤器的重点就在于

OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);

这一句,获取重定向请求。详细代码如下:

@Override
	public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
		String registrationId = this.resolveRegistrationId(request);
		String redirectUriAction = getAction(request, "login");
		return resolve(request, registrationId, redirectUriAction);
	}


private String resolveRegistrationId(HttpServletRequest request) {
		if (this.authorizationRequestMatcher.matches(request)) {
			return this.authorizationRequestMatcher
					.matcher(request).getVariables().get(REGISTRATION_ID_URI_VARIABLE_NAME);
		}
		return null;
	}


private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction) {
		if (registrationId == null) {
			return null;
		}

		// 根据 registrationId 获取 ClientRegistration
		ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
		if (clientRegistration == null) {
			throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId);
		}

		Map<String, Object> attributes = new HashMap<>();
		attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());

		OAuth2AuthorizationRequest.Builder builder;
		if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
		// 授权码模式
			builder = OAuth2AuthorizationRequest.authorizationCode();
			Map<String, Object> additionalParameters = new HashMap<>();
			if (!CollectionUtils.isEmpty(clientRegistration.getScopes()) &&
					clientRegistration.getScopes().contains(OidcScopes.OPENID)) {
				// Section 3.1.2.1 Authentication Request - https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
				// scope
				// 		REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
				addNonceParameters(attributes, additionalParameters);
			}
			if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
				addPkceParameters(attributes, additionalParameters);
			}
			builder.additionalParameters(additionalParameters);
		} else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
		// 隐式授权模式
			builder = OAuth2AuthorizationRequest.implicit();
		} else {
			throw new IllegalArgumentException("Invalid Authorization Grant Type ("  +
					clientRegistration.getAuthorizationGrantType().getValue() +
					") for Client Registration with Id: " + clientRegistration.getRegistrationId());
		}

		String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction);

// 构建授权请求
		OAuth2AuthorizationRequest authorizationRequest = builder
				.clientId(clientRegistration.getClientId())
				.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
				.redirectUri(redirectUriStr)
				.scopes(clientRegistration.getScopes())
				.state(this.stateGenerator.generateKey())
				.attributes(attributes)
				.build();

		return authorizationRequest;
	}

简单总结,OAuth2AuthorizationRequestRedirectFilter就是供请求重定向到第三方授权页。

OAuth2LoginAuthenticationFilter

OAuth2LoginAuthenticationFilter默认只处理由第三方重定向的路径为/login/oauth2/code/*形式的请求。过滤器中进行授权结果的解剖,在封装成为Authentication认证对象。


 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
 		// 获取参数
        MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
        if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) {
            OAuth2Error oauth2Error = new OAuth2Error("invalid_request");
            throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
        } else {
            OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository.removeAuthorizationRequest(request, response);
            if (authorizationRequest == null) {
                OAuth2Error oauth2Error = new OAuth2Error("authorization_request_not_found");
                throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
            } else {
                String registrationId = (String)authorizationRequest.getAttribute("registration_id");
                ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
                if (clientRegistration == null) {
                    OAuth2Error oauth2Error = new OAuth2Error("client_registration_not_found", "Client Registration not found with Id: " + registrationId, (String)null);
                    throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
                } else {
                    String redirectUri = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request)).replaceQuery((String)null).build().toUriString();
                    // 构建授权结果
                    OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params, redirectUri);
                    Object authenticationDetails = this.authenticationDetailsSource.buildDetails(request);
                    OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(clientRegistration, new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
                    authenticationRequest.setDetails(authenticationDetails);
                    OAuth2LoginAuthenticationToken authenticationResult = (OAuth2LoginAuthenticationToken)this.getAuthenticationManager().authenticate(authenticationRequest);
                    OAuth2AuthenticationToken oauth2Authentication = new OAuth2AuthenticationToken(authenticationResult.getPrincipal(), authenticationResult.getAuthorities(), authenticationResult.getClientRegistration().getRegistrationId());
                    oauth2Authentication.setDetails(authenticationDetails);
                    OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(authenticationResult.getClientRegistration(), oauth2Authentication.getName(), authenticationResult.getAccessToken(), authenticationResult.getRefreshToken());
                    this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response);
                    return oauth2Authentication;
                }
            }
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值