Securing endpoints in Spring Boot by rights/roles - java

#RestController
public class AccountController {
#PermitAll
#RequestMapping(value = "/test")
public ResponseEntity<String> test() {
// ...
}
#RolesAllowed("ROLE_ADMIN)
#RequestMapping(value = "/products")
public ResponseEntity<List<Product>> products() {
// ...
}
}
How to configure Spring Boot to be able to access "/test" without authentication, but "/products" with authentication and checking rights/roles?
Is it possible without mention paths of #PermitAll(like "/test") in configuration?

Question : Spring Boot to be able to access "/test" without authentication, but "/products" with authentication
Solution :
http.authorizeRequests().antMatchers("/test").permitAll()
.antMatchers("/products").hasRole("ADMIN").anyRequest().authenticated().and().httpBasic();
Note : By default when you add spring security, it ask authentication for all the url and you need to specify the one which you do not need authentication. For Example /login should be permitAll.
Click here for Source code of security configuration
Refer Sample HttpSecurity sample for more matchers example as below,
For more details : https://docs.spring.io/spring-security/site/docs/current/reference/html/jc.html

You can do it providing the next configuration class. In this case everything is accessible, if not restricted by the annotations.
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().anyRequest().permitAll();
}
}
Check SecurityConfig class here for more configuration options.

Related

java Spring #EnableResourceServer and #EnableWebSecurity

I have RESTful spring resource server with #EnableResourceServer and extending ResourceServerConfigurerAdapter
In documentations says:
...In order to use this filter you must #EnableWebSecurity somewhere in your application, either in the same place as you use this annotation, or somewhere else.
But when I get to the public #interface EnableResourceServer I see ResourceServerConfiguration extends WebSecurityConfigurerAdapter.
Question:
So what do I need for pure RESTful API?
#EnableWebSecurity on any #Config
Extend the WebSecurityConfigurerAdapter?
1 + 2
Neither
My config
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class HGResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors().disable()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.jee().disable()
.logout().disable()
.rememberMe().disable()
.servletApi().disable()
.x509().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.and().authorizeRequests().antMatchers(Url.API_ERROR_LOGS_FRONTEND).permitAll()
.and().authorizeRequests().antMatchers(Url.API_REGISTER_PATH).permitAll()
.and().authorizeRequests().antMatchers(Url.API_VERIFY_EMAIL_PATH).permitAll()
.and().authorizeRequests().antMatchers(Url.API_RESET_PASSWORD_PATH).permitAll()
.and().authorizeRequests().antMatchers(Url.API_CONFIRM_RESET_PASSWORD_PATH).permitAll()
.and().authorizeRequests().anyRequest().authenticated();
}
#Primary
#Bean
public RemoteTokenServices tokenService() {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setCheckTokenEndpointUrl("http://localhost:8081/oauth/check_token");
tokenService.setClientId("client");
tokenService.setClientSecret("secret");
return tokenService;
}
//disable default user creation
#Bean
public UserDetailsService userDetailsService() throws Exception {
return new InMemoryUserDetailsManager();
}
//password encoder
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
No, the enable EnableWebSecurity is implicit.
I do not recommend to use WebSecurityConfigurerAdapter, you will come across some troubles :
Correctly configure spring security oauth2
Spring Boot makes #EnableWebSecurtiy implicit, but otherwise is it required.
You can prove this to yourself by taking a look at this OAuth2 resource server example. If you remove the #EnableWebSecurity annotation there, you will find that the Spring Security Filter Chain is not wired.
You can still extend WebSecurityConfigurerAdapter to separate general web application security concerns from those specific to resource server configuration. This isn't technically necessary, but can make for a cleaner separation of concerns.

Spring Data Rest + Spring Security: #Secured not working

