spring-security-oauth2核心类源码解析

获取authorization_code相关类解析

类UsernamePasswordAuthenticationFilter

(extends AbstractAuthenticationProcessingFilter)

    //主要功能就是获取用户的用户名和密码,并创建UsernamePasswordAuthenticationToken  传递给 ProviderManager 进行身份证认证
    public Authentication attemptAuthentication(HttpServletRequest request,
            HttpServletResponse response) throws AuthenticationException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }

        String username = obtainUsername(request);
        String password = obtainPassword(request);

        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();

        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                username, password);

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);

        //提交给AuthenticationManager 进行身份认证 ---- >  类 AbstractUserDetailsAuthenticationProvider 
        return this.getAuthenticationManager().authenticate(authRequest);
    }
类AuthorizationEndpoint (extends AbstractEndpoint)
//主要判断请求用户是否已经被用户授权,若已授权则返回新的authorization_code , 反之 跳转到用户授权页面
@RequestMapping(value = "/oauth/authorize")
public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,
            SessionStatus sessionStatus, Principal principal) {

        //根据请求参数封装 认证请求对象  ---- > AuthorizationRequest
        // Pull out the authorization request first, using the OAuth2RequestFactory. All further logic should
        // query off of the authorization request instead of referring back to the parameters map. The contents of the
        // parameters map will be stored without change in the AuthorizationRequest object once it is created.
        AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);

        //获取请求中的response_type类型,并检验; 此方法只支持 code 类型和 token类型
        Set<String> responseTypes = authorizationRequest.getResponseTypes();

        if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
            throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
        }

        if (authorizationRequest.getClientId() == null) {
            throw new InvalidClientException("A client id must be provided");
        }

        try {

            if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {
                throw new InsufficientAuthenticationException(
                        "User must be authenticated with Spring Security before authorization can be completed.");
            }
            //获取客户端详情
            ClientDetails client = getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());

            // The resolved redirect URI is either the redirect_uri from the parameters or the one from
            // clientDetails. Either way we need to store it on the AuthorizationRequest.
            String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
            // 如果数据库中配置了Client的redirect client则请求的redirect URL必须与数据库中配置的相匹配OK
            //如果数据库中无配置,则直接返回请求中携带的redirect url
            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);

            //根据ClientDetail 校验请求的scoppe
            // We intentionally only validate the parameters requested by the client (ignoring any data that may have
            // been added to the request by the manager).
            oauth2RequestValidator.validateScope(authorizationRequest, client);

            // 此处检测请求的用户是否已经被授权,或者有配置默认授权的权限; 若已经有accessToke存在或者被配置默认授权的权限则返回含有授权的对象
            //用到userApprovalHandler---- > TokenStoreUserApprovalHandler
            // Some systems may allow for approval decisions to be remembered or approved by default. Check for
            // such logic here, and set the approved flag on the authorization request accordingly.
            authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,(Authentication) principal);
            // TODO: is this call necessary?
            boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
            authorizationRequest.setApproved(approved);

            //若已经授权则直接返回对应的视图,返回的视图中包含新生成的authorization_code(固定长度的随机字符串)值,此处新生成的code会存与库中
            // Validation is all done, so we can check for auto approval...
            if (authorizationRequest.isApproved()) {
                if (responseTypes.contains("token")) {
                    return getImplicitGrantResponse(authorizationRequest);
                }
                if (responseTypes.contains("code")) {
                    return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,
                            (Authentication) principal));
                }
            }

            // Place auth request into the model so that it is stored in the session
            // for approveOrDeny to use. That way we make sure that auth request comes from the session,
            // so any auth request parameters passed to approveOrDeny will be ignored and retrieved from the session.
            model.put("authorizationRequest", authorizationRequest);

            //未被授权者跳转到授权界面,让用户选择是否授权
            return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);

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

    }
    //用于处理用户授权页面的结果 ,  用户是否授予第三方权限 ; 请求中必须包参数  user_oauth_approval
    @RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL) 
    public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model,
            SessionStatus sessionStatus, Principal principal) {

        if (!(principal instanceof Authentication)) {
            sessionStatus.setComplete();
            throw new InsufficientAuthenticationException(
                    "User must be authenticated with Spring Security before authorizing an access token.");
        }

        //获取当前session中存放的 authorizationRequest 
        AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest");

        if (authorizationRequest == null) {
            sessionStatus.setComplete();
            throw new InvalidRequestException("Cannot approve uninitialized authorization request.");
        }

        try {
            Set<String> responseTypes = authorizationRequest.getResponseTypes();

            authorizationRequest.setApprovalParameters(approvalParameters);
            //根据用户是否授权更新 authorizationRequest 对象中的 approved 属性; 授予为true
            authorizationRequest = userApprovalHandler.updateAfterApproval(authorizationRequest,(Authentication) principal);

            boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
            authorizationRequest.setApproved(approved);

            if (authorizationRequest.getRedirectUri() == null) {
                sessionStatus.setComplete();
                throw new InvalidRequestException("Cannot approve request when no redirect URI is provided.");
            }

            if (!authorizationRequest.isApproved()) {
                return new RedirectView(getUnsuccessfulRedirect(authorizationRequest,
                        new UserDeniedAuthorizationException("User denied access"), responseTypes.contains("token")),
                        false, true, false);
            }

            if (responseTypes.contains("token")) {
                return getImplicitGrantResponse(authorizationRequest).getView();
            }
            //生成Authorization_code 并储存,返回给客户端
            return getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal);
        }
        finally {
            sessionStatus.setComplete();
        }

    }

