美文网首页spring security 以及 oauth2
spring security oauth2通过授权码获取acc

spring security oauth2通过授权码获取acc

作者: virtual灬zzZ | 来源:发表于2021-09-30 01:03 被阅读0次

    获取access_token

    路径为 :http://ip:port/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&scope=SCOPE&redirect_uri=REDIRECT_URI&grant_type=authorization_code&code=CODE

    • 其实debug发现,
      clientId和clientSecret也会在org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter进行校验,形成一个UsernamePasswordAuthenticationToken ,权限就是oauth_client_details表中配置的authorities。

    这里需要注意,设置了security.allowFormAuthenticationForClients();,使得clientId和clientSecret可以以拼接的方式加上参数,如果不配置得用basic来装载,如果是使用basic,就不是走ClientCredentialsTokenEndpointFilter,而是走BasicAuthenticationFilter,在返回统一json结果时,需要走BasicAuthenticationFilter,在文章《https://www.jianshu.com/p/5a76d246b37f

    》说明。

    @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
                throws AuthenticationException, IOException, ServletException {
    
            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");
    
            // 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);
    
            return this.getAuthenticationManager().authenticate(authRequest);
    
        }
    
    • 查看源码:
      org.springframework.security.oauth2.provider.endpoint.TokenEndpoint
        @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.");
            }
    
            String clientId = getClientId(principal);
            ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
    
            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");
                }
            }
            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");
            }
    
            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());
                }
            }
    
            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)));
            }
    
            OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
            if (token == null) {
                throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
            }
    
            return getResponse(token);
    
        }
    
    • 获取access_token,本来是post请求的,而且clientId和clientSecret不能放在url中,要想它们可以这样做,需要设置一下,在 继承 AuthorizationServerConfigurerAdapter类中重写中

    • configure(AuthorizationServerEndpointsConfigurer endpoints) 方法 allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);

    • configure(AuthorizationServerSecurityConfigurer security) 方法 allowFormAuthenticationForClients()

    设置如下:

    @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
        }
    
    @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            security.passwordEncoder(passwordEncoder);
            // 开启/oauth/check_token验证端口认证权限访问
            security.checkTokenAccess("isAuthenticated()");
            // 开启/oauth/token_key验证端口无权限访问
            security.tokenKeyAccess("permitAll()");
            /*
             *
             * 主要是让/oauth/token支持client_id和client_secret做登陆认证
             * 如果开启了allowFormAuthenticationForClients,那么就在BasicAuthenticationFilter之前
             * 添加ClientCredentialsTokenEndpointFilter,使用ClientDetailsUserDetailsService来进行登陆认证
             *
             */
            security.allowFormAuthenticationForClients();
        }
    
    • 请求后,一如既往,还是根据clientId去查询相关信息,比较scope是否一致、是否存在该clientId,是否存在grantType参数以及它是不是为authorization_code,有无code授权码,都验证无问题,就生成access_token,其实就是一个uuid。

    这里要注意的是

    • org.springframework.security.oauth2.provider.token.DefaultTokenServices 这个类中创建access_token,注意有效期
    private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
            DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
            int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());
            if (validitySeconds > 0) {
                token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
            }
            token.setRefreshToken(refreshToken);
            token.setScope(authentication.getOAuth2Request().getScope());
    
            return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
        }
    
    • 具体以下,得知access_token第一选择是数据库中配置的,其次是DefaultTokenService中的默认12个小时。其实我们还可以在配置中进行设置的,还有refresh_token也是该原理!
    private int accessTokenValiditySeconds = 60 * 60 * 12; // default 12 hours.
    
    protected int getAccessTokenValiditySeconds(OAuth2Request clientAuth) {
            if (clientDetailsService != null) {
                ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
                Integer validity = client.getAccessTokenValiditySeconds();
                if (validity != null) {
                    return validity;
                }
            }
            return accessTokenValiditySeconds;
        }
    
    public class Oauth2JdbcAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
         @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.tokenServices(customTokenService());
        }
    }
    
    @Bean
        public DefaultTokenServices customTokenService(){
            DefaultTokenServices tokenServices = new DefaultTokenServices();
            tokenServices.setSupportRefreshToken(true);
            tokenServices.setAccessTokenValiditySeconds(3);
            tokenServices.setRefreshTokenValiditySeconds(6);
            return tokenServices;
        }
    
    • 授权码code参数只能用一次,因为在获取access_token的时候,已经做了删除code的操作,已存在的token,再用新code请求,只要token还在有效期内,就会返回已存在的token,只是有效期是延续之前的,而不是重新完整有效期!沿着代码追寻下去可得:


    public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
    
            if (!this.grantType.equals(grantType)) {
                return null;
            }
            
            String clientId = tokenRequest.getClientId();
            ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
            validateGrantType(grantType, client);
    
            if (logger.isDebugEnabled()) {
                logger.debug("Getting access token for: " + clientId);
            }
    
            return getAccessToken(client, tokenRequest);
    
        }
    
    protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
            return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
        }
    
    //找到这个类的授权码模式得继承类,如下图
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
            OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(client, tokenRequest);
            return new OAuth2Authentication(storedOAuth2Request, null);
        }
    

    相关文章

      网友评论

        本文标题:spring security oauth2通过授权码获取acc

        本文链接:https://www.haomeiwen.com/subject/etojnltx.html