When I hit Cloud server Springboot Rest url, response is (type=Unauthorized, status=401) - spring

I have Angualr8 frontend and Springboot backend code, when I am trying to hit the local URL with parm values it works fine but when I am trying to hit the same in prod it gives the below error am not sure is this for the token missing issue.
working URL: http://localhost:8080/apiservice/forgotPasswordEmail?userId=userid
PROD cloud not working URL: http://prod-host/apiservice/forgotPasswordEmail?userId=userid
can you please help me in this case?
CONTROLLER:
#Controller
#ComponentScan(basePackages = {""}) //having my structure
#RequestMapping(value = "/apiservice")
public class LoeRedirectController
{
#RequestMapping(value = "/forgotPasswordEmail", method = {RequestMethod.GET})
public String redirectForgotPassword(#RequestParam String userId) {
String url="http://localhost:8080/index.html"; //for prod it will prod url
if(null !=url) {
// url=url+"?userId="+userId;
}
return "redirect:"+url;
}
SS:
web config file:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().permitAll();
}
}

when I remove security in pom.xml its worked

Related

Spring Boot Keycloak Multi Tenant Configuration

I have a Keycloak instance and created two realms and one user for each realm.
Realm1 (Tenant1) -> User 1
Realm2 (Tenant2) -> User 2
And i have my spring boot application.yml (resource server - API) for one specific realm and fixed in my code.
keycloak:
realm: Realm1
auth-server-url: https://localhost:8443/auth
ssl-required: external
resource: app
bearer-only: true
use-resource-role-mappings: true
It's working and validate for Realm1.
but now i can receive requests from user2 (tenant2) and the token will not be valid because the public key (realm1) is not valid for the signed request jwt token (realm2).
What is the best way to allow multi tenancy and dynamically configuration for multi realms?
thanks,
There's a whole chapter on it: 2.1.18: Multi-Tenanacy
Instead of defining the keycloak config in spring application.yaml, keep multiple keycloak.json config files, and use a custom KeycloakConfigResolver:
public class PathBasedKeycloakConfigResolver implements KeycloakConfigResolver {
#Override
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
if (request.getPath().startsWith("alternative")) { // or some other criteria
InputStream is = getClass().getResourceAsStream("/tenant1-keycloak.json");
return KeycloakDeploymentBuilder.build(is); //TODO: cache result
} else {
InputStream is = getClass().getResourceAsStream("/default-keycloak.json");
return KeycloakDeploymentBuilder.build(is); //TODO: cache result
}
}
}
I'm not sure if this works well with the keycloak-spring-boot-starter, but I think it's enough to just wire your custom KeycloakConfigResolver in the KeycloakWebSecurityConfigurerAdapter:
#Configuration
#EnableWebSecurity
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new PathBasedKeycloakConfigResolver();
}
[...]
}
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
#DependsOn("keycloakConfigResolver")
#KeycloakConfiguration
#EnableGlobalMethodSecurity(jsr250Enabled = true)
#ConditionalOnProperty(name = "keycloak.enabled", havingValue = "true", matchIfMissing = true)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider authenticationProvider = new KeycloakAuthenticationProvider();
authenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(authenticationProvider);
}
/**
* Defines the session authentication strategy.
*/
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.cors()
.and()
.authorizeRequests().antMatchers(HttpMethod.OPTIONS)
.permitAll()
.antMatchers("/api-docs/**", "/configuration/ui",
"/swagger-resources/**", "/configuration/**", "/v2/api-docs",
"/swagger-ui.html/**", "/webjars/**", "/swagger-ui/**")
.permitAll()
.anyRequest().authenticated();
}
}
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.OIDCHttpFacade;
import java.io.InputStream;
import java.util.concurrent.ConcurrentHashMap;
public class PathBasedConfigResolver implements KeycloakConfigResolver {
private final ConcurrentHashMap<String, KeycloakDeployment> cache = new ConcurrentHashMap<>();
#Override
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
String path = request.getURI();
String realm = "realmName";
if (!cache.containsKey(realm)) {
InputStream is = getClass().getResourceAsStream("/" + realm + "-keycloak.json");
cache.put(realm, KeycloakDeploymentBuilder.build(is));
}
return cache.get(realm);
}
}
import org.keycloak.adapters.KeycloakConfigResolver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.context.annotation.Bean;
#SpringBootApplication()
public class Application {
public static void main(String[] args) {
SpringApplication.run(DIVMasterApplication.class, args);
}
#Bean
#ConditionalOnMissingBean(PathBasedConfigResolver.class)
public KeycloakConfigResolver keycloakConfigResolver() {
return new PathBasedConfigResolver();
}
}

Spring Boot RestController DELETE request fails without .csrf().disable()

