Cannot access to unsecured get endpoint in Spring Boot - java

In my controller I have two endpoints where one has PreAuthorize annotation and the other does not:
#GetMapping
public UserResponse getUser(){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
//get and return User...
}
#PreAuthorize("hasRole('ROLE_ADMIN')")
#PostMapping
public UserResponse createUser(#RequestBody UserRequestDetails userDetails){...}
Secured endpoint its ok, works only when I am logged and token with right role is placed in request header. But when I want access to endpoint without PreAuthorize annotation I always got status 403 forbidden. I want access to the getUser endpoint when users are logged and regardless of the possible roles they have.
Here is my security configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception{
http.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(getAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtAuthorizationFilterBean(), UsernamePasswordAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().cors();
}
}
thank!

Related

Spring security on endpoint using Cognito IAM role?

I am trying to restrict specific endpoints on a Spring boot service depending on what role they have set in the OAuth2 credentials.
This is the endpoint
#RestController
#RequestMapping("/api/admin")
public class AdminController {
#GetMapping(produces = "application/json")
public TestResponse get() {
return new TestResponse("Admin API Response");
}
}
This is then secured using SecurityConfiguration bean
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.and()
.authorizeRequests()
.antMatchers("/login", "/", "/home", "/logout", "/ping").permitAll()
.antMatchers("/api/admin").hasRole("arn:aws:iam::xxxxxx:role/spring-sso-test-ADMIN")
.antMatchers("/api/user").hasRole("arn:aws:iam::xxxxxx:role/spring-sso-test-USER")
.and()
.oauth2Login()
.and()
.logout()
.logoutSuccessUrl("/logout");
}
}
I debugged the Principal and can see the correct IAM role in the list of attributes cognito:roles list
However when I hit the endpoint I get a HTTP 403 Unauthorized. Meaning that the user has authenticated successfully, but Spring does not recognize or understand the attributes or how to map them?
I tried using the #Secured annotation but that didn't change anything.
#Secured("arn:aws:iam::xxxxxx:role/spring-sso-test-ADMIN")
#GetMapping(produces = "application/json")
public TestResponse get() {
return new TestResponse("Admin API Response");
}
How do I allow this to work using an IAM role defined in AWS Cognito?
When you use the hasRole DSL method, Spring Security adds the ROLE_ prefix to your authority. So, the authority arn:aws:iam::xxxxxx:role/spring-sso-test-ADMIN will become ROLE_arn:aws:iam::xxxxxx:role/spring-sso-test-ADMIN.
You should use the hasAuthority method instead.
Additionally, you should take the cognito:roles from the attributes and add in the authorities, since it's the property that Spring Security will query to get the authorities.
To map the authorities you can use a OAuth2UserService:
#Bean
SecurityFilterChain app(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.oidcUserService(this.oidcUserService())
...
)
);
return http.build();
}
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
// your custom implementation
}
More details in the documentation.

How to deny all requests without specific roles - SpringBoot security config

