lior
lior

Reputation: 1187

Manually authenticate use spring security

I am using spring security and it works fine, but now I want to start the security process manually, do to client changes I need to get in my controller the user name and password (the form wont call "j_spring_security_check" directly)

I thought of 2 options with both I have some problems:

  1. After I get the parameters and do something I will send a post request to j_spring_security_check url. My code:

    public void test(loginDTO loginDTO) {

    MultiValueMap<String, String> body = new LinkedMultiValueMap<String, String>();
    HttpHeaders headers = new HttpHeaders();
    
    body.add(
         "j_username",
         loginDTO.getJ_username());
    
    body.add(
         "j_password",
         loginDTO.getJ_password());
    
    HttpEntity<?> httpEntity = new HttpEntity<Object>(
                              body, headers);
    headers.add(
            "Accept",
            MediaType.APPLICATION_JSON_VALUE);
    restTemplate.exchange(
                  "http://localhost:8080/XXX/j_spring_security_check",
                  HttpMethod.POST,
                  httpEntity,
                  HttpServletResponse.class);
    } 
    

This doesn't work and I get :500 internal server error why?

  1. second option- I did the following:

    public void test2(loginDTO loginDTO, HttpServletRequest request) {
    
      UsernamePasswordAuthenticationToken token =
                    new UsernamePasswordAuthenticationToken(
                              loginDTO.getJ_username(),
                              loginDTO.getJ_password());
    
      token.setDetails(new WebAuthenticationDetails(request));
      Authentication authentication = this.authenticate(token);
    
      SecurityContextHolder.getContext().setAuthentication(authentication);
    
      this.sessionRegistry.registerNewSession(
                        request.getSession().getId(),
                        authentication.getPrincipal());
    }
    

    The problem is that onAuthenticationSuccess is not called. and it feels wrong, that I'm missing the point of using spring security.

What is the correct why?

Upvotes: 2

Views: 3035

Answers (3)

lior
lior

Reputation: 1187

OK so I combined @Ralph and @manish answers and this is what I did:

(twoFactorAuthenticationFilter is an extension of UsernamePasswordAuthenticationFilter)

 public void manualAuthentication(loginDTO loginDTO, HttpServletRequest request, HttpServletResponse response) throws IOException,
        ServletException {

    AddableHttpRequest addableHttpRequest = new AddableHttpRequest(
                                       request);

    addableHttpRequest.addParameter(
                    "j_username",
                    loginDTO.getJ_username());
    addableHttpRequest.addParameter(
                    "j_password",
                    loginDTO.getJ_password());

    UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) twoFactorAuthenticationFilter.attemptAuthentication(
                                                                          addableHttpRequest,
                                                                          response);
    if (token.isAuthenticated()) {
        twoFactorAuthenticationFilter.successfulAuthentication(
                                   addableHttpRequest,
                                   response,
                                   null,
                                   token);
    }


    }

It works fine

Upvotes: 0

manish
manish

Reputation: 20135

I typically do the following:

@Controller
public class AuthenticationController
{
  @Autowired
  AuthenticationManager authenticationManager;

  @Autowired
  SecurityContextRepository securityContextRepository;

  @RequestMapping(method = Array(RequestMethod.POST), value = Array("/authenticate"))
  public String authenticate(@RequestParam String username, @RequestParam String password, HttpServletRequest request, HttpServletResponse response)
  {
    Authentication result = this.authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));

    SecurityContextHolder.getContext.setAuthentication(result);

    this.securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response);

    return "successView";
  }
}

The reasons for using this approach is:

  1. Very simple, just a few lines of code if you ignore exception handling and such.
  2. Leverages existing Spring Security components.
  3. Uses Spring Security components configured in the application configuration and allows them to be changed as and when required. For example, the authentication may be done against an RDBMS, LDAP, web service, Active Directory, etc. without the custom code needing to worry about it.

Upvotes: 3

Ralph
Ralph

Reputation: 120781

When you want to use as most as possible from the normal Authentication Process, then you could create a mocked HttpServletRequest and HttpServletResponse (org.springframework.mock.web.MockHttpServletRequest and org.springframework.mock.web.MockHttpServletResponse) containing login and password, and then invoke

 UsernamePasswordAuthenticationFilter.attemptAuthentication(
            HttpServletRequest request,
            HttpServletResponse response)`

afterwards you will also need to invoke SessionAuthenticationStrategy.onAuthentication(..) and successfulAuthentication(..)

This is all a bit tricky, because of private fileds, so this is my solution:

public class ExtendedUsernamePasswordAuthenticationFilter
                           extends UsernamePasswordAuthenticationFilter {


    @Override
    public void manualAuthentication(String login,
                                     String password,
                                     HttpServletRequest httpServletRequest)
                              throws IOException, ServletException {

        /** I do not mock the request, I use the existing request and
            manipulate them*/
        AddableHttpRequest addableHttpRequest =
                                  new AddableHttpRequest(httpServletRequest);
        addableHttpRequest.addParameter("j_username", login);
        addableHttpRequest.addParameter("j_password", password);

        MockHttpServletResponse mockServletResponse =
                                  new MockHttpServletResponse();
        Authentication authentication = this.attemptAuthentication(
                                                addableHttpRequest,
                                                mockServletResponse);

        this.reflectSessionStrategy().onAuthentication(
                                        authentication,
                                        addableHttpRequest,
                                        mockServletResponse);
        this.successfulAuthentication(addableHttpRequest,
                                      mockServletResponse,
                                      authentication);
    }

    private SessionAuthenticationStrategy reflectSessionStrategy() {

        Field sessionStrategyField =
                      ReflectionUtils.findField(
                             AbstractAuthenticationProcessingFilter.class,
                             "sessionStrategy",
                             SessionAuthenticationStrategy.class);
        ReflectionUtils.makeAccessible(sessionStrategyField);

        return (SessionAuthenticationStrategy)
               ReflectionUtils.getField(sessionStrategyField, this);
    }
}

AddableHttpRequest is like a mock that is based on an real request

public class AddableHttpRequest extends HttpServletRequestWrapper {

    /** The params. */
    private HashMap<String, String> params = new HashMap<String, String>();


    public AddableHttpRequest(HttpServletRequest request) {
        super(request);
    }

    @Override
    public String getMethod() {
        return "POST";
    }

    @Override
    public String getParameter(final String name) {
        // if we added one, return that one
        if (params.get(name) != null) {
            return params.get(name);
        }
        // otherwise return what's in the original request
        return super.getParameter(name);
    }

    public void addParameter(String name, String value) {
        params.put(name, value);
    }    
}

An other way, would be implementing your own, authentication filter. Thats a class that invoke the AuthenticationManager.authenticate(Authentication authentication). But this class is also responsible for invoking all the stuff around authentication (what AbstractAuthenticationProcessingFilter.doFilter does)`

Upvotes: 0

Related Questions