Spring Security unexpected behavior for REST endpoints authentication?

前端 未结 6 1535
暖寄归人
暖寄归人 2021-01-15 03:44

The scenario we are looking for is as follows:

  1. client connects with REST to a REST login url
  2. Spring microservice (using Spring Security) should return
相关标签:
6条回答
  • 2021-01-15 03:49

    You can implement your custom AuthenticationSuccessHandler and override method "onAuthenticationSuccess" to change the response status as per your need.

    Example:

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
        ObjectMapper mapper = new ObjectMapper();
        Map<String, String> tokenMap = new HashMap<String, String>();
        tokenMap.put("token", accessToken.getToken());
        tokenMap.put("refreshToken", refreshToken.getToken());
        response.setStatus(HttpStatus.OK.value());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        mapper.writeValue(response.getWriter(), tokenMap);
    }
    
    0 讨论(0)
  • 2021-01-15 03:56

    You can use headers().defaultsDisabled() and then chain that method to add the specific headers you want.

    0 讨论(0)
  • 2021-01-15 03:58

    http.formLogin()

    is designed for form-based login. So the 302 status and Location header in the response is expected if you attempt to access a protected resource without being authenticated.

    Based on your requirement/scenario,

    1. client connects with REST to a REST login url

    have you considered using HTTP Basic for authentication?

    http.httpBasic()

    Using HTTP Basic, you can populate the Authorization header with the username/password and the BasicAuthenticationFilter will take care of authenticating the credentials and populating the SecurityContext accordingly.

    I have a working example of this using Angular on the client-side and Spring Boot-Spring Security on back-end.

    If you look at security-service.js, you will see a factory named securityService which provides a login() function. This function calls the /principal endpoint with the Authorization header populated with the username/password as per HTTP Basic format, for example:

    Authorization : Basic base64Encoded(username:passsword)

    The BasicAuthenticationFilter will process this request by extracting the credentials and ultimately authenticating the user and populating the SecurityContext with the authenticated principal. After authentication is successful, the request will proceed to the destined endpoint /principal which is mapped to SecurityController.currentPrincipal which simply returns a json representation of the authenticated principal.

    For your remaining requirements:

    1. Spring microservice (using Spring Security) should return 200 OK and a login token
    2. the client keeps the token
    3. the client calls other REST endpoints using the same token.

    You can generate a security/login token and return that instead of the user info. However, I would highly recommend looking at Spring Security OAuth if you have a number of REST endpoints deployed across different Microservices that need to be protected via a security token. Building out your own STS (Security Token Service) can become very involved and complicated so not recommended.

    0 讨论(0)
  • 2021-01-15 03:59

    You need to override the default logout success handler to avoid redirect. In spring boot2 you can do as below:

    ....logout().logoutSuccessHandler((httpServletRequest,httpServletResponse,authentication)->{
                    //do nothing not to redirect
            })
    

    For more details: Please check this.

    0 讨论(0)
  • 2021-01-15 04:00

    It's a 302 response telling the browser to redirect to your login page. What do you expect to happen? 302 response must have a Location header.

    0 讨论(0)
  • 2021-01-15 04:01

    If you need a rest api, you must not use http.formLogin(). It generates form based login as described here.

    Instead you can have this configuration

    httpSecurity
                    .csrf()
                        .disable()
                    .exceptionHandling()
                        .authenticationEntryPoint(authenticationEntryPoint)
                    .and()
                    .sessionManagement()
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                        .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                        .antMatchers("/login").permitAll()
                        .anyRequest().authenticated()
                    .and()
                    .logout()
                        .disable()
                    .addFilterBefore(authTokenFilter, UsernamePasswordAuthenticationFilter.class);
    

    Create a class, AuthTokenFilter which extends Spring UsernamePasswordAuthenticationFilter and override doFilter method, which checks for an authentication token in every request and sets SecurityContextHolder accordingly.

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            HttpServletResponse resp = (HttpServletResponse) response;
            resp.setHeader("Access-Control-Allow-Origin", "*");
            resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
            resp.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, " + tokenHeader);
    
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            String authToken = httpRequest.getHeader(tokenHeader);
            String username = this.tokenUtils.getUsernameFromToken(authToken); // Create some token utility class to manage tokens
    
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
    
                UsernamePasswordAuthenticationToken authentication =
                                new UsernamePasswordAuthenticationToken(-------------);
                // Create an authnetication as above and set SecurityContextHolder
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
            SecurityContextHolder.getContext().setAuthentication(authentication);
            }
            chain.doFilter(request, response);
    }
    

    Then create an AuthenticationController, mapped with /login url, which checks credentials, and returns token.

    /*
    * Perform the authentication. This will call Spring UserDetailsService's loadUserByUsername implicitly
    * BadCredentialsException is thrown if username and password mismatch
    */
    Authentication authentication = this.authenticationManager.authenticate(
         new UsernamePasswordAuthenticationToken(
                authenticationRequest.getUsername(),
                authenticationRequest.getPassword()
         )
    );
    SecurityContextHolder.getContext().setAuthentication(authentication);        
    UserDetailsImp userDetails = (UserDetailsImp) authentication.getPrincipal();
    // Generate token using some Token Utils class methods, using this principal
    

    To understand loadUserByUsername , UserDetailsService and UserDetails, please refer Spring security docs }

    For better understanding, please thoroughly read above link and subsequent chapters.

    0 讨论(0)
提交回复
热议问题