今天是:
带着程序的旅程,每一行代码都是你前进的一步,每个错误都是你成长的机会,最终,你将抵达你的目的地。
title

spring-ouauth2-原理

 一.Oauth2授权流程

1.oauth2

2.OIDC

 

2.源码解析(springboot-3.1.1,spring-security-oauth2-authorization-server 1.1.1)

1.访问客户端首页   http://localhost:7999/index

OAuth2AuthorizationRequestRedirectFilter

 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 var11) {
            this.unsuccessfulRedirectForAuthorization(request, response, var11);
            return;
        }

        try {
            filterChain.doFilter(request, response);

//调用解析器解析请求
    public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
        String registrationId = this.resolveRegistrationId(request);
        if (registrationId == null) {
            return null;
        } else {
            String redirectUriAction = this.getAction(request, "login");
            return this.resolve(request, registrationId, redirectUriAction);
        }
    }

//由于registrationId ==null 返回空 ,继续执行后面的filter AuthorizationFilter

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        if (this.observeOncePerRequest && this.isApplied(request)) {
            chain.doFilter(request, response);
        } else if (this.skipDispatch(request)) {
            chain.doFilter(request, response);
        } else {
            String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
                AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request);
                this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, decision);
                if (decision != null && !decision.isGranted()) {
                    throw new AccessDeniedException("Access Denied");
                }

                chain.doFilter(request, response);
            } finally {
                request.removeAttribute(alreadyFilteredAttributeName);
            }

        }

由于上面还没有授权访问 会抛出 throw new AccessDeniedException("Access Denied");,此异常被ExceptionTranslationFilter捕获

 

2.重定向到http://localhost:7999/oauth2/authorization/messaging-client-oidc

    }
//检测是否认证,如果没有认证则抛出异常,被上一个过滤器ExceptionTranslationFilter捕获

    private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException {
        if (exception instanceof AuthenticationException) {
            this.handleAuthenticationException(request, response, chain, (AuthenticationException)exception);
        } else if (exception instanceof AccessDeniedException) {
            this.handleAccessDeniedException(request, response, chain, (AccessDeniedException)exception);
        }

    }


    protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException {
        SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
        this.securityContextHolderStrategy.setContext(context);
        this.requestCache.saveRequest(request, response);
        this.authenticationEntryPoint.commence(request, response, reason);
    }

//然后冲认证进入点获取到配置的登录切入点重定向 即在客户端的securityFilterChain中配置的loginpage
    .oauth2Login(oauth2Login ->
                        oauth2Login.loginPage("/oauth2/authorization/messaging-client-oidc"))

3. 重定向到授权服务器

http://localhost:8500/oauth2/authorize?response_type=code&client_id=messaging-client&scope=openid%20profile%20message.read%20message.write&state=zB8C9OngKhZd9bLgq_ndSfY3B7t-HvBsLoorxPrZIJQ%3D&redirect_uri=http://127.0.0.1:7999/login/oauth2/code/messaging-client-oidc&nonce=Df3hSGqMNPMeJ2opt4LMfNqgssZNpU4qxhVLWtiRGJE

 

//DefaultOAuth2AuthorizationRequestResolver,首先解析请求是不是OAuth2AuthorizationRequest ,解析过程中会生成state和nonce参数,然后构建重定向参数

    private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction) {
        if (registrationId == null) {
            return null;
        } else {
            ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
            if (clientRegistration == null) {
                throw new InvalidClientRegistrationIdException("Invalid Client Registration with Id: " + registrationId);
            } else {
                OAuth2AuthorizationRequest.Builder builder = this.getBuilder(clientRegistration);
                String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction);
                builder.clientId(clientRegistration.getClientId()).authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri()).redirectUri(redirectUriStr).scopes(clientRegistration.getScopes()).state(DEFAULT_STATE_GENERATOR.generateKey());
                this.authorizationRequestCustomizer.accept(builder);
                return builder.build();
            }
        }
    }

 

 

 

