Java Springboot Security - dealing with dependency injection - java

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:
org.springframework.boot:spring-boot-starter-security
io.jsonwebtoken:jjwt:0.7.0 (not sure if this one is actually necessary, the examples all included the token generation, which I don't do in this API)
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.

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.

Related

Can I mix both basic authentication and JWT token authentication to protect APIs of a single Spring Boot project?

I am pretty new in Spring Security and I am working on a Spring Boot project that uses Basic Authentication in order to protect some APIs. I am starting from an existing tutorial code (a Udemy course) trying to adapt it to my own use cases.
In this project I have this SecurityConfiguration used to configure the basic authentication.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
private static String REALM = "REAME";
private static final String[] USER_MATCHER = { "/api/utenti/cerca/**"};
private static final String[] ADMIN_MATCHER = { "/api/utenti/inserisci/**", "/api/utenti/elimina/**" };
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable()
.authorizeRequests()
.antMatchers(USER_MATCHER).hasAnyRole("USER")
.antMatchers(ADMIN_MATCHER).hasAnyRole("ADMIN")
.anyRequest().authenticated()
.and()
.httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint()).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public AuthEntryPoint getBasicAuthEntryPoint()
{
return new AuthEntryPoint();
}
/* To allow Pre-flight [OPTIONS] request from browser */
#Override
public void configure(WebSecurity web)
{
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
#Bean
public BCryptPasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
};
#Bean
#Override
public UserDetailsService userDetailsService()
{
UserBuilder users = User.builder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users
.username("ReadUser")
.password(new BCryptPasswordEncoder().encode("BimBumBam_2018"))
.roles("USER").build());
manager.createUser(users
.username("Admin")
.password(new BCryptPasswordEncoder().encode("MagicaBula_2018"))
.roles("USER", "ADMIN").build());
return manager;
}
}
So from what I have understand:
Here it id defined the list of API that can be accessed by a nornmal user and the list of API that can be accessed by and admin user:
private static final String[] USER_MATCHER = { "/api/utenti/cerca/**"};
private static final String[] ADMIN_MATCHER = { "/api/utenti/inserisci/**", "/api/utenti/elimina/**" };
Into the previous configure() method basically it is stating that the API URL matching with the USER_MATCHER are accessible by logged user having role USER while API having URL matching ADMIN_MATCHER are accessible by logged user having role ADMIN. Is this interpretation correct?
Finnally the UserDetailsService bean simply define two users: one belonging to the USER "group" and the other one belonging to both the USER and ADMIN "group".
So, if I well understood, the first one will be aple only to access to the API having enpoint URL /api/utenti/cerca/** while the second one will be able to access also to the APIs having endpoint URLs /api/utenti/inserisci/** and /api/utenti/elimina/**
Is it my reasoning correct?
And now my doubt: into a controller class of this project I defined this method:
#RestController
#RequestMapping("api/users")
#Log
public class UserController {
#Autowired
UserService userService;
//#Autowired
//private BCryptPasswordEncoder passwordEncoder;
//#Autowired
//private ResourceBundleMessageSource errMessage;
#GetMapping(value = "/test", produces = "application/json")
public ResponseEntity<String> getTest() throws NotFoundException {
log.info(String.format("****** getTest() START *******"));
return new ResponseEntity<String>("TEST", HttpStatus.OK);
}
..............................................................................................................
..............................................................................................................
..............................................................................................................
}
As you can see this method handling a GET request toward the localhost:8019/api/users/test endpoint.
This endpoint URL is not in any of the previous two list related the protected endpoint (it is not into the USER_MATCHER list neither into the ADMIN_MATCHER list. So I expected that simply this endpoint was not protected and accessible to everyone. But performing the previous request using PostMan, I obtain this error message:
HTTP Status 401 : Full authentication is required to access this resource
So basically it seems to me that also if this endpoint not belong to any protected endpoint list it is in some way protected anyway (it seems to me that at least the user must be authenticated (infact trying both the previous user I can obtain the expected output, so it should mean that the endpoint is not protected by the user rule but it is protected againts not authenticated access).
Why? Maybe it depende by the previous configure() method settings, in particular this line?
.anyRequest().authenticated()
In case is it possible to disable in some way to implement something like this:
If a called endpoint belong to one of the previous two lists (USER_MATCHER and ADMIN_MATCHER) --> the user must be authenticated and need to have the correct role.
If a called endpoint not belong to one of the previous lists --> everybody can access, also not authenticated user.
This approach make sense or am I loosing something?
I take this occasion to ask you also another information: do you think that it is possible to configure Spring security of this specific project in order to protect some specific endpoints using the basic authentication and some other specific endpoints using the JWT authentication.
Sone further notes to explain why this last question. This project is a microservice that at the moment is used by another microservice (used to generate JWT token) in order to obtain user information. (the other microservice call an API of this project in order to receive user information so it can generate a JWT token that will be used in my application. The comunication between these 2 microservice must use basic authentication).
Since this project contains all the entity classes used to map the tables related to the users on my DB, my idea was to use this project also for generic user management, so it could include functionality like: add a brand new user, changes information of an existing user, obtain the list of all the users, search a specific user, and so on.
These new APIs will be protected by JWT token because each API can be called from a specific user type having different privileges on the system.
So I am asking if in a situation like this I can add without problem 2 different types of authentication (basic authentication for the API that retrieve a user so the other microservice can obtain this info) and JWT authentication for all the other APIs. It make sense or is it better to create a brand new project for a new user management microservice?
So, if I well understood, the first one will be aple only to access to the API having enpoint URL /api/utenti/cerca/** while the second one will be able to access also to the APIs having endpoint URLs /api/utenti/inserisci/** and /api/utenti/elimina/**
Yes.
Why? Maybe it depende by the previous configure() method settings, in particular this line?
Yes, when using .anyRequest().authenticated(), any requests that have not been matched will have to be authenticated.
If a called endpoint not belong to one of the previous lists --> everybody can access, also not authenticated user.
You can achieve this by doing anyRequest().permitAll(). But this is not so secure because you are allowing access to every other endpoints, instead you should stay with anyRequest().authenticated() and allow access to specific endpoints manually, like so:
http
.authorizeRequests()
.antMatchers(USER_MATCHER).hasAnyRole("USER")
.antMatchers(ADMIN_MATCHER).hasAnyRole("ADMIN")
.antMatchers("/api/users/test").permitAll()
.anyRequest().authenticated()
...

Spring Boot AuthenticationToken with path variable

i have a PreAuthenticatedProcessingFilter with a custom AuthenticationManager where i do my authentication and create a AuthenticationToken. I now need to access a path variable (eg. id of "/foo/{id}") and use that for my authentication. How can i access the variable? If i use .antMatchers("/foo/{id}").access("#demo.check(authentication,#id)"); for example i cant create my own token.
my current code is:
MyAuthFilter filter = MyAuthFilter();
filter.setAuthenticationManager(new AuthenticationManager() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// ... authentication stuff
// here i want to access the path variable
return new MyAuthenticationToken(foo);
}
});
httpSecurity.antMatcher("/foo/**").csrf().disable().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilter(filter).authorizeRequests().anyRequest().authenticated();
Update
i am now checking everything inside the access expression (you can access the HttpServletRequest there and have the path variables as parameter). I did not want to have logic in the controller or check the raw path. So this works fine for me now:
httpSecurity.antMatcher("/foo/**").csrf().disable().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
.antMatchers("/foo/test/{testId}/**")
.access("#fooApiGuard.check(authentication, #testId)");
#Service
public class FooApiGuard {
#Autowired
private HttpServletRequest request;
public boolean check(Authentication authentication, Long testId) throws AuthenticationException {
// check stuff
return true;
}
}
Spring Security is built as a Filter chain, which means that inside your custom filter or AuthenticationManager you do not have quite the same context as inside the controller method itself. In fact, your custom filter is supposed to augment the context which will be used down the line by your controller.
What you do have access to is the ServletRequest and ServletResponse objects, so if you must you could extract the raw path from that. However, that doesn't give you the nicely separated out request parameter.
If the path parameter is only necessary to determine whether or not someone is authorized then you could simplify your authentication logic and then subsequently augment your controller with additional security checks to validate e.g. domain level security concerns (does the resource belong to the current user).

What is the purpose of AuthenticationManagerBuilder in Oauth2 resource server

So I have this application that only allow access to its resource when a valid oauth token is provided. I already have an #EnableResourceServer class that handles role based access to different endpoints. But what is the purpose of the block of code below? My app would not start unless I put in this generic block with some generic user name/password. The user/role/password here has no bearing on accessing the resource whatsoever. As long as the role in the oauth token matches the role specified in the #EnableResourcesServer, the caller will get proper response back from the app.
#EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.inMemoryAuthentication() // creating user in memory
.withUser("user")
.password("password").roles("USER")
.and().withUser("admin")
.password("password").authorities("ROLE_ADMIN");
}
}
I found the issue, it was because I separated the access of resources in 2 classes, an #EnableResourceServer and an #EnableWebsecurity. The issue is resolved after removing the #EnableWebSecurity class and moving all access control to #EnableResourceServer.

How to set default #AuthenticationPrincipal on startup

For development purpose, I am trying to configure a development profile for which developers don't need to be authenticated in the application to call REST services.
But, some of these services need an #AuthenticationPrincipal to work.
So I would like to be able to define a mocked #AuthenticationPrincipal on startup to be used by default.
Does anyone have any kind of idea to do so?
Currently the application behavior expected for the user authentication is:
A REST endpoint should send a HTTP code 401 if the user isn't authenticated.
In this case, the Front-end should redirect the user to the back-end URL /login so that he can authenticate itself.
On success, the back-end should then redirect the user to the front-end.
It turn out that it wasn't a good solution, here a list of the different reasons I can think of:
The behavior of the back-end server would be different between the development and the production environments.
This would force the front-end to also have different behaviors between these two environments.
JUnits wouldn't be able to test all expected answers (example: HTTP code 401 if not authenticated) from endpoints.
So in place, I have created a security configuration (enabled only when not using the production profile) emulating the expected behavior from the front-end point of view.
Here the MockAuthenticationSecurityConfiguration class:
#Configuration
#Profile("!PRODUCTION")
public class MockAuthenticationSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final ApplicationProperties applicationProperties;
public MockAuthenticationSecurityConfiguration(final ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("swagger-ui.html").permitAll();
// All API REST endpoint can only be accessed by an authenticated user.
http.authorizeRequests().antMatchers("/api/**").authenticated()
// For these REST endpoint to answer HTTP code 401 in place of redirecting the user to /login.
.and().exceptionHandling().defaultAuthenticationEntryPointFor(new Http401UnauthorizedEntryPoint(), new AntPathRequestMatcher("/api/**"))
// On success, we want to redirect the user to a specific URL (the frontend).
.and().formLogin().permitAll().successHandler(new SimpleUrlAuthenticationSuccessHandler(applicationProperties.getRedirectUrl()))
.and().logout().permitAll()
;
http.csrf().disable();
http.cors();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// Add a mocked user to be used to authenticate.
auth.inMemoryAuthentication().withUser(User.withDefaultPasswordEncoder().username("jdoe").password("jdoe").roles("USER"));
}
}
Here the Http401UnauthorizedEntryPoint class:
public class Http401UnauthorizedEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
}
}
And then the ApplicationProperties class:
#Getter
#Setter
#Component
#ConfigurationProperties("application")
public class ApplicationProperties {
/**
* The URL to which we should redirect the user once he is logged in the application.
*/
private String redirectUrl;
}

