Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setup Stateless Authentication using Spring Boot

I am using latest version of Spring Boot and I am trying to setup StatelessAuthenticaion. So far the tutorials I've been reading are very vague and I am not sure what I am doing wrong. The tutorials I am using is...

http://technicalrex.com/2015/02/20/stateless-authentication-with-spring-security-and-jwt/

The problem with my setup is that it seems everything is running correctly except for the fact that TokenAuthenticationService::addAuthentication is never called so my token is never set and therefore it returns null when the TokenAuthenticationService::getAuthentication is called and therefore returns a 401 even when I successfully logged in (Because addAuthentication is never called to set the token in the header). I am trying to figure a way to add TokenAuthenticationService::addAuthentication but I find it quite difficult.

In the tutorial he adds something similar to WebSecurityConfig::UserDetailsService.userService into auth.userDetailsService().. The only problem I am getting with that is when I do so, it throws a CastingErrorException. It only works when I utilize UserDetailsService customUserDetailsService instead...

WebSecurityConfig

package app.config;

import app.repo.User.CustomUserDetailsService;
import app.security.RESTAuthenticationEntryPoint;
import app.security.RESTAuthenticationFailureHandler;
import app.security.RESTAuthenticationSuccessHandler;
import app.security.TokenAuthenticationService;
import app.security.filters.StatelessAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.sql.DataSource;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
@Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private static PasswordEncoder encoder;
    private final TokenAuthenticationService tokenAuthenticationService;

    private final CustomUserDetailsService userService;

    @Autowired
    private UserDetailsService customUserDetailsService;

    @Autowired
    private RESTAuthenticationEntryPoint authenticationEntryPoint;
    @Autowired
    private RESTAuthenticationFailureHandler authenticationFailureHandler;
    @Autowired
    private RESTAuthenticationSuccessHandler authenticationSuccessHandler;

    public WebSecurityConfig() {
        this.userService = new CustomUserDetailsService();
        tokenAuthenticationService = new TokenAuthenticationService("tooManySecrets", userService);
    }

    @Autowired
    public void configureAuth(AuthenticationManagerBuilder auth,DataSource dataSource) throws Exception {
        auth.jdbcAuthentication().dataSource(dataSource);
    }

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/**").authenticated();
        http.csrf().disable();
        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
        http.formLogin().defaultSuccessUrl("/").successHandler(authenticationSuccessHandler);
        http.formLogin().failureHandler(authenticationFailureHandler);
        //This is ho
        http.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService),
                UsernamePasswordAuthenticationFilter.class);

    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserDetailsService);
    }

    @Bean
    @Override
    public CustomUserDetailsService userDetailsService() {
        return userService;
    }

    @Bean
    public TokenAuthenticationService tokenAuthenticationService() {
        return tokenAuthenticationService;
    }
}

The TokenAuthenticationService successfully calls the getAuthentication method but in the tutorials I read, there is no proper explanation on how addAuthentication is called

TokenAuthenticationService

package app.security;

import app.repo.User.CustomUserDetailsService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TokenAuthenticationService {


    private static final String AUTH_HEADER_NAME = "X-AUTH-TOKEN";

    private final TokenHandler tokenHandler;
    //This is called in my WebSecurityConfig() constructor 
    public TokenAuthenticationService(String secret, CustomUserDetailsService userService) {
        tokenHandler = new TokenHandler(secret, userService);
    }

    public void addAuthentication(HttpServletResponse response, UserAuthentication authentication) {
        final UserDetails user = authentication.getDetails();
        response.addHeader(AUTH_HEADER_NAME, tokenHandler.createTokenForUser(user));
    }

    public Authentication getAuthentication(HttpServletRequest request) {
        final String token = request.getHeader(AUTH_HEADER_NAME);
        if (token != null) {
            final UserDetails user = tokenHandler.parseUserFromToken(token);
            if (user != null) {
                return new UserAuthentication(user);
            }
        }
        return null;
    }
}

TokenHandler

package app.security;

import app.repo.User.CustomUserDetailsService;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;

public final class TokenHandler {

    private final String secret;
    private final CustomUserDetailsService userService;

    public TokenHandler(String secret, CustomUserDetailsService userService) {
        this.secret = secret;
        this.userService = userService;
    }

     public UserDetails parseUserFromToken(String token) {
         String username = Jwts.parser()
        .setSigningKey(secret)
                 .parseClaimsJws(token)
                 .getBody()
                 .getSubject();
         return userService.loadUserByUsername(username);
    }

    public String createTokenForUser(UserDetails user) {
    return Jwts.builder()
            .setSubject(user.getUsername())
            .signWith(SignatureAlgorithm.HS512, secret)
            .compact();
    }
}

In my WebServiceConfig. I add the following

http.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService),
        UsernamePasswordAuthenticationFilter.class);

Which calls on the following class as a filter. It gets the Authentication, but there is No where where it actually adds it.

StatelessAuthenticationFilter

package app.security.filters;

import app.security.TokenAuthenticationService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * Created by anthonygordon on 11/17/15.
 */
