【问题标题】:Can't make a successful Authorization request from Axios request to Spring-security oauth2 backend无法从 Axios 请求向 Spring-security oauth2 后端发出成功的授权请求
【发布时间】:2019-11-18 19:37:18
【问题描述】:

您好,我在 Postman 有这个请求。

如何在 Reactjs axios 中发出同样的请求?

executeAuthenticationService(username, password) {
        return axios.get(`${API_URL}/oauth/token?grant_type=password&username=${username}&password=${password}`,
            { headers: { Authorization: 'Basic ' + window.btoa('my-trusted-client : secret') } } )            
            .catch(
                (error) => {
                    console.log('error: ' + error);
                }
            );
    }

我尝试了上面的那个,但它不起作用。 谢谢

============== 更新: ==============

我已按照您的建议删除了空格。当涉及到 window.btoa 时,没有空格的转换似乎准确,但我仍然无法使其工作。

使用此代码:

executeAuthenticationService(username, password) {
        return axios.get(`${API_URL}/oauth/token?grant_type=password&username=${username}&password=${password}`,
            { 
                headers: 
                { 
                    Authorization: 'Basic ' + window.btoa('my-trusted-client:secret'),
                    "Content-Type": "application/json"
                } 
            })            
            .catch(
                (error) => {
                    console.log('error: ' + error);
                }
            );
    }

我什至将 Authorization 标头硬编码为:

Authorization: "Basic bXktdHJ1c3RlZC1jbGllbnQ6c2VjcmV0",

甚至尝试过:

"Authorization": "Basic bXktdHJ1c3RlZC1jbGllbnQ6c2VjcmV0",

没有任何作用,授权标头不会出现在请求中。

我在我的 Chrome 网络标签中看到了这个:

我发现我可以在axios中添加auth config所以我尝试了这个:

executeAuthenticationService(username, password) {
        return axios.get(`${API_URL}/oauth/token?grant_type=password&username=${username}&password=${password}`,
            { 
                headers: 
                {                     
                    "Content-Type": "application/json"
                }, 

                auth: {
                    username: 'my-trusted-client',
                    password: 'secret'
                }
            })            
            .catch(
                (error) => {
                    console.log('error: ' + error);
                }
            );
    }

仍然没有授权标头...请帮助。谢谢。

============== Update2 ==============

我要补充一些信息。

我的 Springboot 配置如下:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowCredentials(true)               
                .allowedHeaders("Authorization", "Cache-Control", "Content-Type", "Accept", "X-Requested-With", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Origin")
                .exposedHeaders("Access-Control-Expose-Headers", "Authorization", "Cache-Control", "Content-Type", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Origin")
                .allowedMethods("GET", "OPTIONS", "POST", "PUT", "DELETE", "PATCH");
    }
}

我使用的最后一个 reactJS 请求是这样的:

executeAuthenticationService(username, password) {
        return axios.get(`${API_URL}/oauth/token?grant_type=password&username=${username}&password=${password}`,
            { 
                headers: 
                {                     
                    "Content-Type": "application/json"
                }, 

                auth: {
                    username: 'my-trusted-client',
                    password: 'secret'
                }
            })            
            .catch(
                (error) => {
                    console.log('error: ' + error);
                }
            );
    }

当我点击登录表单中的提交按钮时

我从 Chrome 选项卡中得到这个(全部):

然后我在我的 Java 控制台上得到这个:

2019-07-11 06:40:47.008 DEBUG 8068 --- [io-8080-exec-10] o.s.web.servlet.DispatcherServlet        : "ERROR" dispatch for OPTIONS "/error?grant_type=password&username=bill&password=abc123", parameters={masked}
2019-07-11 06:40:47.009 DEBUG 8068 --- [io-8080-exec-10] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public void org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$EmptyHandler.handle()
2019-07-11 06:40:47.010 DEBUG 8068 --- [io-8080-exec-10] o.s.web.servlet.DispatcherServlet        : Exiting from "ERROR" dispatch, status 401

我不知道我还错过了什么。 请帮忙。 非常感谢。

【问题讨论】:

标签: reactjs spring-security oauth-2.0 axios spring-security-oauth2


【解决方案1】:

试试这个(删除空格):

    headers: { 
        Authorization: 'Basic ' + window.btoa('my-trusted-client:secret') 
    }

According to here

