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;
}
Related
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()
...
I have Springboot Microservice app comprises( Discovery , Eureka Client , Zulu Proxy , Gateway )which is configured with OAUTH2 which is working fine.
and OAUTH2 is configured as the in memory token store. i have rest end points gateway exposed
ex :
localhost:8080/hello/gateway
now i have java batch , which will call micro service app gateway example (above api) to get the required response. since that is protected with OAUTH2 i cant access api directly.
is there a way to access the api without token or can we bypass the authorization logic by passing the hardcoded token from batch and validating in the Gateway
Tried to create a non-expiring token but since its in-memory token, it will not work after the api restart
Tired to create a custom filter and but it didnt work as expected . below is my resource server code.
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "resource_id";
#Autowired
private AppProperties appProperties;
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID).stateless(false);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.
anonymous().disable()
.authorizeRequests()
.antMatchers("/testService/**").authenticated()
.and()
//.addFilterBefore(new BatchCustomFilter(), BasicAuthenticationFilter.class)
.exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
Let me if know there is any good way of doing this , Suggestions are highly appreciated .
yes had overridden WebSecurityConfigurerAdapter configure method to ignore the specific endpoint.
#Override
public void configure(final WebSecurity web) throws Exception {
web.ignoring().antMatchers("/testService/testApi/**");
}
Now im able to access Api from batch after ignoring the endpoint from web security.
I'm having trouble with my security configuration and I need to create a list of the Ip's that are accessing my application.
My problem is to get the Ip in a dynamic way for all my application requests.
But I don't want to add a HttpServletRequest for all the requests of my application, what I want is a method that is called before every request, even before SecurityConfig events.
What I've tried to do is to use AuthenticationProvider to get the request HttpServletRequest and then get the Ip of my client. The problem is that I can't find a way to create a single class and it connects to all my requests. This is my code:
public abstract class IPAddressBasedAuthenticationProvider implements AuthenticationProvider {
/**
* Context http request
*/
#Autowired
private HttpServletRequest request;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String ipAddress = request.getRemoteAddr();
System.out.println(ipAddress);
return authentication;
}
}
What I was thinking was that my requests where going to pass in this code and then show my client Ip.
I tried autowiring it in my controller classes but my application doesn't execute then.
What can I do to make this work?
I've used a different way to get the addresses of my client requests.
I've created a class called WebConfig that implements WebMvcConfigurer. This class configures some usages in spring. and here's the code for this class:
#EnableWebMvc
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoggerInterceptor());
}
}
This method is registering an interceptor, this interceptor will intercept the requests and using methods you will create it will execute when some request is send. For exemple my code:
public class LoggerInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) {
System.out.println(request.getRemoteAddr());
return true;
}
}
In my case I've used "preHandle" method to get the Ip of my client request, this method is called every time someone calls my application via request and before spring handles the request my method is called, it can be used as a security configuration as well, because you can return false, in that case the request won't execute. But for that usage there's others ways with spring boot.
If I'm doing something wrong please correct me.
I'm creating a new Spring REST application with some basic services and entities.
I added Spring Security and without overriding any class, i just added to application.properties a user and password.
So far so good, i opened Postman to try out a endpoint and it always return 401 to my requests.
I tried in postman set the authorization via "Basic Auth" (is what header WWW-Authenticate asks), tried "Digest auth" using the "Realm" value from the header. But none of it works.
Here is what i have in my application.properties
spring.security.user.name=root
spring.security.user.password=root
This is my request
https://imgur.com/URM3TGD
(Sorry i can't embbed the image because of my reputation)
And here is the endpoint
#PostMapping("saveUsuario")
public Usuario saveUsuario(Usuario usuario) {
return usuarioRepository.save(usuario);
}
(If possible) i don't want to override any Spring Security class, just "use as it".
Thank you!
So here is what i found.
Thanks to #jzheaux we discover that the problem was with the csrf configuration (Using POST request).
So i was forced to override the class WebSecurityConfigurerAdapter to disable it.
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
}
But then, the endpoints could be called without authentication!
So, this is the final code:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.cors();
http.authorizeRequests().anyRequest().fullyAuthenticated();
http.httpBasic();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
}
}
First disable the CSRF.
Then enable Cors.
I set that i want any request to be fully authenticated
The challenge type is HTTP basic
I disable the creation of cookies so it'll always ask for credentials.
So far so good, it's working!
Per https://docs.spring.io/spring-boot/docs/1.5.0.RELEASE/reference/htmlsingle/#boot-features-security
you should change your password with
security.user.password=root
instead of spring.security.user.password=root
similar security properties that are overridable are in the #ConfigurationProperties class: SecurityProperties.java
See https://github.com/spring-projects/spring-boot/blob/v1.5.0.RELEASE/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/SecurityProperties.java
I'm taking my first try on the Spring Security and really got stuck on such a task: i have a default webpage, which should be defaulty non-authenticated, and i have a batch of controller calls, which i want to secure with a PreAuthorized annotation. The basic idea is, that i want to disable the default "redirect to login page", but still have the opportinity to operate the Spring Security's method security complex.
I'm using a java configuration, which looks like:
#Configuration
#EnableWebSecurity
public class SpringWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/res/**"); // #3
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().anyRequest().permitAll();
}
}
I know(or seem to understand) that by this point all of my calls should be permitted(have been sitting over this for the past two days, and clearly running out of ideas).
The Controller's method, which i want to secure, is:
#PreAuthorize("hasRole('ADMIN')")
#RequestMapping(value="/admin", method = RequestMethod.GET)
public String getAdminPage(Model model){
return "admin";
}
I know that i can use antMatcher to add "/**/admin" and authorize the calls to the specific url, but the general idea is:
Disable the "go to login page" on the root(and other random controller mappings).
Do a manual ajax based authentication from a ajax drop down(or something).
When a random non-autherised user bumps in a page, which has a #PreAuthorized on a Controller, then, and only then, he should be redirected.
UPD: the basic question is to invoke the redirect to login page only on access denied scenario, allowing the anonymous role for basic site view's and calls.
Answering my own question(maybe not as clean as it should look).
You can config the Spring Security Http Security so it wont ask to login on every page:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().anyRequest().permitAll();
To enable the method security(the PreAuthorize("hasRole('ADMIN')") and ect), you need to add an annotation:
#EnableGlobalMethodSecurity(prePostEnabled = true)
after which you need to add to the HttpSecurity object something to catch the exceptions of "Access Denied and ect" (found this on some other stackoverflow question thread):
http.exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPoint() {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
if (authException != null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().print("Unauthorizated....");
}
}
});
And now you can secure your controller and other components with #PreAutherized. Hope this will help someone.
But there still remains one thing - when the user is unauthorized and i try to reach some preAuthorized page, the above mentioned exception handler is invoked, returning the "Unauthorized..." message. But when the user is authorized and i try a page with a diffirent preAuthorized role, i get the default 403 Access Denied.