I try to modify existing example - Tonr2 and Sparklr2.
Also I viewed this tutorial based on Spring Boot Spring Boot OAuth2. I try to build application like in Tonr2 example but without first login (on tonr2). I just need one Authentication on Sparklr2 side. I do this:
#Bean
public OAuth2ProtectedResourceDetails sparklr() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setId("sparklr/tonr");
details.setClientId("tonr");
details.setTokenName("oauth_token");
details.setClientSecret("secret");
details.setAccessTokenUri(accessTokenUri);
details.setUserAuthorizationUri(userAuthorizationUri);
details.setScope(Arrays.asList("openid"));
details.setGrantType("client_credentials");
details.setAuthenticationScheme(AuthenticationScheme.none);
details.setClientAuthenticationScheme(AuthenticationScheme.none);
return details;
}
But I have Authentication is required to obtain an access token (anonymous not allowed) . I checked this question. Of course, my user is anonymous - I want to login on Sparklr2. Also, I tried different combinations of settings of this bean, but nothing good. How to fix it? How to make it work as I want?
Almost two years late for the post.
The exception is thrown from AccessTokenProviderChain
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth instanceof AnonymousAuthenticationToken) {
if (!resource.isClientOnly()) {
throw new InsufficientAuthenticationException(
"Authentication is required to obtain an access token (anonymous not allowed)");
}
}
You either
Use ClientCredentialsResourceDetails in your OAuth2RestTemplate, or
Authenticate the user before using AuthorizationCodeResourceDetails to access external resources
In fact, in the tonr2 and sparklr2 example (I personally find the name very confusing), to access resources on sparklr2, a user has to first authenticate on tonr2. As seen in oauth2/tonr:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("marissa").password("wombat").roles("USER").and().withUser("sam")
.password("kangaroo").roles("USER");
}
If your user is anonymous, you might want to check for Single Sign On.
For whoever just want to quickly try out Oauth2 integration, add basic auth to your application:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated().and().httpBasic();
}
application.properties:
spring.security.user.password=password
spring.security.user.name=user
Don't forget to add spring-boot-starter-security to your project.
e.g. In gradle: compile 'org.springframework.boot:spring-boot-starter-security'
Or you can also disable AnonymousAuthenticationToken from creating by:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.anonymous().disable();
}
Old post...
The exception indeed is thrown form AccessTokenProviderChain but it happens when spring security filters invoking if incorrect order. Make sure that your OpenIdAuthenticationFilter is invoking after OAuth2ClientContextFilter.
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()
...
This question already has answers here:
Springboot Security hasRole not working
(3 answers)
Closed 1 year ago.
I am trying to learn Spring Security and want to secure an API. I want a login form with database authentication on the one hand, and on the other hand a OAuth2 authentication.
But I am stuck at the first task. The problem is that the application doesn't accept my roles and I have no idea why.
Here's my Config class:
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("SELECT username,password,enabled FROM users WHERE username=?")
.authoritiesByUsernameQuery("SELECT username, authority FROM authorities WHERE username=?");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/createTrack").hasRole("CREATOR")
.antMatchers("/getTrack").hasAnyRole("CREATOR", "USER")
.antMatchers("/test").hasRole("CREATOR")
.antMatchers("/all").permitAll()
.and().formLogin();
}
#Bean
public PasswordEncoder getPasswordEncoder() {
// TODO: Change this to hashed password (this is for demo purposes)
return NoOpPasswordEncoder.getInstance();
}
}
Everything is made like descriped in the Spring documentation: https://docs.spring.io/spring-security/site/docs/current/reference/html5/#servlet-authentication-jdbc
I worked out a few endpoints of my API to test the logged in user's name and role. Everything works fine - the login seems to work great, also the role is correct.
But when I try to call an endpoint that's only accessible for one or both specific roles, it gets me a 403 error response.
What am I doing wrong? I tried a lot of things and it still does not work.
Thanks!
PS: I am using MyBatis (task from my company) - if that matters in any way?
Thank you guys for your help!
Toerktumlare's answer brought me to the solution. I turned on Debug Logging as you suggested. CORS was not the problem, but reading the debug messages brought me on the right way.
The problem was minor though. I stored the roles in the database as "CREATOR" or "USER". Debug messages showed me that Spring was looking for "ROLE_CREATOR" or "ROLE_USER" and - because my roles weren't saved this way - didn't find them. Hence I got a 403 HTTP response.
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.
I'm setting up my Spring Security (v4.0.1) web application. I want to have two authentication providers, an "in-memory" one to manage the administrator account and a custom one which refers to my own implementation. The system should attempt the authentication against the "in-memory" provider first of all and against the custom one in second place. My code looks like this:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth,
AuthenticationProvider provider) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("s3cr3t")
.authorities("ADMIN");
auth.authenticationProvider(provider);
}
However, this code leads the framework to try my custom implementation first. It makes a bit of sense, since the AuthenticationManagerBuilder#authenticationProvider method adds a Provider to the internal List while the AuthenticationManagerBuilder#inMemoryAuthentication one configures it internally. How could I manage to get it work?
You can create your InMemoryUserDetailsManagerConfigurer manually and tell it to configure itself on the AuthenticationManagerBuilder when you have finished configuring it so it installs it's AuthenticationProvider before your custom one:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth,
AuthenticationProvider provider) throws Exception {
inMemoryConfigurer()
.withUser("admin")
.password("s3cr3t")
.authorities("ADMIN")
.and()
.configure(auth);
auth.authenticationProvider(provider);
}
private InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder>
inMemoryConfigurer() {
return new InMemoryUserDetailsManagerConfigurer<>();
}
Normally what happens is that the InMemoryUserDetailsManagerConfigurer is created and added to the list of configurers that should be applied when the AuthenticationManager is built - which is after you've installed your custom AuthenticationProvider.
More or less from spring.io Documentation
If you are using XML configuration (e.g. spring-security.xml):
<security:authentication-manager>
<security:authentication-provider ref="FirstProvider" />
<security:authentication-provider ref="SecondProvider" />
</security:authentication-manager>
(I am using that setup for one of Spring's built-in authentication provider next to a custom one, works fine)
If you are using Java Config, I can only reference some other person's post maclema on Java config for multiple authentication provider, since I have never (successfully) tried code config
Today we tend to configure Spring Security via WebSecurityConfigurerAdapter, you register filters that extract credentials from a request and register providers that use tokens built by filters by populating Authentication object with other details like authorities:
#Override
protected void configure(HttpSecurity http) throws Exception {
AbstractPreAuthenticatedProcessingFilter tokenPreAuthProcessingFilter = new RequestHeaderAuthenticationFilter();
http.addFilterAfter(tokenPreAuthProcessingFilter, SecurityContextPersistenceFilter.class);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// First.
auth.authenticationProvider(new PreAuthenticatedAuthenticationProvider());
// Second.
auth.inMemoryAuthentication()
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser(...);
}
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();
}
}