As far as Spring security is concerned, it is completely new to me. I found many sources online describing how to set up basic security and was able to get HTTPS REST calls to work with the following configuration on the server side:
#Configuration
#EnableWebSecurity
#EnableConfigurationProperties(SecurityAuthProperties.class)
public class ServerSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final SecurityAuthProperties properties;
#Autowired
public ServerSecurityConfiguration(SecurityAuthProperties properties) {
this.properties = properties;
}
#Override
public void configure(HttpSecurity http) throws Exception {
properties.getEndpoints().forEach((key, value) -> {
try {
for (HttpMethod method : value.getMethods()) {
http.authorizeRequests().antMatchers(method, value.getPath()).permitAll().and()
.httpBasic().and().csrf().disable();
}
} catch (Exception e) {
throw new SecurityConfigurationException(
"Problem encountered while setting up endpoint restrictions", e);
}
});
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
Upon closer inspection, though, it looks as though some portion (not sure how much) is actually being disabled. Could this be why it allows access from a client?
When I modified the configuration to what follows below, I always get the response "Forbidden".
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/rst/**").permitAll();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
It seems to me that this code would allow access to anything in the path /rst and under, yet the opposite seems to be true. What am I missing?
Note: Another thing I should mention is that there is currently no "user" authentication. The "client" is not web based, but is a separate Spring Boot service that has its own client-side security configuration.
Update:
Here is one of the controllers:
#RestController
#RequestMapping("/rst/missionPlanning")
public class MissionPlannerController {
#Autowired
private MissionPlanner service;
#Autowired
private ThreadPoolTaskExecutor executor;
#PostMapping(value = "/planMission", produces = MediaType.APPLICATION_JSON_VALUE)
public DeferredResult<ResponseEntity<GeneralResponse>> planMission() {
DeferredResult<ResponseEntity<GeneralResponse>> result = new DeferredResult<>(60000L);
executor.execute(new Runner(result));
return result;
}
private class Runner implements ITask {
private DeferredResult<ResponseEntity<GeneralResponse>> result;
public Runner(DeferredResult<ResponseEntity<GeneralResponse>> result) {
this.result = result;
}
#Override
public void executeTask() {
// Invoke service and set result.
result.setResult(ResponseEntity.ok(service.planMission()));
}
}
}
Update:
Interesting. I found an example from another SO post (Security configuration with Spring-boot) that seems to work. The only thing that's different is the disabling of CSRF.
I see that stands for Cross-Site Request Forgery, but I don't really understand what that is, whether I should have it enabled, and if I do, how do I get it to work then?
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/rst/**").permitAll();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
There could be something wrong with how you've set up your controller. Does your controller that contains that path have #RequestMapping("/rst")?
It'd be helpful if you updated your post with what your controller looks like.
Edit:
It seems your issue was the type of request being made if you had to disble CSRF.
CSRF requires a token to be specified on all request methods that can cause a change (i.e. POST, PUT, DELETE, PATCH, but not GET).
The reason for this is that when you control the web page, it adds a layer of security where only you are allowed to make these API calls. Without the CSRF token specified in the request, a malicious user will not be able to make that request to your service since the CSRF token is impossible to guess.
You can read more about it here:
https://docs.spring.io/spring-security/site/docs/3.2.0.CI-SNAPSHOT/reference/html/csrf.html#csrf-include-csrf-token
And here: https://www.baeldung.com/spring-security-csrf
Related
I'm using spring boot to build a web application, sometimes I ddont want to spend to much time on permissioning and rule management I just want to be able to create a random string token and allow other services to use my service if they know this token...
all know all the danger and problems related to this approach but for prototyping integration this is what i need...
the question is how can i configure spring security to validate a specific token for a specific endpoint [note that i want different tokens for different endpoints, if the thing was only 1 single token for the whole application se solution would be a simple filter]
EXAMPLE:
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers(AUTH_WHITELIST).permitAll()
.antMatchers(HttpMethod.POST, "/boards/").access("SOMETHING LIKE: httpRequest->headers->get('tokenHeader') == 'mytoken'")
.anyRequest().authenticated()
//......
}
I would like to achieve something like this....
The only way i'm able to do what i want now is by doing:
#Value("${controller.plan.authorization}")
private String AUTHORIZATION_TOKEN;
#GetMapping
public List<Plan> getPlans(#RequestHeader(HttpHeaders.AUTHORIZATION) authToken: String) {
if (AUTHORIZATION_TOKEN.equals(authToken))
return planService.findAll()
else
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "invalid authorization_token")
}
which is quite out of spring pattern and really annoying in my opinion
is there any way to achieve that using spring security?
So you have some common code which needs to be executed in the same way in multiple controllers and endpoints.
This makes interceptor a suitable solution.
Try the following
#Configuration
public class WebSecurityConfig implements WebMvcConfigurer
{
#Autowired
private MyInterceptor myInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor).addPathPatterns("*/boards/*");
}
}
Then the functionality you have described should be something close to the following
#Component
public class MyInterceptor implements HandlerInterceptor {
#Value("${controller.plan.authorization}")
private String AUTHORIZATION_TOKEN;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
final String requestTokenHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (AUTHORIZATION_TOKEN.equals(requestTokenHeader)) {
//continue execution in spring flow to reach the relative controller
return true;
} else {
//break flow and return response.
response.getWriter().write("invalid authorization_token");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return false;
}
}
}
Now your controller can break free of that boilerplate code.
I have a simple Spring Boot REST service for the IFTTT platform. Each authorized request will contain a header IFTTT-Service-Key with my account's service key and I will use that to either process the request or return a 401 (Unauthorized). However, I only want to do this for select endpoints -- and specifically not for ANY of the Spring actuator endpoints.
I have looked into Spring Security, using filters, using HandlerInterceptors, but none seem to fit what I am trying to do exactly. Spring security seems to come with a lot of extra stuff (especially the default user login), filters don't really seem to match the use case, and the handler interceptor works fine but I would have to code logic in to watch specific URLs and ignore others.
What is the best way to achieve what I am trying to do?
For reference, this is the code I have now:
public class ServiceKeyValidator implements HandlerInterceptor {
private final String myIftttServiceKey;
public ServiceKeyValidator(#Value("${ifttt.service-key}") String myIftttServiceKey) {
this.myIftttServiceKey = myIftttServiceKey;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// TODO will have to put logic in to skip this when actuator endpoints are added
String serviceKeyHeader = request.getHeader("IFTTT-Service-Key");
if (!myIftttServiceKey.equals(serviceKeyHeader)) {
var error = new Error("Incorrect value for IFTTT-Service-Key");
var errorResponse = new ErrorResponse(Collections.singletonList(error));
throw new UnauthorizedException(errorResponse);
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
You need to add filtering for the required endpoints in the place where you register your HandlerInterceptor.
For example:
#EnableWebMvc
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new ServiceKeyValidator())
.addPathPatterns("/ifttt/**")
.excludePathPatterns("/actuator/**");
}
}
You can use different URLs path matchers to filter which URL endpoints must be handled by your interceptor and which are not. As the method addPathPatterns returns InterceptorRegistration object that configures this.
This is my first Question ever here on SO, it was helpfull and saved me lots of time, but now I can't find any solution to my problem.
As I'm rather new to spring and espacially to spring-security, I'm stuck with something that might be easy if i had more knowledge.
I have an existing Application that uses a local user database. It uses a custom UserDetails implementation that works if used with user:password authentification through a login form.
Here is the current setup:
public class SecurityContext extends WebSecurityConfigurerAdapter {
....
#Autowired
public void configureGlobal(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider()).userDetailsService(userDetailsService());
}
#Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider result = new DaoAuthenticationProvider();
result.setUserDetailsService(userDetailsService());
result.setPasswordEncoder(passwordEncoder());
return result;
}
#Override
#Bean
public GatesUserDetailsService userDetailsService() {
GatesUserDetailsService result = new GatesUserDetailsService();
result.setClientService(clientService);
result.setAccountService(accountService);
result.setCardService(cardService);
result.setPersonService(personService);
result.setAccountPropertyService(accountPropertyService);
result.setLoginAttemptService(loginAttemptService);
return result;
}
Now I want to use SSO from an external IDP that speaks OpenIdConnect.
Going through the documentation I was able to get this up and running in a "default" manner. That is, at the and of my process a get a user that is an Instance of OidcUser. I need that user to be either extended or incorporate the existing userDetails.
The documentation (Spring Boot and OAuth2) recommends to
Implement and expose OAuth2UserService to call the Authorization
Server as well as your database. Your implementation can delegate to
the default implementation, which will do the heavy lifting of calling
the Authorization Server. Your implementation should return something
that extends your custom User object and implements OAuth2User.
I was able to introduce my own Oauth2UserService that gets called right at the and of the authentification by setting:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.and()
.oauth2Login()
.failureHandler(authenticationFailureHandler())
.successHandler(authenticationSuccessHandler())
.userInfoEndpoint()
.userService(this.oauth2UserService())
.oidcUserService(this.oidcUserService());}
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
final OidcUserService delegate = new OidcUserService();
return (userRequest) -> {
OidcUser oidcUser = delegate.loadUser(userRequest);
//..DO some additional Stuff check against external Server
//Here I could load my custom userDetails
GatesUserDetails userDetails = (GatesUserDetails) userDetailsService.loadUserByUsername("131:" + username);
....
But I have now Idea how to make my customUser a vaild return to my function.
I tried to implement the OidcUser Interface in my userDetails, but still it does not work.
Any hint (even to a more understandable doc) would be highly appreciated.
EDIT
To clarify things, I implemented the oidcUser Interface as stated in the docs along with the necessary implementations (getAttribute, getAttributes, getAuthorities) but still I could not use this as the return type would still be our GatesUserDetails, no way (for me) to cast it to oidcUser
Have the same problem with spring-security-oauth2-client-5.6.2, after hours google and debugger it solved.
First, make sure your UserInfo entrypoint is correct in case you own the
Auth server.
Plus requested scopes contains any of profiles not
only openid.
Logic found here: OidcUserService::shouldRetrieveUserInfo
private boolean shouldRetrieveUserInfo(OidcUserRequest userRequest) {
// Auto-disabled if UserInfo Endpoint URI is not provided
ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails();
if (StringUtils.isEmpty(providerDetails.getUserInfoEndpoint().getUri())) {
return false;
}
// The Claims requested by the profile, email, address, and phone scope values
// are returned from the UserInfo Endpoint (as described in Section 5.3.2),
// when a response_type value is used that results in an Access Token being
// issued.
// However, when no Access Token is issued, which is the case for the
// response_type=id_token,
// the resulting Claims are returned in the ID Token.
// The Authorization Code Grant Flow, which is response_type=code, results in an
// Access Token being issued.
if (AuthorizationGrantType.AUTHORIZATION_CODE
.equals(userRequest.getClientRegistration().getAuthorizationGrantType())) {
// Return true if there is at least one match between the authorized scope(s)
// and accessible scope(s)
return this.accessibleScopes.isEmpty()
|| CollectionUtils.containsAny(userRequest.getAccessToken().getScopes(), this.accessibleScopes);
}
return false;
}
Hope this could help someone.
This question already has answers here:
Filter invoke twice when register as Spring bean
(3 answers)
Closed 2 years ago.
We are facing an issue with SpringSecurity ignoring a method. We tried to skip authentication for a few urls (acutator/health) and resources. Authentication is being taken care externally and we are having one custom filter to extract the principle for authorization.
We override the configured method as shown below:
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**", "/actuator/health");
}
protected void configure(HttpSecurity http) throws Exception {
http.addFilter(cutstomFilter).authorizeRequests()
.antMatchers("/add","/update","/upload").hasAuthority("ADMIN").anyRequest().authenticated()
.and().logout().logoutSuccessUrl("/logoutUser").and()
.exceptionHandling().accessDeniedPage("/accessDenied").and().csrf().disable();
}
With the given implementation, our customFilter is being called for resources and health url. This is causing reauthenticating due to principle change.
We tried adding this code but customFilter gets called for health url as well.
http.authorizeRequests().antMatchers("/actuator/health").permitAll()
Note: Checked the #Rob Winch answer but did not understand why we need a custom filer if we are putting those url in the ignore list.
https://stackoverflow.com/a/19985323/2138633
UPDATE: Please see comment from #dur in question, it will probably solve the issue without major changes.
To make it clear, your first security configuration is correct. Your problem
is that your filter is used as a servlet filter not only as a security chain
filter. Spring Boot does this autmatically, if you expose your filter.
https://stackoverflow.com/a/39314867/14072498
OP is mentioning that actuator end-points are involved. Let's have a look in doc:
https://spring.io/guides/topicals/spring-security-architecture
Doc says:
If you want your application security rules to apply to the actuator
endpoints, you can add a filter chain that is ordered earlier than the
actuator one and that has a request matcher that includes all actuator
endpoints.
Doc is suggesting to divide config into multiple implementations of WebSecurityConfigurerAdapter.
In the example config below, you should apply what you refer to as custom filter to the MainAppConfigurerAdapter.
Example on "Multiple Spring Boot Security Configuration":
https://medium.com/#igor.bonny/multiple-spring-boot-security-configuration-c876f1b6061e
In order to skip authentication for other end-points, add
.and()
.authorizeRequests().anyRequest().permitAll();
to end of app chain shown below.
To verify security settings, add integration tests for all end-points.
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
#Configuration
#Order(ManagementServerProperties.BASIC_AUTH_ORDER - 1)
public class ActuatorConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) {
http.antMatcher("/actuator/**")
...
}
}
#Configuration
#Order(SecurityProperties.DEFAULT_FILTER_ORDER)
public class MainAppConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) {
http.antMatcher("/api/**")
...
}
}
}
Following https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-endpoints-security, you should :
Add to your properties file
management.endpoints.web.exposure.include=*
Update your security configuration to look like
#Configuration(proxyBeanMethods = false)
public class ActuatorSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests((requests) ->
requests.anyRequest().permitAll());
}
}
One way to do it is to use patterns in both security config and in your custom filter that extracts the principal from the authentication. You can do it as follows:
Declare ignore patterns:
static final String[] IGNORE_PATTERNS = new String[] { "**/*.js", "**/*.css", "/resources/**"};
Declare permit all patterns:
static final String[] PERMIT_ALL_PATTERNS = new String[] { "/login", "/logout", "/health"};
Use ignored patterns in WebSecurity:
web.ignoring().antMatchers(IGNORE_PATTERNS);
Use permit-all patterns in HttpSecurity:
http.authorizeRequests().antMatchers(PERMIT_ALL_PATTERNS).permitAll().anyRequest().authenticated().and() ...
Declare a RequestMatcher in your filter:
List<RequestMatcher> matchers = new ArrayList<>();
for (String pattern : IGNORE_PATTERNS) {
matchers.add(new AntPathRequestMatcher(pattern));
}
for (String pattern : PERMIT_ALL_PATTERNS) {
matchers.add(new AntPathRequestMatcher(pattern));
}
RequestMatcher ignoreRequestMatcher = new OrRequestMatcher(matchers);
Use the request matcher in the doFilter method of the filter:
if (ignoreRequestMatcher.matches((HttpServletRequest) request)) {
/* skip this filter */
chain.doFilter(request, response);
return; } /* rest of the filter code below */
I have a simple Micronaut- based "hello world" service that has a simple security built in (for the sake of testing and illustrating the Micronaut security). The controller code in the service that implements the hello service is provided below:
#Controller("/hello")
public class HelloController
{
public HelloController()
{
// Might put some stuff in in the future
}
#Get("/")
#Produces(MediaType.TEXT_PLAIN)
public String index()
{
return("Hello to the World of Micronaut!!!");
}
}
In order to test the security mechanism, I have followed the Micronaut tutorial instructions and created a security service class:
#Singleton
public class SecurityService
{
public SecurityService()
{
// Might put in some stuff in the future
}
Flowable<Boolean> checkAuthorization(HttpRequest<?> theReq)
{
Flowable<Boolean> flow = Flowable.fromCallable(()->{
System.out.println("Security Engaged!");
return(false); <== The tutorial says return true
}).subscribeOn(Schedulers.io());
return(flow);
}
}
It should be noted that, in a departure from the tutorial, the flowable.fromCallable() lambda returns false. In the tutorial, it returns true. I had assumed that a security check would fail if a false is returned, and that a failure would cause the hello service to fail to respond.
According to the tutorials, in ordeer to begin using the Security object, it is necessary to have a filter. The filter I created is shown below:
#Filter("/**")
public class HelloFilter implements HttpServerFilter
{
private final SecurityService secService;
public HelloFilter(SecurityService aSec)
{
System.out.println("Filter Created!");
secService = aSec;
}
#Override
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> theReq, ServerFilterChain theChain)
{
System.out.println("Filtering!");
Publisher<MutableHttpResponse<?>> resp = secService.checkAuthorization(theReq)
.doOnNext(res->{
System.out.println("Responding!");
});
return(resp);
}
}
The problem occurs when I run the microservice and access the Helo world URL. (http://localhost:8080/hello) I cannot cause the access to the service to fail. The filter catches all requests, and the security object is engaged, but it does not seem to prevent access to the hello service. I do not know what it takes to make the access fail.
Can someone help on this matter? Thank you.
You need to change request in your filter when you no have access to resource or process request as usual. Your HelloFilter looks like this:
#Override
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> theReq, ServerFilterChain theChain) {
System.out.println("Filtering!");
Publisher<MutableHttpResponse<?>> resp = secService.checkAuthorization(theReq)
.switchMap((authResult) -> { // authResult - is you result from SecurityService
if (!authResult) {
return Publishers.just(HttpResponse.status(HttpStatus.FORBIDDEN)); // reject request
} else {
return theChain.proceed(theReq); // process request as usual
}
})
.doOnNext(res -> {
System.out.println("Responding!");
});
return (resp);
}
And in the last - micronaut has security module with SecurityFilter, you can use #Secured annotations or write access rules in configuration files more examples in the doc