I have the following SpringBoot web security configuration.
For authorization, I want to automatically forbid all requests that authentication does not include the roles ADMIN, SUPER_ADMIN, CUSTOMER but this denies all requests and only picks up the denyAll attribute in the springExprFilter hence it votes to deny access.
What am I missing from my configuration?
#EnableWebSecurity
#RequiredArgsConstructor
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final MemberDetailsService memberDetailsService;
private final JwtRequestFilter jwtRequestFilter;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(memberDetailsService).passwordEncoder(getPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers()
.disable()
.csrf()
.disable()
.authorizeRequests()
// permit all request for authentication
.antMatchers("/v1/authenticate")
.permitAll()
.and()
.authorizeRequests()
// permit all request with the following list of roles
// methods will enforce their own authorization logic
.antMatchers("/v1/members/")
.hasAnyAuthority("ADMIN", "CUSTOMER", "SUPER_ADMIN")
.and()
.authorizeRequests()
.anyRequest()
.denyAll()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
I figured out the spring security configuration expression was correct. The issue was that the antmatcher .antMatchers("/v1/members/") was incorrect. It was implying to match a request with the path /v1/members/ which was not the intended functionality.
For any interested party, the request I was making was GET v1/members/:uuid.
I ought to have used a wildcard .antMatchers("/v1/members/**") to catch all request for the member endpoint.

spring boot oauth2 feign allow anonymous requests

I have a client service that distribute a single page application. All the requests from the single page app pass through the client service that uses proxies (Feign) to redirect the calls.
I'd like to allow anonymous calls but I'm not able to do that with my current configuration.
So to make it simpler I have three services : a client, an oauth2 server and an oauth2 resource server.
The oauth2 server is also a resource server.
The client is connected to the oauth2-server with this configuration
security:
oauth2:
client:
clientId: autorisation_code_client
clientSecret: *******
accessTokenUri: https://localhost:****/oauth2-server/oauth/token
userAuthorizationUri: https://localhost:****/oauth2-server/oauth/authorize
#tokenCheckUri: https://localhost:****/oauth2-server/oauth/check_token
resource:
userInfoUri: https://localhost:****/oauth2-server/me
Here is the WebSecurityConfigurerAdapter class of the client, when an user try to access to the login path he's redirected to the oauth2-server to authenticate himself.
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers(
"/",
"/index.html",
"/login**",
"/logout**",
//resources
"/assets/**",
"/static/**",
"/*.ico",
"/*.js",
"/*.json").permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), SessionManagementFilter.class);
}
The feign proxy used by the client, I'd like to configure the oauth2-server/user/like/*** path to be accessible by anonymous users.
#RestController
#FeignClient(name = "oauth2-server", url = "https://localhost:****")
public interface ProxyOauth2Server {
#GetMapping(value = "oauth2-server/user/like/{name}")
ResponseEntity<?> getUserLikeName(#PathVariable("name") String name);
}
To transmit the token through Feign I have this configuration in the client Main class.
#EnableConfigurationProperties
#SpringBootApplication
#EnableFeignClients("com.tutosharing.client.proxies")
public class ClientUiApplication {
#Autowired
private SecurityPropertiesConfig config;
#Bean
protected OAuth2ProtectedResourceDetails resource() {
AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
resource.setAccessTokenUri(config.getAccessTokenUri());
resource.setUserAuthorizationUri(config.getUserAuthorizationUri());
resource.setClientId(config.getClientId());
resource.setClientSecret(config.getClientSecret());
return resource;
}
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails resource) {
return new OAuth2FeignRequestInterceptor(oauth2ClientContext, resource);
}
}
Now the oauth2 server which also serves as a resource server
#SpringBootApplication
#EnableResourceServer
#EnableAuthorizationServer
#EnableConfigurationProperties
public class AuthorizationServerApplication {}
the oauth2 server WebSecurityConfigurerAdapter class
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/",
"/login",
"/login.do",
"/oauth/authorize**")
.and()
.authorizeRequests()
.antMatchers(
"/",
"/login",
"/login.do")
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login.do")
.usernameParameter("*********")
.passwordParameter("*********")
.and()
.userDetailsService(userDetailsServiceBean())
.requiresChannel()
.anyRequest()
.requiresSecure();
}
}
The Rest controler method I'd like to allow to anonymous users
#RestController
public class UserRControllerRest {
#GetMapping({"/user/like/{name}"})
#JsonView(View.SimpleUser.class)
#PreAuthorize("hasRole('ROLE_USER')")
public ResponseEntity getUserLikeName(#PathVariable String name) {
Set<AuthUser> users = this.userRepository.findByNameLike(name);
return new ResponseEntity(users, HttpStatus.OK);
}
}
If I configure the Rest method with #PreAuthorize("hasRole('ROLE_ANONYMOUS')")
and the WebSecurityConfigurerAdapter like this
http.requestMatchers()
.antMatchers(
...
"/user/like/**",
...)
.and()
.authorizeRequests()
.antMatchers("/user/like/**")
.anonymous()
...
}
} // #formatter:on
I'm able to get an answer if I contact directly the oauth2-server with Postman, but not if I pass through the client service that uses Feign, I'm always redirected to the login page.
So how can I allow anonymous request Through Feign ?
I've found a solution but I'm not sure this is the Best way. So if you have another solution you are welwome.
So far I used this configuration to get the Token from the oauth2-server anytime an user made a request from the client through Feign.
#Bean
protected OAuth2ProtectedResourceDetails resource() {
AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
resource.setAccessTokenUri(config.getAccessTokenUri());
resource.setUserAuthorizationUri(config.getUserAuthorizationUri());
resource.setClientId(config.getClientId());
resource.setClientSecret(config.getClientSecret());
return resource;
}
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor(#Qualifier("oauth2ClientContext") OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails resource) {
return new OAuth2FeignRequestInterceptor(oauth2ClientContext, resource);
}
The problem with that configuration is that anytime I made a request with Feign a request is sent to the oauth2-client to the /oauth/authorize endpoint. But if the user is not connected it fails, so an unauthenticated user cannot make any request from the client service.
So I used another RequestInterceptor.
#Bean
public RequestInterceptor requestTokenBearerInterceptor() {
return requestTemplate -> {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (!principal.equals("anonymousUser")) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)
SecurityContextHolder.getContext().getAuthentication().getDetails();
requestTemplate.header("Authorization", "bearer " + details.getTokenValue());
}
};
}
This way the token that the client service already has, once the user is connected, is added to the request whitout making another request to the /oauth/authorize endpoint. I think the token is sent with every request, I don't think it's a good practice for security matters.
Also in the WebSecurityConfigurerAdapter classes of the client-server I need to add the path so that it is accessible to non-connected users
http.antMatcher("/**")
.authorizeRequests()
.antMatchers(
"/oauth2-server/user/like/**",
...)
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
...;
same for the oauth2-server
http.antMatcher("/**")
.authorizeRequests()
.antMatchers(
"/user/like/**",
...)
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
...;
With that configuration an unauthenticated user can make a request to an unprotected endpoint.

