46

I am trying to set the database name as the request input parameter from the spring security login page. At present I am only getting username that's been retrieved using spring security SecurityContextHolder.getContext().getAuthentication().

How to access the additional field that's been set on the login page?

Tuna
  • 2,937
  • 4
  • 37
  • 61
Bhas
  • 875
  • 3
  • 10
  • 12
  • Do you want to pass this parameter before login or get parameters after login from DB? – vacuum Apr 09 '12 at 14:51
  • 2
    i want to get the request parameter value before connecting to DB, i wanted to decide which DB the user needs to get connected based upon the select values provided on the login screen. – Bhas Apr 09 '12 at 15:34
  • 1
    I dont think, Spring have capability to do it out of box. Anyway, you can create our own authentication filter and get this parameter from `HttpRequest` object. – vacuum Apr 09 '12 at 15:38
  • 1
    Lookt at the `UsernamePasswordAuthenticationFilter`. It is used by default on any login page. – vacuum Apr 09 '12 at 15:41
  • yes i am trying to work it out that way. But i missing some part over there and running into errors. Please Let me know if you have some idea or working sample (configuration) of it. – Bhas Apr 09 '12 at 15:42
  • What version of Spring Security are you using? – sourcedelica Apr 09 '12 at 19:17
  • may be your are looking for [this](http://naeemgik.blogspot.com/2018/10/passing-extra-login-fields-with-spring.html) – naeemgik Nov 12 '18 at 09:05

8 Answers8

47

There's a number of ways to do this but the official way to do it is using a custom AuthenticationDetails and AuthenticationDetailsSource, subclassing Spring's WebAuthenticationDetails and WebAuthenticationDetailsSource, respectively. Add the extra field to the custom WebAuthenticationDetails and have the custom WebAuthenticationDetailsSource get the data from the request to populate the field.

In Spring Security 3.1 it's easy to configure by using the authentication-details-source-ref attribute of the <form-login> element.

In 3.0 you have to use a BeanPostProcessor. There is an example in the Spring Security FAQ on using a BeanPostProcessor to configure a custom WebAuthenticationDetailsSource.

Once this is done then you can call SecurityContextHolder.getContext().getAuthentication().getDetails() to get access to your extra field.

sourcedelica
  • 23,940
  • 7
  • 66
  • 74
  • Thanks Ericacm, The have implemented the solution provided by Taylor Mingos and it worked fine for me. – Bhas Apr 09 '12 at 20:09
  • Thanks, for the custom AuthenticationDetails idea. This is interesting. You say it's official, but I can't find any official documentation about that technique. Is there any? – murrayc Apr 08 '13 at 19:03
  • 5
    There's not much documentation - you have to infer it from how the `UsernamePasswordAuthenticationFilter` works - it uses a `WebAuthenticationDetailsSource` and adds a `WebAuthenticationDetails` to the `UsernamePasswordAuthenticationToken`. – sourcedelica Apr 09 '13 at 01:57
27

Elaborating on @Vacuum's comment

Here's a simple way (untested, but I believe this would work)

  1. Create a new class ExUsernamePasswordAuthenticationFilter that will extend the default filter and grab the additional parameter and store it in the session. It will look something like this:
public class ExUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        final String dbValue = request.getParameter("dbParam");
        request.getSession().setAttribute("dbValue", dbValue);

        return super.attemptAuthentication(request, response); 
    } 
}
  1. In your UserDetailsService implementation, modify your implementation of:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException;

to grab the session variable that the filter from step 1) makes available.

  1. in your <http /> security set-up, override the default filter with your custom one
<custom-filter ref="beanForYourCustomFilterFromStep1" position="FORM_LOGIN_FILTER"/>

Refer to this part of the documentation for more info about custom filters: http://static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ns-custom-filters

Ratul Sharker
  • 7,484
  • 4
  • 35
  • 44
  • Thanks a lot for your information. I implemented it, it worked well. – Bhas Apr 09 '12 at 20:08
  • 1
    There is less invasive way of doing this and without violating the contract of `UserDetailsService`. I agree with @sourcedelica. Also eee my [answer](http://stackoverflow.com/a/16155696/719633) to similar question for more detailed example. – Roadrunner Apr 22 '13 at 20:11
  • 4
    @Bhas, how did you modified UserDetailsService to grab the session variable? – Tural Mar 15 '16 at 13:58
23

sourcedelica mentioned using AuthenticationDetailsSource and a custom AuthenticationDetails. Here is an example.

Add authentication-details-source-ref attribute with the bean id customWebAuthenticationDetailsSource to form-login:

<security:http>
    <security:intercept-url pattern="/**" access="..." />
    <security:form-login authentication-details-source-ref="customWebAuthenticationDetailsSource" login-page="..." />
    <security:logout logout-success-url="..." />
</security:http>

Create a new class CustomWebAuthenticationDetailsSource:

package security;

import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.web.authentication.WebAuthenticationDetails;

import javax.servlet.http.HttpServletRequest;

public class CustomWebAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {
    @Override
    public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
        return new CustomWebAuthenticationDetails(context);
    }
}

and the related CustomWebAuthenticationDetails:

package security;

import org.springframework.security.web.authentication.WebAuthenticationDetails;
import javax.servlet.http.HttpServletRequest;

public class CustomWebAuthenticationDetails extends WebAuthenticationDetails {

    private final String yourParameter;

    public CustomWebAuthenticationDetails(HttpServletRequest request) {
        super(request);
        yourParameter = request.getParameter("yourParameter");
    }

