无法从Axios请求向Spring-security oauth2后端进行成功的授权请求

时间:2019-07-09 09:21:25

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

嗨,我在邮递员中有此要求。

enter image description here

如何在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的“网络”标签中找到了该标签:

enter image description here

我发现我可以在axios中添加身份验证配置,所以我尝试了这一操作:

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标签中获得的(整个内容):

enter image description here

然后我在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

我不知道我还想念什么。 请帮忙。 非常感谢。

5 个答案:

答案 0 :(得分:0)

尝试一下(删除空格):

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

According to here

答案 1 :(得分:0)

您可以尝试以下方法:

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

答案 2 :(得分:0)

axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;

您可以尝试设置所有请求的标题

答案 3 :(得分:0)

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

这意味着您甚至在生成整个请求之前都会被拒绝?

或者可能会禁用您的OPTIONS请求作为授权的一部分,类似于在这里进行的操作:https://www.baeldung.com/spring-security-cors-preflight

答案 4 :(得分:0)

在使用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的另一个选项。

也许对您来说有点晚了,但我希望这对某人有帮助。