获取AccessToken请求相关类解析

类 ClientCredentialsTokenEndpointFilter (extends AbstractAuthenticationProcessingFilter)
此Filter是在用户已经进行过身份认证,并且已经通过的条件下
    //此方法主要作用为:提取客户端的Client_id 和 Client_secret 传递给你AuthenticationManager进行认证
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {

        //验证请求的方法是否支持 ,  POST 、 GET  此处只支持POST方法
        if (allowOnlyPost && !"POST".equalsIgnoreCase(request.getMethod())) {
            throw new HttpRequestMethodNotSupportedException(request.getMethod(), new String[] { "POST" });
        }
        //获取客户端信息
        String clientId = request.getParameter("client_id");
        String clientSecret = request.getParameter("client_secret");

        //如果身份已经认证 直接放行,无需在进行认证
        //(此处有疑问,SecurityContextHolder.getContext() 获取的Authentication一定是当前用户的认证信息吗,如何保证??????????)
        // If the request is already authenticated we can assume that this
        // filter is not needed
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.isAuthenticated()) {
            return authentication;
        }

        if (clientId == null) {
            throw new BadCredentialsException("No client credentials presented");
        }

        if (clientSecret == null) {
            clientSecret = "";
        }

        clientId = clientId.trim();
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId,clientSecret);

        //提交给AuthenticationManager 进行身份认证 ---- > 类 AbstractUserDetailsAuthenticationProvider 
        return this.getAuthenticationManager().authenticate(authRequest);

    }
类 AbstractUserDetailsAuthenticationProvider

(implements AuthenticationProvider,InitializingBean, MessageSourceAware)

    //此方法主要功能为:1、 通过用户名(或客户端ID)获取用户信息(客户端信息);2、账号检验(基本检验、密码检验);3、账号检验通过则创建UsernamePasswordAuthenticationToken对象
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports","Only UsernamePasswordAuthenticationToken is supported"));

        // Determine username
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED": authentication.getName();

        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);

        if (user == null) {
            cacheWasUsed = false;

            try {
                //获取用户的先关信息(此处的用户可能为客户端Client)  需要 userDetailsService 或者 clientDetailsService  ----- > DaoAuthenticationProvider
                user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);
            }
            catch (UsernameNotFoundException notFound) {
                logger.debug("User '" + username + "' not found");

                if (hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.badCredentials",
                            "Bad credentials"));
                }
                else {
                    throw notFound;
                }
            }

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

        try {
            //账号的基本检测,如:是否不可用、是否锁定、是否过期
            preAuthenticationChecks.check(user);
            //检验账号密码是否正确
            additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);
        }
        catch (AuthenticationException exception) {
            if (cacheWasUsed) {
                // There was a problem, so try again after checking
                // we're using latest data (i.e. not from the cache)
                cacheWasUsed = false;
                user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);
                preAuthenticationChecks.check(user);
                additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);
            }
            else {
                throw exception;
            }
        }
        //账号的基本检测,如:是否不可用、是否锁定、是否过期
        postAuthenticationChecks.check(user);

        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }
        Object principalToReturn = user;

        if (forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }
        //此处会根据传入参数创建UsernamePasswordAuthenticationToken对象并返回
        return createSuccessAuthentication(principalToReturn, authentication, user);
    }
类 TokenEndpoint (extends AbstractEndpoint)
    //此方法的主要作用为:1、获取客户端详情并根据请求参数组装TokenRequest;2、校验请求的Scope;3、为客户端是生成Token
    @RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
    public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
    Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {

        if (!(principal instanceof Authentication)) {
            throw new InsufficientAuthenticationException(
                    "There is no client authentication. Try adding an appropriate authentication filter.");
        }

        //通过clientId 获取客户端详情
        String clientId = getClientId(principal);
        ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);

        //获取请求的相关参数,如grant_type,client_id,scope(此处会依据用户的权限进行过滤)等, 封装组建TokenRequest 
        TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);

        if (clientId != null && !clientId.equals("")) {
            // Only validate the client details if a client authenticated during this
            // request.
            if (!clientId.equals(tokenRequest.getClientId())) {
                // double check to make sure that the client ID in the token request is the same as that in the
                // authenticated client
                throw new InvalidClientException("Given client ID does not match authenticated client");
            }
        }
        //对客户端传入的Scope进行校验 , Scope超限将直接抛出异常
        if (authenticatedClient != null) {
            oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
        }
        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");
        }
        // grant_type=authorzation_code 时清空scope
        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());
            }
        }
        //grant_type=refresh_token 时 需要设置scope ,因为它有自己的scope
        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)));
        }

        //为客户端生成token (此处会验证客户端的grant_type,只有检验通过才会生成Token)  ------- >类 DefaultTokenServices
        OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
        if (token == null) {
            throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
        }
        //封装返回
        return getResponse(token);

    }