    public String getyourParameter() {
        return yourParameter;
    }

    //TODO override hashCode, equals and toString to include yourParameter
    @Override
    public int hashCode() { /* collapsed */ }
    @Override
    public boolean equals(Object obj) { /* collapsed */ }
    @Override
    public String toString() { /* collapsed */ }
}
Community
  • 1
  • 1
ksokol
  • 8,035
  • 3
  • 43
  • 56
  • then How can I use the **CustomWebAuthenticationDetails** ? – flyingfox Jun 12 '15 at 09:03
  • see @sourcedelica answer: `SecurityContextHolder.getContext().getAuthentication().getDetails()` – ksokol Jun 18 '15 at 14:06
  • Did not work for me. The AuthenticationDetailsSource calls buildDetails after the login and my GET parameter is not there anymore. Using Spring 3.1.6 – raupach Nov 30 '15 at 11:18
  • 1
    @raupach There must be something wrong with your security configuration. If you like you can grab my example from [Github](https://github.com/ksokol/spring-sandbox/blob/master/spring-mvc/src/main/resources/security.xml) for debugging. – ksokol Nov 30 '15 at 12:35
  • @ksokol Yes, my bad. The parameter is available in the post request not in the initial get request. I now set the get paramaters I need as input hidden and then it works fine. – raupach Nov 30 '15 at 12:40
  • Thanks for the example @ksokol, it works mostly fine for me but while getting the details, the context is null. I need the extra parameter to perform a custom query by overriding JdbcDaoImpl.loadUsersByUsername(String username). Any idea of how to get the extra parameter at that point? – DiegoSahagun Dec 08 '15 at 09:45
  • 1
    @diegosahagun While calling `JdbcDaoImpl.loadUsersByUsername` yet the `SecurityContext` is not populated with the current `Authentication`, hence `null` reference. In your case you need a custom `AuthenticationProvider` which is capable of handling your extra parameter. In that case you can't `UserDetailsService`. Maybe you can elaborate on your problem in a separate question and receive multiple answers. – ksokol Dec 08 '15 at 10:11
9

There is an easier way if you are using custom AuthenticationProvider. You can just inject HttpServletRequest and retrieve your extra parameter:

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Autowired(required = false)
    private HttpServletRequest request;

    @Autowired
    private MyAccountService myAccountService;

    @Override
    public Authentication authenticate(Authentication authentication) {

        System.out.println("request testing= " + request.getParameter("testing"));

        .....
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}
Sam YC
  • 10,725
  • 19
  • 102
  • 158
5

@user1322340 does not provide implement detail to get session Attributes in loadUserByUsername function:

Step 1: Follow all the step provided by @user1322340

Step 2: you need add one configuration in web.xml like this:

<listener>
    <listener-class>
       org.springframework.web.context.request.RequestContextListener
    </listener-class>
</listener>

Step 3: Use such code to get attributes:

RequestContextHolder.getRequestAttributes().getAttribute("yourAttributeName", RequestAttributes.SCOPE_SESSION);

Step 4: Register your filter in spring security config. If you get a error "authenticationManager must be specified". after you register your filter in config. You need set a authenticationManagerBean for your extended filter and config it in that way:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public ExUsernamePasswordAuthenticationFilter exUsernamePasswordAuthenticationFilter()
            throws Exception {
        ExUsernamePasswordAuthenticationFilter exUsernamePasswordAuthenticationFilter = new ExUsernamePasswordAuthenticationFilter();
        exUsernamePasswordAuthenticationFilter
                .setAuthenticationManager(authenticationManagerBean());
        return exUsernamePasswordAuthenticationFilter;
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        RequestMatcher requestMatcher = new RequestMatcher() {
            @Override
            public boolean matches(HttpServletRequest httpServletRequest) {
                if (httpServletRequest.getRequestURI().indexOf("/api", 0) >= 0) {
                    return true;
                }
                return false;
            }
        };

        http
                .addFilterBefore(exUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                ...
    }
}
Nicholas Lu
  • 1,655
  • 15
  • 14
3

For spring security 3.0 or above which uses java configuration, the following simple steps works well.

  • Add a your of filter before the UserNameandPasswordAuthenticationFilter in HttpSecurity object in configure.

    http.addFilterBefore(new YourFilter(), UsernamePasswordAuthenticationFilter.class);
    
  • Let the filter has a line like this to get the needed fields in your request to session.

    if(requestPath != null &&requestPath.equals("/login") ) {
            session.setAttribute("yourParam",req.getParameter("yourParam"));
        }
    
  • Later you may get the parameter value from the session in any class as:

    String yourParam =(String)request.getSession().getAttribute("yourParam");
    
0

Simple way:

1) register RequestContextListener

@Bean
public RequestContextListener requestContextListener(){
    return new RequestContextListener();
}

2) And to main class:

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.
currentRequestAttributes()).
getRequest();

3) After that we can take params in custom headers:

request.getHeader("OrganizationId")
0

Simplest way in only 2 steps:

Step 1.

Add the following listener in web.xml:

<listener>
    <listener-class>
        org.springframework.web.context.request.RequestContextListener
    </listener-class>
</listener>

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value></param-value>
</context-param>

Step 2.

Add the following in your class method where you want to get additional param:

RequestAttributes attribs = RequestContextHolder.getRequestAttributes();

if (RequestContextHolder.getRequestAttributes() != null) {
    HttpServletRequest request = ((ServletRequestAttributes) attribs).getRequest();
}

Now you can get your additional parameter by the following, assuming the extra parameter is named "loginType":

request.getParameter("loginType")