4.认证服务武器8500  ,由OAuth2AuthorizationEndpointFilter处理,由于此时还是匿名认证,则放行到后面的filter ,然后和上面重定向逻辑一样 会重定向到登录页面http://localhost:8500/login

   protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (!this.authorizationEndpointMatcher.matches(request)) {
            filterChain.doFilter(request, response);
        } else {
            try {
                Authentication authentication = this.authenticationConverter.convert(request);
                if (authentication instanceof AbstractAuthenticationToken) {
                    ((AbstractAuthenticationToken)authentication).setDetails(this.authenticationDetailsSource.buildDetails(request));
                }

                Authentication authenticationResult = this.authenticationManager.authenticate(authentication);
                if (!authenticationResult.isAuthenticated()) {//没有认证,放行
                    filterChain.doFilter(request, response);
                    return;
                }

                if (authenticationResult instanceof OAuth2AuthorizationConsentAuthenticationToken) {
                    if (this.logger.isTraceEnabled()) {
                        this.logger.trace("Authorization consent is required");
                    }

                    this.sendAuthorizationConsent(request, response, (OAuth2AuthorizationCodeRequestAuthenticationToken)authentication, (OAuth2AuthorizationConsentAuthenticationToken)authenticationResult);
                    return;
                }

                this.sessionAuthenticationStrategy.onAuthentication(authenticationResult, request, response);
                this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authenticationResult);
            } catch (OAuth2AuthenticationException var6) {
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace(LogMessage.format("Authorization request failed: %s", var6.getError()), var6);
                }

                this.authenticationFailureHandler.onAuthenticationFailure(request, response, var6);
            }

        }
    }

 

5.输入用户名和密码 点击登录  ,此时AbstractAuthenticationProcessingFilter 判断是否需要认证,如果需要认证则调用子类的attemptAuthentication方法

 //UsernamePasswordAuthenticationFilter 拿到用户名和密码

   public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String username = this.obtainUsername(request);
            username = username != null ? username.trim() : "";
            String password = this.obtainPassword(request);
            password = password != null ? password : "";
            UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

然后寻找合适的认证提供者进行认证,这里会找到DaoAuthenticationProvider,然后执行父类AbstractUserDetailsAuthenticationProvider的authenticate方法

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
            return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
        });
        String username = this.determineUsername(authentication);
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("Failed to find user '" + username + "'");
                if (!this.hideUserNotFoundExceptions) {
                    throw var6;
                }

                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

        try {
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
            if (!cacheWasUsed) {
                throw var7;
            }

            cacheWasUsed = false;
            user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        }

        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }

        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }

先从换从从查找,如果没有,则通过配置的userDetailsService通过loadUserByUsername加载,认证成功后执行认证成功策略AbstractAuthenticationProcessingFilter 

    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
        context.setAuthentication(authResult);
        this.securityContextHolderStrategy.setContext(context);
        this.securityContextRepository.saveContext(context, request, response);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
        }

        this.rememberMeServices.loginSuccess(request, response, authResult);
        if (this.eventPublisher != null) {
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }

        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }

SavedRequestAwareAuthenticationSuccessHandler ,冲requestCache中获取之前开始认证时候存入的请求 [nio-8500-exec-1] o.s.s.w.s.HttpSessionRequestCache        : Saved request http://localhost:8500/oauth2/authorize?response_type=code&client_id=messaging-client&scope=openid%20profile%20message.read%20message.write&state=D--ICQCGOIuMY3zdsnlzN7330ETAQflC4ad2-wsMBcA%3D&redirect_uri=http://127.0.0.1:7999/login/oauth2/code/messaging-client-oidc&nonce=oh900P9yudr1v4UBUDw2BV8GtWV-XTq1AWKrdYmRfog&continue to session

    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        SavedRequest savedRequest = this.requestCache.getRequest(request, response);
        if (savedRequest == null) {
            super.onAuthenticationSuccess(request, response, authentication);
        } else {
            String targetUrlParameter = this.getTargetUrlParameter();
            if (!this.isAlwaysUseDefaultTargetUrl() && (targetUrlParameter == null || !StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
                this.clearAuthenticationAttributes(request);
                String targetUrl = savedRequest.getRedirectUrl();
                this.getRedirectStrategy().sendRedirect(request, response, targetUrl);
            } else {
                this.requestCache.removeRequest(request, response);
                super.onAuthenticationSuccess(request, response, authentication);
            }
        }
    }

之后开始成功重定向  AbstractAuthenticationTargetUrlRequestHandler

    protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        String targetUrl = this.determineTargetUrl(request, response, authentication);
        if (response.isCommitted()) {
            this.logger.debug(LogMessage.format("Did not redirect to %s since response already committed.", targetUrl));
        } else {
            this.redirectStrategy.sendRedirect(request, response, targetUrl);
        }
    }