I have a Spring Boot Rest Service proof of concept.
I have this for my security: (obviously a poor real implmentation).
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
/* not production grade quality */
httpSecurity.authorizeRequests().anyRequest().permitAll();
}
// #Override
// public void configure(WebSecurity web) throws Exception {
// web.debug(true);
// }
}
Using Postman:
All of my GETs were working fine. Then I added a DELETE request. and got
{
"timestamp": "blah blah blah",
"status": 403,
"error": "Forbidden",
"message": "Forbidden",
"path": "/v1/mything/1"
}
Postman setup: (not rocket science)
DELETE
http://localhost:8080/v1/mythings/1
So I added the ".csrf().disable()", and my DELETE works.
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
public class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
/* not production grade quality */
httpSecurity.csrf().disable(); /* had to add this "Cross Site Request Forgery" disable for DELETE operations */
httpSecurity.authorizeRequests().anyRequest().permitAll();
}
// #Override
// public void configure(WebSecurity web) throws Exception {
// web.debug(true);
// }
}
But my question is WHY does .csrf().disable() .. allow DELETE requests? Seems somewhat unrelated.
Thanks.
My full rest controller below:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.inject.Inject;
import java.time.OffsetDateTime;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
#RestController
#RequestMapping("/v1")
public class MyThingController {
private final Logger logger;
private final IMyThingManager mythingManager;
/* The Inject annotation is the signal for which constructor to use for IoC when there are multiple constructors. Not needed in single constructor scenarios */
#Inject
public MyThingController(IMyThingManager mythingManager) {
this(LoggerFactory.getLogger(MyThingController.class), mythingManager);
}
public MyThingController(Logger lgr, IMyThingManager mythingManager) {
if (null == lgr) {
throw new IllegalArgumentException("Logger is null");
}
if (null == mythingManager) {
throw new IllegalArgumentException("IMyThingManager is null");
}
this.logger = lgr;
this.mythingManager = mythingManager;
}
#RequestMapping(value = "/mythings", method = RequestMethod.GET)
Collection<MyThingDto> getAllMyThings() {
Collection<MyThingDto> returnItems = this.mythingManager.getAll();
return returnItems;
}
#RequestMapping(method = RequestMethod.GET, value = "mythings/{mythingKey}")
ResponseEntity<MyThingDto> getMyThingById(#PathVariable Long mythingKey) {
this.logger.info(String.format("Method getMyThingById called. (mythingKey=\"%1s\")", mythingKey));
Optional<MyThingDto> foundItem = this.mythingManager.getSingle(mythingKey);
ResponseEntity<MyThingDto> responseEntity = new ResponseEntity<>(HttpStatus.NOT_FOUND);
if (foundItem.isPresent()) {
responseEntity = new ResponseEntity<>(foundItem.get(), HttpStatus.OK);
}
return responseEntity;
}
#RequestMapping(value = "mythings/{mythingKey}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Integer> deleteUser(#PathVariable("mythingKey") Long mythingKey) {
this.logger.info(String.format("Method deleteUser called. (mythingKey=\"%1s\")", mythingKey));
int rowCount = this.mythingManager.deleteByKey(mythingKey);
int rowCount = 1; /* use this to "fake it" */
ResponseEntity<Integer> responseEntity = new ResponseEntity<>(HttpStatus.NOT_FOUND);
if (rowCount > 0) {
responseEntity = new ResponseEntity<>(rowCount, HttpStatus.OK);
}
return responseEntity;
}
}
CSRF protection checks for a CSRF token on changing methods like POST, PUT, DELETE. And as a REST API is stateless you don't have a token in a cookie. That's why you have to disable it for REST APIs.
References
https://security.stackexchange.com/questions/166724/should-i-use-csrf-protection-on-rest-api-endpoints
Spring Security Reference: https://docs.spring.io/spring-security/site/docs/current/reference/html5/#csrf
Guide to CSRF Protection in Spring https://www.baeldung.com/spring-security-csrf
The guide to CSRF Protection says: "However, if our stateless API uses a session cookie authentication, we need to enable CSRF protection as we'll see next."
For this case the solution is not to disable csrf. Is there another possibility to use CSRF Protection with DELETE

Why is an InMemoryUserDetailsManager injected into this controller even though I've configured a JdbcUserDetailsManager elsewhere?

