GeleiaDeMocoto
GeleiaDeMocoto

Reputation: 161

Java Springboot Security - dealing with dependency injection

I'm working on a Java Springboot REST API. In order to access the endpoints, users must send a request to an external Identity Server service, which will return a token. That token will then be sent in the header Authorization to this API, which will check if the user is in the database before allowing the request to go through to the controller.

I'm a bit new to Java so I used some examples on the internet on how to make this happen. I reached a point where the request comes in, gets filtered, and then I can allow it to go though or not. Now I need to add the part where I check the database to see if the user is there. I'm having some problems with this.

I added the following packages to gradle:

Here is the code I have implemented inside the security package. This is before trying to add the database integration, so it runs and works:

WebSecurityConfig.java:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf().disable().authorizeRequests()
        .antMatchers(httpMethod.GET, "/user").authenticated()
        .and()
        .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    // this following one might not be necessary, it was in the example but I don't think it's being used
    @Override 
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("admin")
            .password("password")
            .roles("ADMIN");

    }
}

JWTAuthenticationFilter.java:

public class JWTAuthenticationFilter extends GenericFilterBean {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
                
                Authentication authentication = TokenAuthenticationService.getAuthentication((HttpServletRequest) request);
                SecurityContextHolder.getContext().setAuthentication(authentication);
                filterChain.doFilter(request, response);
            }
}

TokenAuthenticationService.java:

public class TokenAuthenticationService {

    static final String SECRET = "mySecret";
    static final String TOKEN_PREFIX = "Bearer";
    static final String HEADER_STRING = "Authorization";

    static Authentication getAuthentication(httpServletRequest request) {
        String token = request.getHeader(HEADER_STRING);
        
        // do a bunch of stuff with the token to get the user Identity

        if (userID != null) {

            // here I need to call a method from a class in gateway, to find this user in the database..
            // if not found I'll return null
            
            return new UsernamePasswordAuthenticationToken(userID, null, Collections.emptyList());
        }

        return null;
    }


}

So that's the code that's working right now. However, I can't call external method from inside getAuthentication because it's static, so in order to call the gateway method, I made it not-static. Because I made it not-static, I had to change the way I called it in JWTAuthenticationFilter. Instead of calling the method directly I had to add the line:

TokenAuthenticationService tokenAuthenticationService = new TokenAuthenticationService();

and then call getAuthentication using tokenAuthenticationService.

After that I tried to call the method userGateway.getByUserID directly. But I need an instant of UserGateway for that. I can't initialize an instance of UserGatewayImplementation directly. Not only is it against the principles of dependency injection that we follow in this project, it would also require initializing something else that is used by that class. That something else also requires another object and so on.

So I added the annotation @RequiredArgsConstructor to the class, and gave it the following:

private final UserGateway userGateway;

so that I could call this.userGateway.getByUserID(userID).

However, because I had to create an instance of TokenAuthenticationService (because the method isn't static anymore), and I added an attribute to TokenAuthenticationService (userGateway), it wants me to pass an instance of UserGateway to the constructor when I create tokenAuthenticationService in JWTAuthenticationFilter.

Just like before, I can't do that. So I added @RequiredArgsConstructor to the class JWTAuthenticationFilter, and gave it this attribute:

private final TokenAuthenticationService tokenAuthenticationService;

so that I could use it to call getAuthentication.

This of course led to the same problem in WebSecurityConfig. In this line:

.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

It creates an instance of JWTAuthenticationFilter, but now it wants me to pass an instance of TokenAuthenticationService to it, because it has that attribute. So I did the same thing, added @RequiredArgsConstructor to the WebSecurityConfig class, and gave it this attribute:

private final JWTAuthenticationFilter jwtAuthenticationFilter;

and then passed this jwtAuthenticationFilter to addFilterBefore.

Doing all of this made the editor stop complaining, but when I try to run the application, it gives me the following error:

***************************
APPLICATION FAILED TO START
***************************
 
Description:
 
Parameter 0 of constructor in (path).security.WebSecurityConfig required a bean of type '(path).security.JWTAuthenticationFilter' that could not be found.
 

Action:
 
Consider defining a bean of type '(path).security.JWTAuthenticationFilter' in your configuration. 

I googled this error and tried to add @Bean to JWTAuthenticationFilter, to doFilter, etc, but it didn't work and I'm not surprised, because I was doing it blindly. I'd appreciate any help with this, even if it's brief. At the end of the day, I just want to be able to call a method from another class in getAuthentication, to check the database and see if the user is there. I obviously need to learn more about Java and Springboot, but unfortunately I'm in a hurry to make this work.

Upvotes: 0

Views: 581

Answers (1)

GeleiaDeMocoto
GeleiaDeMocoto

Reputation: 161

After trying a bunch of things blindly, I ended up finding the answer by myself. I just had to add the annotation @Component to the classes JWTAuthenticationFilter and TokenAuthenticationService. Can't quite explain it at this point, but I'll leave it here in case anyone else ever needs it.

Upvotes: 1

Related Questions