Spring Boot AuthenticationToken with path variable - java

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).

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()
...

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:
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.

Why spring makes it so hard to use additional login parameters with spring security

In a spring mvc application i need to capture an additional 'location' parameter on login screen and use it for authentication in addition to username. I came across few approaches suggested to achieve this but none of it is straight forward and involves extending and/or implementing number of spring classes and interfaces. I somehow achieved it by extending UsernamePasswordAuthenticationFilter and by retrieving and putting location parameter in session.
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
final Long locationId = Long.parseLong(request.getParameter("locations"));
request.getSession().setAttribute("LOCATION_ID", locationId);
return super.attemptAuthentication(request, response);
}
}
This approach seems like a hack and doesn't seem too elegant. On top of that, now since i am using a custom AuthenticationFilter i have to manually configure this filter by injecting number of dependencies.
#Bean
public CustomAuthenticationFilter customAuthenticationFilter () {
CustomAuthenticationFilter filter= new CustomAuthenticationFilter();
filter.setRequiresAuthenticationRequestMatcher(
new AntPathRequestMatcher("/login","POST"));
filter.setAuthenticationManager(authenticationManagerBean());
filter.setUsernameParameter("username");
filter.setPasswordParameter("password");
filter.setAuthenticationSuccessHandler(simpleUrlAuthenticationSuccessHandler());
filter.setAuthenticationFailureHandler(simpleUrlAuthenticationFailureHandler());
filter.setRememberMeServices(persistentTokenBasedRememberMeServices());
return filter;
}
Eventually, capturing location and using it in authentication works but then it creates new issues if i want to use remember-me feature. I explained the problem here Spring remember-me with extra login parameter.
Using extra login parameters must be a common requirement. Spring framework known for being pluggable and extensible i wish there was a more user friendly way to use additional parameters.
Can anyone please suggest me a better approach here to use extra parameter and get remember-me working as well. Thanks
The classes of spring security are really complicated.
Use AbstractPreAuthenticatedProcessingFilter as your superclass.
Than you can return an Object instead of a String in
getPreAuthenticatedPrincipal
getPreAuthenticatedCredentials
there you read whatever you need from the HttpRequest and return an object that contains all the data you need (as you need it as principal or credential wherever location belongs.). Maybe you create a simple class for the return type.
Than you create a subclass of org.springframework.security.authentication.AuthenticationProvider
.In the method public Authentication authenticate(Authentication authentication)
you can access the objects you returned in AbstractPreAuthenticatedProcessingFilter using authentication.getCredentials() and authentication.getPrincipal()
with this you create one or more new SimpleGrantedAuthority(role), one for each role the user has.Collect them in a List grantedAuthorities;
And finally create a
Authentication auth=new new UsernamePasswordAuthenticationToken(authentication.getPrincipal(),authentication.getCredentials(),grantedAuthorities)
which you return.

Spring Security: OAuth2 custom filter to validate password expiration error handling

I've been trying to implement OAuth2 password expiration filter and I'm unsure about what the proper way would be to do so. The idea is as follows:
User tries to login.
User gets response with a header containing token if the password is expired.
User get's redirected to password change page using that token (i.e. /password-change/{token}).
He submits his old and new passwords, it gets changed.
Some rest controller retrieves user id by that token and does the rest password changing logic.
User should be redirected back to the initial login page where he logins with his new password (if he would be logged in instantly after the password change, he could navigate through secured pages even if the password would not be changed in background due to some exception, etc.).
So... I set a custom flag in user details for password expiration because I can't use credentialsNonExpired as it gets validated in DaoAuthenticationProvider and thrown as an exception which gets processed as InvalidGrantException which doesn't give me much control. I've figured out that in order to access user details right after it's authentication my filter should be in the inner Spring Security filter chain placed after OAuth2AuthenticationProcessingFilter:
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
...
http.addFilterAfter(new PasswordExpirationFilter(), BasicAuthenticationFilter.class
}
}
Why does my filter get placed after OAuth2AuthenticationProcessingFilter while there's no BasicAuthenticationFilter in the chain? I've digged through Spring Security and OAuth2 documentation and sources and couldn't find the right answer.
If that user's password is expired my filter generates some random string and it saves it to retrieve user details later during the password change request (at least it should be):
public class PasswordExpirationFilter extends OncePerRequestFilter implements Filter, InitializingBean {
private static final String TOKEN_HEADER = ...;
private ExpiredPasswordRepository repo; // gets set in a constructor and is basically holding a concurrent map of tokens
...
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
UserDetails details = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (details.isPasswordExpired) {
String uuid = UUID.randomUUID().toString();
repo.push(uuid, details.getId());
SecurityContextHolder.clearContext();
SecurityContextHolder.getContext().setAuthentication(null);
request.getSession(false).invalidate(); // don't create a new session
response.addHeader(TOKEN_HEADER, uuid);
response.sendError(HttpStatus.SC_PRECONDITION_FAILED, "Credentials have expired");
} else {
filterChain.doFilter(request, response);
}
}
}
Do I have to revoke the OAuth token as well? It gets reused in later requests and I keep getting the last userDetails object and therefore I keep getting the same response from my filter.
Is it even the right place to do all this validation? How should one validate the password for the concrete user and not the OAuth client?
Ok, I think I resolved this issue by revoking the access token via injected TokenStore in my filter (I used BearerTokenExtractor to get the token value) which seems pretty logical in this situtation. I still had no time to figure out, why my filter gets placed after OAuth2AuthenticationProcessingFilter, though.

Cleanest way to call multiple AuthenticationSuccessHandlers?

I have a java webapp using Spring 3.1. My Spring security context defines multiple authentication filters, each corresponding to a different authentication path (e.g. username/password vs. Single Sign On). Each auth filter defines its own AuthenticationSuccessHandler. Now, I want to inject 2 additional actions to take upon successful authentication, and they should apply across all authentication types:
set a tracking event code for Google Analytics to use on the front-end
update the user's preferred locale in our database
These could be any actions that you want a hook for, after the user has been successfully authenticated. The important point is that, unlike the regular AuthenticationSuccessHandlers (which are different for each authentication path), they don't forward or redirect the request. So it's safe to call a bunch of them.
Is there a clean way to integrate these additional authentication success "actions", using Spring Web/Security 3.1?
I looked into implementing an ApplicationListener<AuthenticationSuccessEvent>, but my events need to access the request, and all AuthenticationSuccessEvent provides is the Authentication object itself.
I couldn't find a way, so I decided to roll my own proxy:
public class AuthenticationSuccessHandlerProxy implements AuthenticationSuccessHandler {
private List<AuthenticationSuccessHandler> authenticationSuccessHandlers;
public AuthenticationSuccessHandlerProxy(List<AuthenticationSuccessHandler> successHandlers) {
this.authenticationSuccessHandlers = successHandlers;
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
for (AuthenticationSuccessHandler successHandler : this.authenticationSuccessHandlers) {
successHandler.onAuthenticationSuccess(request, response, authentication);
}
}
}
After looking breafly into the source code of AbstractAuthenticationProcessingFilter and all other places where AuthenticationSuccessHandler.onAuthenticationSuccess(...) is called I do not see any possibility to do it using Spring Security.
As a workaround you can try to wrap your success handlers into some AspectJ or AOP pointcut and then apply this pointcut to AuthenticationSuccessHandler.onAuthenticationSuccess(...) execution. Maybe like this you can target all authentication types.

Categories

Resources