spring security HTTP Status 403 - Access Denied

Login is success but spring security blocking url even i given access to USER . How can i manage this thing?
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("sahil").password("123")
.roles("ADMIN","USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/welcome","/inventory/**","/sales/**").access("hasRole('USER')")
.and()
.csrf().disable();
}
LoginController.java
#Controller
public class LoginController {
#RequestMapping(value = { "/", "/login" }, method = RequestMethod.GET)
public String showLoginPage() {
return "login";
}
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String handleUserLogin(ModelMap model, #RequestParam String name, #RequestParam String password) {
if (!service.validateUser(name, password)) {
model.put("errorMsg", "Invalid Credential");
return "login";
}
System.out.println("principal : " + getLoggedInUserName());
model.put("name", name);
model.put("password", password);
return "welcome";
}
private String getLoggedInUserName() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
System.out.println("in if");
return ((UserDetails)principal).getUsername();
} else {
System.out.println("in else");
return principal.toString();
}
}
#RequestMapping(value = "/welcome", method = RequestMethod.GET)
public String showWelcomeDashboard() {
return "welcome";
}
}
1 . Once Login success page redirected to welcome page but url is still localhost:8080/login instead of localhost:8080/welcome.
2. After redirecting to URL localhost:8080/sales is it 403 Access denied.
What is spring security
Spring security is all about authentication and authorization, in your case you are missing authentication. There is no configuration of authentication in your security configuration. What you are missing is authentication filter for your spring security. Spring security provides default authentication filter UsernamePasswordAuthenticationFilter that can be configured by .formLogin(). You can use default provided or you can define your own custom authentication filter(Implementation of UsernamePasswordAuthenticationFilter).
Once authentication is success spring security will grant authorities for authenticated user. If authentication is configured correctly, below configuration is responsible for authentication and granting authority
auth.inMemoryAuthentication().withUser("sahil").password("123")
.roles("ADMIN","USER");
Authenticated users each request will be passed through filter FilterSecurityInterceptor and it will verifies authority granted for authenticated user with authorization configured for resources as given in below code.
.antMatchers("/welcome","/inventory/**","/sales/**").access("hasRole('USER')")
You missed all this by not configuring authentication filter.
Now for making it simple use.formLogin() in your http configuration.
#Override
protected void configure(final HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.antMatchers("/welcome","/inventory/**","/sales/**").access("hasRole('USER')")
.and().exceptionHandling()
.accessDeniedPage("/403")
.and().formLogin()
.and().logout()
.logoutSuccessUrl("/login?logout=true")
.invalidateHttpSession(true)
.and()
.csrf()
.disable();
}
.formLogin() without any configuration provides default login page with username and password default form parameters.And after authentication it redirects to "/" If you want to provide your custom login page then use below configuration.
.and().formLogin()
.loginPage("/login")
.usernameParameter("email").passwordParameter("password")
.defaultSuccessUrl("/app/user/dashboard")
.failureUrl("/login?error=true")
.loginPage("") - Your custom login page URL
.usernameParameter("").passwordParameter("") - Your custom login form parameters
.defaultSuccessUrl("") - Page url after successful authentication
.failureUrl("") - Page url after authentication failure
Note: You should not use "/login" POST method in your controller, Even though if you write, it will not be reached from spring security filter chain. As your configuration was wrong before, it was reaching before! Now you remove those from your controller and use conventional approach as mentioned above.

spring security login 404

im exploring a little of spring.
i got across spring boot for easy endpoints see:
#Controller
#EnableAutoConfiguration
public class SampleController {
#RequestMapping("/sample")
#ResponseBody
String sample() {
return "Hello sample!";
}
#RequestMapping("/sample2")
#ResponseBody
String sample2() {
return "Hello sample secured!";
}
}
logically the endpoints are accessible on localhost:8181/sample
but on using spring security the "protected" endpoint becames unaccessible because the login page gives me 404
my security class is as follows:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/sample" ).permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Bean
#Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
i am able to access /sample as is not protected. but unable to access /sample2 as it redirects to /login
im configuring my security class according to this guide: https://spring.io/guides/gs/securing-web/
I am able to access /sample as is not protected. But unable to access
/sample2 as it redirects to /login
Because you have not by-passed /sample2 in your security configuration.
.antMatchers("/sample2" ).permitAll()
Another thing is that as you have specified custom login page
.formLogin()
.loginPage("/login")
you have to provide a login page.
Inject userDetailsService into authenticationProvider:
#Bean
public AuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider authenticationProvider=new CustomAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
return authenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.authenticationProvider(authenticationProvider());
}
Add this configuration to spring security:
.antMatchers("/sample2").hasRole("USER")

Categories