I'm trying to use Spring Security JDBC Authentication in a Spring Boot web app.
Here's the (much simplified, relevant) configuration:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
import javax.sql.DataSource;
#Configuration
public class SecurityConfiguration {
#Autowired
private DataSource dataSource;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
JdbcUserDetailsManagerConfigurer jdbcUserDetailsManagerConfigurer = auth.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema();
}
}
Here's a controller:
import org.adventure.inbound.UserFormData;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/users")
public class UserController {
private final UserDetailsManager userDetailsManager;
public UserController(UserDetailsManager userDetailsManager) {
this.userDetailsManager = userDetailsManager;
}
#PostMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ResponseEntity<Void> registerUser(UserFormData userFormData) {
userDetailsManager.createUser(
User
.withUsername(userFormData.getUsername())
.password(userFormData.getPassword())
.authorities(new SimpleGrantedAuthority("ROLE_USER"))
.build());
return ResponseEntity.created(null).build();
}
}
When I start the app, I can see that:
The AuthenticationManagerBuilder gets a JDBCUDM configured:
but perhaps some step is missing?
The InMemoryUserDetailsManager is instantiated with Spring Security's default user:
The Controller, when instantiated, receives an InMemoryUserDetailsManager:
This means that when I try to create a new user, it uses the InMemoryUDM instead of the JDBCUDM that I'd like to use. Why is that?
Working solution
We already configure the UserDetailsService through JdbcUserDetailsManagerConfigurer, but didn't expose it as a bean.
#Autowired
#Bean
public UserDetailsManager configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
JdbcUserDetailsManagerConfigurer jdbcUserDetailsManagerConfigurer = auth.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema();
return jdbcUserDetailsManagerConfigurer.getUserDetailsService();
}
In your configuration override the userDetailsServiceBean(), and let it call the super method and add the #Bean annotation to make the configured service available. This is also explained here in the Javadocs.
#Configuration
public class SecurityConfiguration extends WebSecutiryConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
JdbcUserDetailsManagerConfigurer jdbcUserDetailsManagerConfigurer = auth.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema();
}
#Bean
public UserDetailsService userDetailsServiceBean()
throws java.lang.Exception {
return super.userDetailsServiceBean();
}
}
Also because you want to use your configuration instead of the Spring Boot one, you might need to add #EnableWebSecurity to disable the Spring Boot defaults.
Make your SecurityConfiguration extend from WebSecurityConfigurerAdapter, add #EnableWebSecurity and expose your JdbcUserDetailsManager as #Bean.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration
extends WebSecurityConfigurerAdapter {
#Bean
public JdbcUserDetailsManager userDetailsManager() {
return yourJdbcUserDetailsManager;
}
/// etc.
}

Multiple forward slashes in request mapping in spring

#RestController
#RequestMapping("/api")
public class AbcController {
#RequestMapping(value = "/abc", method = RequestMethod.GET)
public String abc(){
return "Hello";
}
}
Valid URL: http://localhost:8080/api/abc
Invalid URls:
http://localhost:8080////api/abc
http://localhost:8080/////api////abc
http://localhost:8080/////////api/////abc
Problem: My controller is accepting all above urls. I want to restrict it and accept only valid url and throw error on invalid urls.
Note: I'm not using any custom routing. It's default spring has.
The simplest way is to add custom handler interceptor to validate the url.
public class ValidateURLInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (isValidUrl(request.getRequestURI())) {
return true;
}
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid URL");
return false;
}
private static boolean isValidUrl(String url) {
return !url.contains("//");
}
}
And then update the MVC configuration
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ValidateURLInterceptor());
}
}
Add maven dependency for spring security and use below code to allow access to all the paths without logging in.
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
#EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
public void configure(WebSecurity web) throws Exception
{
web
.ignoring()
.antMatchers("/**");
}
}

Invalid Access Token Spring Boot Resource Server

I have a Spring Boot Resource Server, like this:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
}
And a endpoint like this:
#RestController
public class TestResourceOne {
private static final Logger log = Logger.getLogger(TestResourceOne.class);
#RequestMapping(value = "/calcsqrt")
public Double calcSqtr(#RequestParam("value") Double value) {
return Math.sqrt(value);
}
#RequestMapping(value = "/sum")
public Double calcSqtr(#RequestParam("value1") Double value1, #RequestParam("value2") Double value2) {
return value1 + value2;
}
}
My Authorization Server is in Azure AD, so when i call this endpoint "/calcsqrt" i pass the Bearer Token generated by Azure. This is my request:
GET /serviceone/calcsqrt?value=3 HTTP/1.1
Host: localhost:8080
Authorization: Bearer MY_ACCESS_TOKEN_HERE
Cache-Control: no-cache
Postman-Token: ef5d493c-39f1-4bc4-9084-4ea510ac1255
But i always get the following error from spring:
{
"error": "invalid_token",
"error_description": "Invalid access token: MY_ACCESS_TOKEN_HERE"
}
It seems your resource config class was wrong. i have implemented resource config like this
package com.ig.user.config;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
/**
* #author Jai
*
*/
#Configuration
#EnableResourceServer
#EnableWebSecurity
public class ResourceConfig extends ResourceServerConfigurerAdapter {
private final String userInfoUri = "url";
private final String clientId = "foo";
#Override
public void configure(final ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("user");
}
#Override
public void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/api/v1/user/activate/**").permitAll()//
.anyRequest().authenticated();
}
#Primary
#Bean
public UserInfoTokenServices tokenService() {
final UserInfoTokenServices tokenService = new UserInfoTokenServices(userInfoUri, clientId);
return tokenService;
}
}
EDIT-1
userInfoUri is the URL which gives the Authorization for this resource
resourceId For every resource server we have to create one resourceId and that the same id we have to keep in where you are storing the client details
Hope it will help you out

Resources