Using Spring Security with zero-legged Oauth 1

I am writing an LTI application using Spring boot. LTI applications are basically a plug-in for a learning management system (in this case Canvas) which work by sending an Oauth1 signed POST to my server. The result of this request is displayed to the user inside of an iframe. There is a pre-shared key and secret that the LMS uses to sign the request. If the signature on the POST checks out, I have an authenticated user. I have this part working, partially based on this question.
During the initial request (which comes to the URL "/launch") I can call SecurityContextHolder.getContext().getAuthentication() and use this without problems. My problem is when the user makes another request, say for a picture or by clicking on a link in my content, the SecurityContext object isn't following them. I'm pretty sure I'm not setting up the Spring security filter chain correctly so the SecurityContextPersistenceFilter isn't being hit on subsequent requests. At the end of the day, SecurityContextHolder.getContext().getAuthentication() returns null.
The OAuth signature verification happens in a WebSecurityConfigurerAdapter like so: (again, based on this)
#Configuration
public static class OAuthSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
//spring auto-wiring to set up the
//zeroLeggedOauthProviderProcessingFilter (see linked question)
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/launch")
.addFilterBefore(zeroLeggedOAuthProviderProcessingFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests().anyRequest().hasRole("OAUTH")
.and().csrf().disable();
}
}
So this works and creates an authenticated principal and everything. But due to that antMatcher, it only applies to the /launch path.
It seems like it should be simple to add another security configurer adapter that will ensure that all other paths in the application are protected by an authenticated session and in so doing would cause the SecurityContext associated with this user to become available but I have been unable to come up with the magic sauce. The documentation focuses more on standard login form based authentication setups. I'm also kind of new to Spring in general so I'm clearly missing something. I tried this but it causes all other requests to return a 403:
#Configuration
public static class SessionSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().hasRole("OAUTH")
.and().csrf().disable();
}
}

Categories

Resources