public class StatelessAuthenticationFilter extends GenericFilterBean {

    private final TokenAuthenticationService authenticationService;

    public StatelessAuthenticationFilter(TokenAuthenticationService authenticationService) {
        this.authenticationService = authenticationService;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
        throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        Authentication authentication = authenticationService.getAuthentication(httpRequest);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        filterChain.doFilter(request, response);
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        SecurityContextHolder.getContext().setAuthentication(null);
    }
}

The following class is what gets passed in the TokenAuthenticationService::addAuthentication

UserAuthentication

package app.security;

import app.repo.User.User;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

public class UserAuthentication implements Authentication {

    private final UserDetails user;
    private boolean authenticated = true;

    public UserAuthentication(UserDetails user) {
        this.user = user;
    }

     @Override
    public String getName() {
        return user.getUsername();
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return user.getAuthorities();
    }

     @Override
    public Object getCredentials() {
        return user.getPassword();
    }

     @Override
    public UserDetails getDetails() {
        return user;
    }

     @Override
    public Object getPrincipal() {
        return user.getUsername();
    }

     @Override
    public boolean isAuthenticated() {
        return authenticated;
    }

     @Override
    public void setAuthenticated(boolean authenticated) {
        this.authenticated = authenticated;
    }
 }

Thats it...

My Solution (But Need Help)...

My solution was to set the TokenAuthenticationService::addAuthentication method in my success handler... The only problem with that is the tutorial added the class TokenAuthenticationService to the WebServiceConfig class. And thats the only place its accessible. If there is a way I can obtain it in my successHandler, I might be able to set the token.

package app.security;

import app.controllers.Requests.TriviaResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
 * Created by anthonygordon on 11/12/15.
 */
@Component
public class RESTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
        TriviaResponse tresponse = new TriviaResponse();
        tresponse.setMessage("You have successfully logged in");
        String json = ow.writeValueAsString(tresponse);
        response.getWriter().write(json);
        clearAuthenticationAttributes(request);
    }
}
like image 202
numerical25 Avatar asked Feb 04 '26 00:02

numerical25


1 Answers

You have to call TokenAuthenticationService.addAuthentication() yourself when a user supplies their login credentials the first time.

The tutorial calls addAuthentication() in GoogleAuthorizationResponseServlet after a user successfully logs in using their Google account. Here's the relevant code:

private String establishUserAndLogin(HttpServletResponse response, String email) {
    // Find user, create if necessary
    User user;
    try {
        user = userService.loadUserByUsername(email);
    } catch (UsernameNotFoundException e) {
        user = new User(email, UUID.randomUUID().toString(), Sets.<GrantedAuthority>newHashSet());
        userService.addUser(user);
    }

    // Login that user
    UserAuthentication authentication = new UserAuthentication(user);
    return tokenAuthenticationService.addAuthentication(response, authentication);
}

If you already have an authentication success handler, then I think you're on the right track that you need to call TokenAuthenticationService.addAuthentication() from there. Inject the tokenAuthenticationService bean into your handler and then start using it. If your success handler doesn't end up being a Spring bean then you can explicitly look tokenAuthenticationService up by calling WebApplicationContextUtils.getRequiredWebApplicationContext.getBean(TokenAuthenticationService.class).

There is also an issue in the tutorial's GitHub repo that will address the confusion between the initial login supplied by the user and the stateless authentication happening on all subsequent requests.

like image 155
Erik Gillespie Avatar answered Feb 07 '26 00:02

Erik Gillespie



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!