类 DefaultTokenServices

(Implements AuthorizationServerTokenServices, ResourceServerTokenServices, ConsumerTokenServices, InitializingBean)

    //生成accessToken和RefreshToken
    @Transactional
    public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {

        //首先尝试获取当前存在的Token
        OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
        OAuth2RefreshToken refreshToken = null;
        //如果accesToken已存在并且没有失效,则重新保存并返回;如果accessToken失效,则提取refrehToken并清除老的AccessToken;
        //如果accessToken为null,则直接生成新的Token
        if (existingAccessToken != null) {
            if (existingAccessToken.isExpired()) {
                if (existingAccessToken.getRefreshToken() != null) {
                    refreshToken = existingAccessToken.getRefreshToken();
                    // The token store could remove the refresh token when the
                    // access token is removed, but we want to
                    // be sure...
                    tokenStore.removeRefreshToken(refreshToken);
                }
                tokenStore.removeAccessToken(existingAccessToken);
            }
            else {
                // Re-store the access token in case the authentication has changed
                tokenStore.storeAccessToken(existingAccessToken, authentication);
                return existingAccessToken;
            }
        }
        //老的refreshToken 不失效将会复用,失效的话生成新的RefresshToken
        // Only create a new refresh token if there wasn't an existing one
        // associated with an expired access token.
        // Clients might be holding existing refresh tokens, so we re-use it in
        // the case that the old access token
        // expired.
        if (refreshToken == null) {
            refreshToken = createRefreshToken(authentication);
        }
        // But the refresh token itself might need to be re-issued if it has
        // expired.
        else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
            ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
            if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
                refreshToken = createRefreshToken(authentication);
            }
        }
        //生成新的accessToken,并储存 , 保存refreshToken
        OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
        tokenStore.storeAccessToken(accessToken, authentication);
        // In case it was modified
        refreshToken = accessToken.getRefreshToken();
        if (refreshToken != null) {
            tokenStore.storeRefreshToken(refreshToken, authentication);
        }
        return accessToken;

    }

资源访问相关类

OAuth2AuthenticationProcessingFilter类
    //主要功能就是获取请求中携带的Token,然后通过Token提取Authentication,然后将Authentication放入上下文 ;  获取Authentication成功将会允许访问资源
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
            ServletException {

        final boolean debug = logger.isDebugEnabled();
        final HttpServletRequest request = (HttpServletRequest) req;
        final HttpServletResponse response = (HttpServletResponse) res;

        try {
            //提取请求携带的Token组建一个认证Authentication ,Token提取过程:从Hander和url中获取携带的Token
            Authentication authentication = tokenExtractor.extract(request);

            if (authentication == null) {
                if (stateless && isAuthenticated()) {
                    if (debug) {
                        logger.debug("Clearing security context.");
                    }
                    SecurityContextHolder.clearContext();
                }
                if (debug) {
                    logger.debug("No token in request, will continue chain.");
                }
            }
            else {
                request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
                if (authentication instanceof AbstractAuthenticationToken) {
                    AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
                    needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
                }
                //获取Token携带的认证信息 , Oauth2AuthenticationMananger主要做三件是1、通过token获取用户的Oauth2Authentcation对象(TokenServices);2、验证访问的资源resourceId是否符合范围;3、验证客户端访问的Scope(clientDetailsService)
                Authentication authResult = authenticationManager.authenticate(authentication);

                if (debug) {
                    logger.debug("Authentication success: " + authResult);
                }
                //将当前的Authentication 放入Context中 ,访问后面资源 
                eventPublisher.publishAuthenticationSuccess(authResult);
                SecurityContextHolder.getContext().setAuthentication(authResult);

            }
        }
        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"));

            authenticationEntryPoint.commence(request, response,
                    new InsufficientAuthenticationException(failed.getMessage(), failed));

            return;
        }

        chain.doFilter(request, response);
    }
类OAuth2AuthenticationManager (implementsAuthenticationManager,InitializingBean)

Oauth2AuthenticationMananger主要做三件是:
1. 通过token获取用户的Oauth2Authentcation对象(TokenServices);
2. 验证访问的资源resourceId是否符合范围;
3. 验证客户端访问的Scope(clientDetailsService)

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        if (authentication == null) {
            throw new InvalidTokenException("Invalid token (token not found)");
        }
        String token = (String) authentication.getPrincipal();
        //通过token获取用户的Oauth2Authentcation对象(TokenServices);
        OAuth2Authentication auth = tokenServices.loadAuthentication(token);
        if (auth == null) {
            throw new InvalidTokenException("Invalid token: " + token);
        }

        //验证访问的资源resourceId是否符合范围;
        Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();
        if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {
            throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");
        }
        //验证客户端访问的Scope(clientDetailsService)
        checkClientDetails(auth);

        if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
            OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
            // Preserve the authentication details if any from the one loaded by token services
            details.setDecodedDetails(auth.getDetails());
        }
        auth.setDetails(authentication.getDetails());
        auth.setAuthenticated(true);
        return auth;

    }
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值