6.  再次执行/oauth2/authorize请求, 然后被OAuth2AuthorizationEndpointFilter处理->>  OAuth2AuthorizationCodeRequestAuthenticationConverter 转换请求 将请去转为OAuth2AuthorizationCodeRequestAuthenticationToken ,之后被OAuth2AuthorizationCodeRequestAuthenticationProvider认证 ,在该类的方法中生成授权码,认证成功再次执行重定向 并且携带授权码  http://127.0.0.1:7999/login/oauth2/code/messaging-client-oidc?code=2V2TwP37MefzAkw_U5W1FuZ0Hn79uS9Vfu23wpdYmBITVtxELEmcpnRdCWSbU9ODgymRbURmLElDhb6sgSqgs3zFPl4vlyXuHU8NUTQliYQwPwWJnJLNN2yYITDuQjMJ&state=JgWcgJZwNPaODcBXDFtnF8EYHEn0tX2TwgPrXGm9F6g%3D

被OAuth2LoginAuthenticationFilter处理 ,会抛出异常

    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 {

 

====为什么会再走一遍

重定向到授权服务

http://localhost:8500/oauth2/authorize?response_type=code&client_id=messaging-client&scope=openid%20profile%20message.read%20message.write&state=x2wgRAaH1iUghjaKc1w5s9LIsjUhUyqexMRUFTrSJP0%3D&redirect_uri=http://127.0.0.1:7999/login/oauth2/code/messaging-client-oidc&nonce=mUMNu7MQWDGsAQtT-XD4nJJvgNm3Edk4FA-dwtQbD0s

重定向??

http://127.0.0.1:7999/login/oauth2/code/messaging-client-oidc?code=3X5rgRXvtviBc4T0AUuNjQfG7dMbPMH0fnNVmBt-BybRM2cO1EqeIvmNXXUwpaHB1SscyaNOUsjmufzTC34SziXgdme6oAxL9FtmIEGSHicN8SL_ePi2CJGQ_MVTNIPk&state=x2wgRAaH1iUghjaKc1w5s9LIsjUhUyqexMRUFTrSJP0%3D

有了授权码,就可以换取访问token, OidcAuthorizationCodeAuthenticationProvider  ,请求认证服务/oauth2/token 节点,被

OAuth2TokenEndpointFilter处理,token 由OAuth2AuthorizationCodeAuthenticationProvider 生成 ,同时包含access_token ,refresh_token ,id_token .同时会加载OidcUser,默认节点为/userinfo

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        OAuth2LoginAuthenticationToken authorizationCodeAuthentication = (OAuth2LoginAuthenticationToken)authentication;
        if (!authorizationCodeAuthentication.getAuthorizationExchange().getAuthorizationRequest().getScopes().contains("openid")) {
            return null;
        } else {
            OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication.getAuthorizationExchange().getAuthorizationRequest();
            OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication.getAuthorizationExchange().getAuthorizationResponse();
            if (authorizationResponse.statusError()) {
                throw new OAuth2AuthenticationException(authorizationResponse.getError(), authorizationResponse.getError().toString());
            } else if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
                OAuth2Error oauth2Error = new OAuth2Error("invalid_state_parameter");
                throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
            } else {
                OAuth2AccessTokenResponse accessTokenResponse = this.getResponse(authorizationCodeAuthentication);
                ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration();
                Map<String, Object> additionalParameters = accessTokenResponse.getAdditionalParameters();
                if (!additionalParameters.containsKey("id_token")) {
                    OAuth2Error invalidIdTokenError = new OAuth2Error("invalid_id_token", "Missing (required) ID Token in Token Response for Client Registration: " + clientRegistration.getRegistrationId(), (String)null);
                    throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString());
                } else {
                    OidcIdToken idToken = this.createOidcToken(clientRegistration, accessTokenResponse);
                    this.validateNonce(authorizationRequest, idToken);
                    OidcUser oidcUser = (OidcUser)this.userService.loadUser(new OidcUserRequest(clientRegistration, accessTokenResponse.getAccessToken(), idToken, additionalParameters));
                    Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper.mapAuthorities(oidcUser.getAuthorities());
                    OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(authorizationCodeAuthentication.getClientRegistration(), authorizationCodeAuthentication.getAuthorizationExchange(), oidcUser, mappedAuthorities, accessTokenResponse.getAccessToken(), accessTokenResponse.getRefreshToken());
                    authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
                    return authenticationResult;
                }
            }
        }
    }

 

分享到:

专栏

类型标签

网站访问总量