简体   繁体   中英

User Authentication with REST in a web app secured with Spring Security

My Spring Boot-based web app authenticates against a remote REST api. To do so, I have extended AbstractUserDetailsAuthenticationProvider like so:

public class RestAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        // TODO Auto-generated method stub
    }

    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        String password = (String) authentication.getCredentials();
        Credentials creds = new Credentials();
        creds.setUsername(username);
        creds.setPassword(password);

        RestTemplate template = new RestTemplate();
        try {
            ResponseEntity<Authentication> auth = template.postForEntity("http://localhost:8080/api/authenticate",
                    creds, Authentication.class);
            if (auth.getStatusCode() == HttpStatus.OK) {

                String token = auth.getHeaders().get("Authorization").get(0); // Great! Now what?

                return new User(authentication.getName(), password,
                        Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
            }

            throw new BadCredentialsException("Failed to authenticate, server returned " + auth.getStatusCodeValue());

        } catch (HttpClientErrorException e) { // Check for type of error
            throw new BadCredentialsException(e.getStatusText());
        }
    }
}

This works great. However my problem is that subsequent access to the API will require the API key being provided in the RestTemplate headers. This is the line :

String token = auth.getHeaders().get("Authorization").get(0); // Great! Now what?  

What I'm after is some way to persist that token at the session level for future access. Down the line, I'm trying to do something along the lines of:

    SecurityContext context = SecurityContextHolder.getContext();
    Authentication userDetails = context.getAuthentication() ;

where somehow userDetails would contain the API key.

Any suggestions?

Thanks!

This is you designing & implementing your own security solution. The framework provides you a way to implement how you resolve UserDetails based on credentials .

I see the question for subsequent invocations of this. For that reason "old school" web apps have sessions, but "new school" thinks it's "not cool" anymore as it "does not scale" ( or whatever other reasons ) and I assume you're one of those.

If you don't want to use sessions - then you need to get your user from the database ( or cache, or session store, or what you like ) based on some identified.

Your current implementation provides this feature by using username and password , but I assume you don't want to keep that in memory & send with every request.

So you want to generate a token, store it in the database ( or cache, or token store, or session store ) and then implement another AuthenticationProvider which will next time read the UserDetails based on token, not user credentials.

You could use a JWT token which actually is able to serialize your whole UserDetails object into the token itself & send it over the wire, but I wouldn't recommend that either as there is no way to invalidate it, unless you're storing the token id in your database / session store.

More to read here: Invalidating JSON Web Tokens

So it turns out it was a simple matter of extending WebAuthenticationDetails , like so:

public class JwtAuthenticationDetails extends WebAuthenticationDetails {

    private static final long serialVersionUID = 6951476359238150556L;
    private String token ;

    public JwtAuthenticationDetails(HttpServletRequest request) {
        super(request);
    }
    public String getToken() {
        return token;
    }
    public void setToken(String token) {
        this.token = token;
    }

    @Override
    public String toString() {
        return "JwtAuthenticationDetails [token=" + token + "; " + super.toString() + "]";
    }
}

And then to register that in my WebSecurityConfigurerAdapter :

private AuthenticationDetailsSource<HttpServletRequest, JwtAuthenticationDetails> getAuthenticationDetailsSource() {
    // TODO Auto-generated method stub
    return new AuthenticationDetailsSource<HttpServletRequest, JwtAuthenticationDetails>() {

        @Override
        public JwtAuthenticationDetails buildDetails(HttpServletRequest context) {
            return new JwtAuthenticationDetails(context);

        }

    } ;
}

Finally,

in my RestAuthenticationProvider , I can store the token in the authentication details:

@Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
        throws AuthenticationException {

    String password = (String) authentication.getCredentials();
    String token = getToken(authentication.getName(), password);

    JwtAuthenticationDetails details = (JwtAuthenticationDetails) authentication.getDetails() ;
    details.setToken(token);
    Collection<GrantedAuthority> authorities = getAuthorities(token);
    return new User(authentication.getName(), password, authorities);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM