3

I have a Spring MVC app that uses Spring Security and form based login for authorization/authentication.

Now I want to add a special URL that includes a token that should be accessible without additional information because the token is unique to a user:

http://myserver.com/special/5f6be0c0-87d7-11e2-9e96-0800200c9a66/text.pdf

How do I need to configure Spring Security to use that token for user authentication?

Thomas Einwaller
  • 8,873
  • 4
  • 40
  • 55
  • Do you want programatically authenticate your user based on this token? – Maksym Demidas Mar 08 '13 at 10:17
  • yes, I want to load the user via this token from the DB with a custom UserDetailsService and fully authenticate him as if he signed in with login/password – Thomas Einwaller Mar 08 '13 at 10:30
  • 1
    Fully authenticating a user based on a URL is not a good idea. If you are just using it to provide access to that file then that's probably fair enough, but I'd be careful if it allows them to do other things they can normally only do with a full login. – Shaun the Sheep Mar 08 '13 at 14:38

3 Answers3

1

You can provide a custom PreAuthenticatedProcessingFilter and PreAuthenticatedAuthenticationProvider. See Pre-Authentication Scenarios chapter for details.

xuesheng
  • 3,396
  • 2
  • 29
  • 38
Maksym Demidas
  • 7,707
  • 1
  • 29
  • 36
1

You need to define your custom pre auth filter.

In security app context within http tag:

<custom-filter position="PRE_AUTH_FILTER" ref="preAuthTokenFilter" />

Then define your filter bean (and its properties approprietly):

<beans:bean class="com.yourcompany.PreAuthTokenFilter"
      id="preAuthTokenFilter">
    <beans:property name="authenticationDetailsSource" ref="authenticationDetailsSource" />
    <beans:property name="authenticationManager" ref="authenticationManager" />
    <beans:property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
</beans:bean>

Create your custom filter extended from GenericFilterBean

public class PreAuthTokenFilter extends GenericFilterBean {

private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationManager authenticationManager;
private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource();

@Override
public void doFilter(ServletRequest req, ServletResponse resp,
                     FilterChain chain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) resp;

    String token = getTokenFromHeader(request);//your method

    if (StringUtils.isNotEmpty(token)) {
        /* get user entity from DB by token, retrieve its username and password*/

        if (isUserTokenValid(/* some args */)) {
            try {
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
                authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
                Authentication authResult = this.authenticationManager.authenticate(authRequest);
                SecurityContextHolder.getContext().setAuthentication(authResult);
            } catch (AuthenticationException e) {
            }
        }
    }

    chain.doFilter(request, response);
}

/*
other methods
*/

If you don't want or cannot retrieve a password, you need to create your own AbstractAuthenticationToken which will receive only username as param (principal) and use it instead of UsernamePasswordAuthenticationToken:

public class PreAuthToken extends AbstractAuthenticationToken {

    private final Object principal;

    public PreAuthToken(Object principal) {
        super(null);
        super.setAuthenticated(true);
        this.principal = principal;
    }

    @Override
    public Object getCredentials() {
        return "";
    }

    @Override
    public Object getPrincipal() {
        return principal;
    }
}
Igorock
  • 2,691
  • 6
  • 28
  • 39
0

I ran into this problem, and solved it using a custom implementation of the Spring Security RembereMe Service infrastructure. Here is what you need to do.

  • Define your own Authentication object

    public class LinkAuthentication extends AbstractAuthenticationToken { @Override public Object getCredentials() { return null; }

    @Override
    public Object getPrincipal()
    {
    
        return the prncipal that that is passed in via the constructor 
    }
    

    }

Define

public class LinkRememberMeService implements RememberMeServices, LogoutHandler
{    
    /**
     * It might appear that once this method is called and returns an authentication object, that authentication should be finished and the
     * request should proceed. However, spring security does not work that way.
     * 
     * Once this method returns a non null authentication object, spring security still wants to run it through its authentication provider
     * which, is totally brain dead on the part of Spring this, is why there is also a
     * LinkAuthenticationProvider
     * 
     */
    @Override
    public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response)
    {
        String accessUrl = ServletUtils.getApplicationUrl(request, "/special/");
        String requestUrl = request.getRequestURL().toString();
        if (requestUrl.startsWith(accessUrl))
        {
            // take appart the url extract the token, find the user details object 
                    // and return it. 
            LinkAuthentication linkAuthentication = new LinkAuthentication(userDetailsInstance);
            return linkAuthentication;
        } else
        {
            return null;
        }
    }

    @Override
    public void loginFail(HttpServletRequest request, HttpServletResponse response)
    {
    }

    @Override
    public void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication)
    {
    }

    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
    {
    }
}


public class LinkAuthenticationProvider implements AuthenticationProvider
{

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException
    {
        // Spring Security is totally brain dead and over engineered
        return authentication;
    }

    @Override
    public boolean supports(Class<?> authentication)
    {
        return LinkAuthentication.class.isAssignableFrom(authentication);
    }

}

Hack up the rest rest of your spring security xml to define a custom authentication provider, and the custom remember me service.

P.S. if you do base64 encoding of the GUID in your URL it will be a few characters shorter. You can use the Apache commons codec base64 binary encoder / decoder to do safer url links.

public static String toBase64Url(UUID uuid)
{
    return Base64.encodeBase64URLSafeString(toBytes(uuid));
}
ams
  • 60,316
  • 68
  • 200
  • 288