I'm building an application using Spring Data Rest, Spring Boot and Spring Security. I need to use #Secured annotations on methods and I've configured Spring Security in the following way:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// #formatter:off
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.securityContext().securityContextRepository(securityContextRepository())
.and()
.exceptionHandling()
.accessDeniedPage(RestPath.Errors.ROOT + RestPath.Errors.FORBIDDEN)
.and()
.csrf().disable();
}
// #formatter:on
#Bean
public SecurityContextRepository securityContextRepository() {
return new ApiUserSecurityContextRepository();
}
#Bean
public UserDetailsService userDetailsService() {
return new ApiUserDetailsService();
}
#Bean
public AuthenticationManager authenticationManager() throws Exception {
return new ProviderManager(Collections.singletonList(authenticationProvider()));
}
#Bean
public AuthenticationProvider authenticationProvider() throws Exception {
final DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
This type of configuration works well for regular MVC controllers and returns 403 when I try to access them. For example, the following controller security works:
#ResponseBody
#RequestMapping(value = RestPath.Configs.SLASH_TEST, method = RequestMethod.GET, produces = MediaTypes.HAL_JSON_VALUE)
#Secured({"ROLE_USER"})
public ResponseEntity test(#RequestParam(value = RestParam.DB_TEST, required = false) final boolean dbTest) throws ApplicationAvailabilityException {
final AppTestData appTestData = configService.testAppAvailability(dbTest);
return ResponseEntity.ok(projectionFactory.createProjection(AppTestProjection.class, appTestData));
}
However, when I try to use #Secured annotation over a rest repository - it does NOT, e.g.:
#RepositoryRestResource(collectionResourceRel = Shop.COLLECTION_NAME, path = RestResourceRel.SHOPS, excerptProjection = StandardShopProjection.class)
#Secured({"ROLE_USER"})
public interface RestShopRepository extends MongoRepository<Shop, String> {
#Secured({"ROLE_ADMIN"})
#Override
Shop findOne(String s);
}
ApiUserSecurityContextRepository is getting called for both of the methods, but only a custom MVC controller is get to the end of chain and I can check that it accesses vote() method in RoleVoter class for granting access.
As an example, I've checked Spring Data Rest + Spring Security sample, so #Secured or #PreAuthorize annotations should work with Spring Data Rest. Any ideas why they don't work?
Finally resolved the issue. The problem was in the following, I had another ShopRepository in different application module, which was not annotated with #RepositoryRestResource and it was the one which was used when accessing it using REST.
The following line of configuration in custom RepositoryRestConfigurerAdapter fixed the exploration of repositories which need to be exposed, so only annotated ones are exposed now:
config.setRepositoryDetectionStrategy(RepositoryDetectionStrategy.RepositoryDetectionStrategies.ANNOTATED);
After that I could not access the resource at all using REST, so I've figured out that it is not visible to Spring. I just had to enable Mongo repositories on API level with annotation #EnableMongoRepositories.

How to attach UserDetails to Principal received from JWT token using Spring Oauth2

I have these services
oauth2 authentication server that uses JDBC provider (DB-1)
Zuul-enabled web application that proxies requests to authserver and resource server
resource that enables #EnableOauth2Resource (DB-2)
I need to fetch the User Details in resource server from DB-2 using credentials received from DB-1 and set enabled authorities to the current Principal for further using with #PreAuthorize of #RolesAllowed annotations.
Are there some hook to replace or update org.springframework.security.oauth2.provider.OAuth2Authentication after successful authentication using oauth2 token?
I got the principal at resource server by configuring my Resource Server as..
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
public ResourceServerConfiguration() {
super();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated();
}
/* returns org.springframework.security.core.userdetails.UserDetailsService */
#Bean
UserDetailsService customUserDetailsService () {
return new CustomUserDetailsService();
}
#Bean
public UserAuthenticationConverter userAuthenticationConverter () {
DefaultUserAuthenticationConverter defaultUAC = new DefaultUserAuthenticationConverter();
defaultUAC.setUserDetailsService(customUserDetailsService());
return defaultUAC;
}
#Bean
public AccessTokenConverter accessTokenConverter() {
DefaultAccessTokenConverter defaultATC = new DefaultAccessTokenConverter();
defaultATC.setUserTokenConverter(userAuthenticationConverter());
return defaultATC;
}
#Bean
RemoteTokenServices getRemoteTokenServices () {
RemoteTokenServices rts = new RemoteTokenServices();
rts.setCheckTokenEndpointUrl("http://localhost:9999/uaa/oauth/check_token");
rts.setAccessTokenConverter(accessTokenConverter());
rts.setClientId("yourClientID");
rts.setClientSecret("yourClientSecret");
return rts;
}
...
}
basically here we are updating the authentication principal at resource server with customUserDetailsService by getting the authentication info from accesstoken. RemoteTokenServices queries the /check_token endpoint of auth-server to obtain the contents of an access token. If the endpoint returns a 400 response, this indicates that the token is invalid.
If using Spring Boot and your user details can be fetched in a standard way, first check the Spring Boot documentation on customizing user information.
If you need further control, I suggest providing a ResourceServerConfigurer to provide a custom ResourceServerTokenServices which can populate your OAuth2Authentication with the principal details you require.
The ResourceServerTokenServices is handed an accessToken by the OAuth2AuthenticationManager or the OAuth2ClientAuthenticationProcessingFilter and expects a populated OAuth2Authentication in return. This provides your the opportunity to fetch principal information and stuff it into the OAuth2Authentication object.
Again, if using Spring Boot, the ResourceServerConfigurer, if instantiated as a bean, will be picked up by the OAuth2 auto configuration and your ResourceServerTokenServices will be registered.
#Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Autowired
private MyResourceServerTokenServices tokenServices;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenServices(tokenServices);
}
}
override
#Override
public OAuth2Authentication loadAuthentication(String accessToken) {
// fetch user information and return populated authentication
}

Spring MVC 404 Error on Certain Requests

I'm receiving a 404 error when accessing a particular page in my spring boot web application.
The strange thing is that I don't receive that error when the resource is mapped to a different location.
#RequestMapping(value="report", method = RequestMethod.GET)
public String getReportPage() {
return "templates/report.html";
}
works just fine while
#RequestMapping(value="report/{uuid}", method = RequestMethod.GET)
public String getReportPage() {
return "templates/report.html";
}
does not. I need the uuid parameter for my angular service so I cannot simply remove that from the path. I've tried adding the path variable to the model; that makes no difference.
The directory structure is set up as follows:
webapp
resources
...
templates
report.html
The configuration is pretty much an out of the box spring boot with some added resource handlers and some basic security:
#Configuration
public class MvcConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/resources/", "file:resources/");
}
}
#Configuration
#EnableWebSecurity
class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and()
.csrf().disable();
}
}
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
auth.authenticationProvider(authProvider());
}
.... custom user details service and authentication provider ...
}
Any thoughts about what may be causing this issue?
Edit: After some further investigation, it looks like anything mapped beyond the first level doesn't work for the web controller (but the rest controllers are working just fine). For example, a mapping with the value /web/report doesn't work either.
While looking through debug messages I found that the application was looking for the pages in the wrong place:
DEBUG : Looking up handler method for path /report/templates/report.html
Which is why only top level requests were working.
Changing the mapping as such:
#RequestMapping(value="report/{uuid}", method = RequestMethod.GET)
public String getReportPage() {
return "/templates/report.html";
}
fixed the problem.

Spring Security Basic Authentication always causes 404

I've been building a REST API using Spring. I'm using Basic Authentication with Spring Security (3.2) and I'm having an issue where any unauthenticated request causes a 404 error, even with an implementation of AuthenticationEntryPoint (regardless, Spring should give a 401 as far as I am aware by default). Requesting the resource in my browser, I am not even prompted for credentials. Here's a screenshot of the problem:
After reading the documentation and a number of tutorials on the subject, I can't seem to find where I've gone wrong. The only thing I can imagine is happening is some exception that's being caught.
Spring Security configuration:
#Slf4j
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String REALM_NAME = "Autopulse API";
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// Set to stateless authentication.
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.csrf().disable();
httpSecurity.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
httpSecurity.userDetailsService(userDetailsService);
httpSecurity.authorizeRequests()
.antMatchers("/make/private").authenticated();
httpSecurity.httpBasic().realmName(REALM_NAME);
}
}
Authentication Entry Point:
#Slf4j
#Component
public class HttpBasicAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
Controller:
#Slf4j
#RestController
#RequestMapping("/make")
public class MakeController {
#RequestMapping(value = "/private", method = RequestMethod.GET)
public String getPrivateStuff() {
return "private things!";
}
}
When I provide valid user credentials in the Authorization header, I can see the protected resource ("private things!"), however if I do not provide an Authorization header, or I enter invalid credentials, I simply get the 404 error. I can attach my user details service and user details classes if required.
I figured it out. The problem came down to Spring and exception handling. I had a class called ExceptionController that looked like:
#Slf4j
#RestController
#ControllerAdvice
public class ExceptionController implements ErrorController {
// #ExceptionHandler methods here.
#Override
public String getErrorPath() {
return null;
}
}
It turns out that, by implementing ErrorController, I was handing control to this class AFTER the authentication entry point where Spring could not find the appropriate method and would eventually throw the unwanted 404 error. The fix was to remove those details:
#Slf4j
#RestController
#ControllerAdvice
public class ExceptionController {
// #ExceptionHandler methods here.
}

Categories

Resources