【讨论】:

    【解决方案2】:

    你可以试试这个:

    axios.get(`${API_URL}/oauth/token?grant_type=password&username=${username}&password=${password}`,
            {
              headers: {
                Authorization: "Basic " + window.btoa("my-trusted-client : secret"),
                "Content-Type": "application/json",
              },
            }
          )
          .then(resp => {})
          .catch(error => {
            console.log("error: " + error);
          });
    

    【讨论】:

      【解决方案3】:
      axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
      

      你可以试试这个来为你的所有请求设置标题

      【讨论】:

        【解决方案4】:

        可能是您的 Access-Control-Allow-Methods 上缺少 OPTIONS 的方法吗?

        这意味着您甚至可能在生成整个请求之前就被拒绝了?

        或者可能禁用您的 OPTIONS 请求作为授权的一部分,类似于此处所做的:https://www.baeldung.com/spring-security-cors-preflight

        【讨论】:

        • 嗨,这正是我 10 分钟前在厨房里和我的朋友讨论这个问题时的想法。我今晚回家时会试试看。谢谢。
        【解决方案5】:

        在使用 Spring 和 Axios 进行基本身份验证时,我失去了试图处理此类问题的生活欲望。

        在尝试了几乎所有方法之后,甚至像您的配置一样,我找到了适合我的用例的解决方案,也可以是您的。

        Axios 请求

        我用这样的axios发出请求,注意有一个auth参数:

          async removeAnexo(uuidAnexo, successCallback, errorCallback) {
            const credentials = JSON.parse(Cookies.get("credentials"));
        
            const axiosRequest = axios.create({
              baseURL: serverUrl,
              auth: {
                username: credentials.username,
                password: credentials.password
              },
              headers: {
                "Content-Type": "application/json"
              }
            });
            await axiosRequest
              .post("processos/anexo/" + uuidAnexo + "/remove")
              .then(res => {
                if (res.status === 200) {
                  successCallback();
                  return res;
                }
              })
              .catch(error => {
                errorCallback(error);
              });
          }
        

        弹簧配置

        1. 在发出您请求的实际请求之前,您需要有一个身份验证入口点以在预检向端点触发 OPTIONS 请求时公开一些必要的标头。
            import java.io.IOException;
        
            import javax.servlet.ServletException;
            import javax.servlet.http.HttpServletRequest;
            import javax.servlet.http.HttpServletResponse;
        
            import org.springframework.http.HttpHeaders;
            import org.springframework.http.HttpMethod;
            import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
            import org.springframework.stereotype.Component;
        
            @Component
            public class AuthEntryPoint extends BasicAuthenticationEntryPoint {
        
                @Override
                public void commence(HttpServletRequest request, HttpServletResponse response,
                        org.springframework.security.core.AuthenticationException authException)
                        throws IOException, ServletException {
                    if (HttpMethod.OPTIONS.matches( request.getMethod() )) {
                        response.setStatus( HttpServletResponse.SC_OK );
                        response.setHeader( HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, request.getHeader( HttpHeaders.ORIGIN ) );
                        response.setHeader( HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, request.getHeader( HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS ) );
                        response.setHeader( HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, request.getHeader( HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD ) );
                        response.setHeader( HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*" );
                        response.setHeader( HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true" );
                    } else {
                        response.sendError( HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage() );
                    }
                }
        
                @Override
                public void afterPropertiesSet() throws Exception {
                    this.setRealmName( "simpleRealm" );
                    super.afterPropertiesSet();
                }
        
            }
        
        
        1. 然后你需要提供一个自定义过滤器,一个 cors 过滤器,也暴露 一些标头,是 SessionManagementFilter 所必需的。
            import java.io.IOException;
        
            import javax.servlet.Filter;
            import javax.servlet.FilterChain;
            import javax.servlet.FilterConfig;
            import javax.servlet.ServletException;
            import javax.servlet.ServletRequest;
            import javax.servlet.ServletResponse;
            import javax.servlet.http.HttpServletRequest;
            import javax.servlet.http.HttpServletResponse;
        
            import org.springframework.stereotype.Component;
        
            @Component
            public class CorsFilter implements Filter {
        
                @Override
                public void init(FilterConfig filterConfig) throws ServletException {
        
                }
        
                @Override
                public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
                        throws IOException, ServletException {
                    HttpServletResponse response = (HttpServletResponse) servletResponse;
                    HttpServletRequest request = (HttpServletRequest) servletRequest;
        
                    response.setHeader( "Access-Control-Allow-Origin", request.getHeader( "Origin" ) );
                    response.setHeader( "Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS" );
                    response.setHeader( "Access-Control-Allow-Headers", "*" );
                    response.setHeader( "Access-Control-Allow-Credentials", "true" );
                    response.setHeader( "Access-Control-Expose-Headers", "Content-Disposition,X-Suggested-Filename,X-Auth-Token,Authorization" );
                    response.setHeader( "Access-Control-Max-Age", "180" );
                    filterChain.doFilter( servletRequest, servletResponse );
                }
        
                @Override
                public void destroy() {
        
                }
            }
        
        
        1. 现在,您必须提供一个 AuthenticationProvider,在我的情况下,我 配置了一个基于 BasicAuth 的 CustomAuthenticationProvider。
            import org.springframework.beans.factory.annotation.Autowired;
            import org.springframework.core.env.Environment;
            import org.springframework.core.env.Profiles;
            import org.springframework.security.authentication.AuthenticationProvider;
            import org.springframework.security.authentication.BadCredentialsException;
            import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
            import org.springframework.security.core.Authentication;
            import org.springframework.security.core.AuthenticationException;
            import org.springframework.security.core.authority.SimpleGrantedAuthority;
            import org.springframework.security.core.userdetails.UsernameNotFoundException;
            import org.springframework.security.crypto.password.PasswordEncoder;
            import org.springframework.stereotype.Component;
        
            import com.pontosistemas.clif.entities.system.Login;
            import com.pontosistemas.clif.repositories.system.LoginRepository;
        
            @Component
            public class CustomAuthenticationProvider implements AuthenticationProvider {
        
                @Autowired
                private LoginRepository loginRepository;
        
                @Autowired
                private Environment env;
        
                @Autowired
                private PasswordEncoder passwordEncoder;
        
                @Override
                public Authentication authenticate(Authentication auth) throws AuthenticationException {
                    boolean notProductionProfile = env.acceptsProfiles( Profiles.of( "default", "homolog" ) );
        
                    String username = auth.getName();
                    String password = auth.getCredentials().toString();
        
                    if (notProductionProfile && "admin".equals( username ) && "admin".equals( password )) {
                        return new UsernamePasswordAuthenticationToken( username, password, Arrays.asList( new SimpleGrantedAuthority( "ADMIN" ), new SimpleGrantedAuthority( "protected" ) ) );
                    } else {
                        boolean usernameActiveExists = loginRepository.existsLoginByUsernameAndActiveIsTrue( username );
        
                        if (usernameActiveExists) {
        
                            Login login = loginRepository.findByUsernameAndActiveIsTrue( username );
        
                            String hashPassword = login.getPassword();
        
                            boolean matches = passwordEncoder.matches( password, hashPassword );
        
                            if (matches)
                                return new UsernamePasswordAuthenticationToken( username, hashPassword, Arrays.asList( new SimpleGrantedAuthority( login.getRole().toString() ), new SimpleGrantedAuthority( "protected" ) ) );
                            else
                                throw new BadCredentialsException( "Senha inválida!" );
                        }
                    }
                    throw new UsernameNotFoundException( "Nome de usuário não encontrado" );
        
                }
        
                @Override
                public boolean supports(Class<?> auth) {
                    return auth.equals( UsernamePasswordAuthenticationToken.class );
                }
            }
        
        1. 最后,你需要用胶水把所有这些部件粘在一起,这个 胶水是一个WebSecurityConfigurerAdapter,您可以仔细查看更多细节,尝试注释每个配置行,看看会发生什么。就我而言,我配置了登录成功、注销或登录失败时发生的情况。对于每种情况,都有一个要实现的处理程序。
            import java.io.IOException;
            import java.util.HashMap;
            import java.util.Map;
        
            import javax.servlet.ServletException;
            import javax.servlet.http.HttpServletRequest;
            import javax.servlet.http.HttpServletResponse;
        
            import org.springframework.beans.factory.annotation.Autowired;
            import org.springframework.context.annotation.Configuration;
            import org.springframework.http.HttpMethod;
            import org.springframework.security.config.annotation.web.builders.HttpSecurity;
            import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
            import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
            import org.springframework.security.config.http.SessionCreationPolicy;
            import org.springframework.security.core.Authentication;
            import org.springframework.security.core.AuthenticationException;
            import org.springframework.security.web.authentication.AuthenticationFailureHandler;
            import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
            import org.springframework.security.web.session.SessionManagementFilter;
        
            import com.fasterxml.jackson.databind.ObjectMapper;
        
            @Configuration
            @EnableWebSecurity
            public class BasicSecurityAdapter extends WebSecurityConfigurerAdapter {
        
                @Autowired
                private AuthEntryPoint authEntryPoint;
        
                @Autowired
                private CustomAuthenticationProvider customAuthProvider;
        
                @Autowired
                private CorsFilter corsFilter;
        
                @Override
                protected void configure(HttpSecurity http) throws Exception {
                    http.csrf().disable();
                    http.addFilterBefore( corsFilter, SessionManagementFilter.class );
                    http.headers().frameOptions().disable();
                    http.httpBasic().authenticationEntryPoint( authEntryPoint );
                    http.authenticationProvider( customAuthProvider );
        
                    http
                            .authorizeRequests()
                            .antMatchers( "/public/**" ).permitAll()
                            .antMatchers( HttpMethod.OPTIONS, "/**" ).permitAll()
                            .antMatchers( "/**" ).hasAuthority( "protected" )
                            .anyRequest().authenticated()
                            .and()
                            .sessionManagement()
                            .sessionCreationPolicy( SessionCreationPolicy.STATELESS );
        
                    http.formLogin().loginProcessingUrl( "/login" );
        
                    http.formLogin().successHandler( new AuthenticationSuccessHandler() {
                        @Override
                        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                Authentication authentication) throws IOException, ServletException {
                            response.addHeader( "Access-Control-Expose-Headers", "role" );
                            response.setHeader( "Access-Control-Allow-Origin", request.getHeader( "Origin" ) );
                            response.setHeader( "Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS" );
                            response.setHeader( "Access-Control-Allow-Headers", "*" );
                            response.setHeader( "Access-Control-Allow-Credentials", "true" );
                            response.setHeader( "Access-Control-Expose-Headers", "Content-Disposition,X-Suggested-Filename,X-Auth-Token,Authorization" );
                            response.setHeader( "Access-Control-Max-Age", "180" );
                            String role = authentication.getAuthorities().stream().findFirst().get().toString();
                            response.addHeader( "role", role );
        
                            ObjectMapper objectMapper = new ObjectMapper();
                            Map<String, Object> data = new HashMap<String, Object>();
                            data.put( "role", role );
                            response.getWriter().print( objectMapper.writeValueAsString( data ) );
                        }
                    } );
        
                    http.formLogin().failureHandler( new AuthenticationFailureHandler() {
                        @Override
                        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                AuthenticationException exception) throws IOException, ServletException {
                            response.addHeader( "Access-Control-Expose-Headers", "role" );
                            response.setHeader( "Access-Control-Allow-Origin", request.getHeader( "Origin" ) );
                            response.setHeader( "Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS" );
                            response.setHeader( "Access-Control-Allow-Headers", "*" );
                            response.setHeader( "Content-type", "application/json; charset=UTF-8" );
                            response.setCharacterEncoding( "UTF-8" );
                            response.setStatus( HttpServletResponse.SC_UNAUTHORIZED );
        
                            ObjectMapper objectMapper = new ObjectMapper();
                            Map<String, String> data = new HashMap<String, String>();
                            data.put( "message", exception.getMessage() );
                            response.getWriter().print( objectMapper.writeValueAsString( data ) );
                        }
                    } );
        
                    http.logout().logoutSuccessHandler( (request, response, authentication) -> {
                        response.setHeader( "Access-Control-Allow-Origin", request.getHeader( "Origin" ) );
                        response.setHeader( "Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS" );
                        response.setHeader( "Access-Control-Allow-Headers", "*" );
                        response.setStatus( HttpServletResponse.SC_OK );
                    } );
                }
        
            }
        

        我的用例

        在我的场景中,我在用户登录时将用户登录名和密码存储在 cookie 中,并在调用 API 时携带这些凭据。

        我相信您可以从基本身份验证更改为尝试更改 CustomAuthenticationProvider 的另一个选项。

        对你来说可能有点晚了,但我希望这对某人有所帮助。

        【讨论】:

          猜你喜欢
          • 2020-05-11
          • 2020-11-03
          • 1970-01-01
          • 2022-11-14
          • 2021-01-09
          • 2020-05-23
          • 2015-10-15
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多