Spring Boot Security - Thymeleaf sec:authorize-url not working - spring

The sec:authorize-url Tag does not work with Spring boot security by default:
git clone https://github.com/spring-projects/spring-boot
Project spring-boot-sample-web-method-security:
Add dependency
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity3</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
Adapt the controller from the sample:
#RequestMapping("/")
public String home(Map<String, Object> model) {
model.put("message", "Hello World");
model.put("title", "Hello Home");
model.put("date", new Date());
return "home";
}
#RequestMapping("/admin/foo")
public String home2(Map<String, Object> model) {
model.put("message", "Hello World");
model.put("title", "Hello Home");
model.put("date", new Date());
return "home";
}
Add url matching to application security:
http.authorizeRequests().antMatchers("/login").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
...
Add testcode in home.html
<div sec:authorize="hasRole('ROLE_ADMIN')">
has role admin
</div>
<div sec:authorize-url="/admin/foo">
can see /admin
</div>
When I start the app and login I will always see the "can see /admin" part no matter if I can actually access the url or not. The role evaluation itself works as expected, as does the url permission itself (I get a 403 when I try to access it with ROLE_USER).
If I add a dummy privilegeEvaluator to the web security configuration that simply returns false for every request, the div will disappear correctly.
Am I missing something here? Is this expected behaviour and what do I need to define to make authorize-url work that way it used to when configuring security with xml?
Update: Basic authentication
This issue is connected to Basic authentication and its AutoConfiguration in SpringBootWebSecurityConfiguration:
In SampleMethodSecurityApplication change the ApplicationSecurity order by replacing:
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
with
#Order(SecurityProperties.BASIC_AUTH_ORDER + 1)
and deactivate basic in spring boot application.properties
security.basic.enabled: false
Now the authorize-url tag will work as expected, but you have lost http basic AutoConfiguration of course.
Leaving security.basic.enabled: true and changing the order of the ApplicationSecurity to be higher than BASIC_AUTH_ORDER will leave you with Basic authentication instead of form login...
Update - PrivilegeEvaluator
I have found the following workaround. Simply register the security interceptor manually in your SecurityConfig:
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Override
public void configure(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();
web.postBuildAction(new Runnable() {
#Override
public void run() {
web.securityInterceptor(http.getSharedObject(FilterSecurityInterceptor.class));
}
});
}
It allows you to use the recommended ACCESS_OVERRIDE_ORDER and http basic auto configuration. I have posted more details here
Any explanation why this works is appreciated.

using thymeleaf-extras-springsecurity4 should solve the problem
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>

Related

Request Attribute not found after Spring Security added to Spring Boot app

I have a Spring Boot application that is running. As soon as I added Spring Security, the app generated an error.
I have a form that is backed by a bean. When I enable Spring Security, the bean for the form cannot be found. Before I added Spring Security, the bean and form worked.
The error that I receive after making a GET request to the form is
Neither BindingResult nor plain target object for bean name 'orderActive' available as request attribute
The form is using the ThymeLeaf package.
Spring Security Configuration
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("buzz")
.password("{noop}infinity")
.authorities("ROLE_USER");
}
}
Controller Method
#GetMapping("/orders/current")
public String orderForm() {
return "orderForm";
}
Test Class Annotations
#SpringBootTest
#AutoConfigureMockMvc
class DesignTacoControllerTest {
Test Method
#WithMockUser("buzz")
#Test
public void testProcessDesignGet() throws Exception {
mockMvc.perform(get("/orders/current")
.requestAttr("orderActive", new Order()))
.andExpect(status().isOk());
}
orderForm
<form method="POST" th:action="#{/orders}" th:object="${orderActive}">
I have tried adding a RequestAttribute to the controller method.
#GetMapping("/orders/current")
public String orderForm(#RequestAttribute("orderActive") Order orderActive) {
return "orderForm";
}
When I debug, the order has the same ID as the one that was added in the test method. The next step is to render the view. When I continue, the error appears.
Somewhere between the controller method and the view, the request parameter disappears. It has something to do with security, since the code runs without security enabled. The order form is found, so the page is not forbidden. Does security disable the request attributes?
You say it worked before Security, but, do you have a class (DTO) OrderForm with the fields you need in your form? I don't see one. If you don't create one and then add it to the model (that's the Binding part):
#GetMapping("/orders/current")
public String orderForm(Model model) {
model.addAttribute("orderForm", new OrderForm())
return "orderForm";
}

Enable and disable endpoints at runtime with Spring boot

Let's say I have the following controller:
#RestController
public class MyController {
#GetMapping("v1/remain")
public MyObject getRemain() {
// ...
}
}
How can I enable or disable this endpoint at runtime dynamically with Spring boot? Also, is it possible to change this without having to restart the application?
You can either use #ConditionalOnExpression or #ConditionalOnProperty
#RestController
#ConditionalOnExpression("${my.property:false}")
#RequestMapping(value = "my-end-point", produces = MediaType.APPLICATION_JSON_VALUE)
public class MyController {
#RequestMapping(value = "endpoint1", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> endpoint1(
return new ResponseEntity<>("Hello world", HttpStatus.OK);
}
}
Now if you want the above controller to work, you need to add following in application.properties file.
my.controller.enabled=true
Without the above statement, it will behave like the above controller don't exist.
Similiarly,
#ConditionalOnProperty("my.property")
behaves exactly same as above; if the property is present and "true", the component works, otherwise it doesn't.
To dynamically reload beans when a property changes, you could use Spring boot actuator + Spring cloud so that you have access to the /actuator/refresh endpoint.
This can be done by adding the following dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
The latter does require that you add the BOM for Spring cloud, which is:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Now you can enable the /actuator/refresh endpoint by setting the following property:
management.endpoints.web.exposure.include=refresh
This will allow you to send a POST call to /actuator/refresh, which will return an array of all changed properties.
By using the /actuator/refresh endpoint, it also allows you to use the #RefreshScope annotation to recreate beans. However, there are a few limitations:
#RefreshScope recreates the bean without re-evaluating conditionals that might have changed due to the refresh. That means that this solution doesn't work with #RefreshScope, as seen in the comment section of this question.
#RefreshScope doesn't work nicely with filters either, as seen in this issue.
That means you have two options:
Add the #RefreshScope to the controller and do the conditional logic by yourself, for example:
#RefreshScope
#RestController
#RequestMapping("/api/foo")
public class FooController {
#Value("${foo.controller.enabled}")
private boolean enabled;
#GetMapping
public ResponseEntity<String> getFoo() {
return enabled ? ResponseEntity.of("bar") : ResponseEntity.notFound().build();
}
}
This means you would have to add this condition to all endpoints within your controller. I haven't verified if you could use this with aspects.
Another solution is to not use #RefreshScope to begin with, and to lazily fetch the property you want to validate. This allows you to use it with a filter, for example:
public class FooFilter extends OncePerRequestFilter {
private Environment environment;
public FooFilter(Environment environment) {
this.environment = environment;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if ("true".equalsIgnoreCase(environment.getProperty("foo.controller.enabled"))) {
filterChain.doFilter(request, response);
} else {
response.setStatus(HttpStatus.NOT_FOUND.value());
}
}
}
You'll have to register the filter as well, for example by using:
#Bean
public FilterRegistrationBean<FooFilter> fooFilter(Environment environment) {
FilterRegistrationBean<FooFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new FooFilter(environment));
bean.addUrlPatterns("/api/foo");
return bean;
}
Please note, this approach only fetches the property dynamically from the Environment. Refreshing the Environment itself still requires you to use the /actuator/refresh endpoint.

Spring Security WebFlux and LDAP

What customization's are required in order to secure a Reactive Spring Boot application with LDAP? The examples I've seen so far are based on Spring MVC and the example for securing a WebFlux only shows a simple Reactive example with an in-memory Map.
Here is one solution for this that I have come up with and tested.
Deserving special attention is this information in this class: ReactiveAuthenticationManagerAdapter. There, it states:
Adapts an AuthenticationManager to the reactive APIs. This is somewhat
necessary because many of the ways that credentials are stored (i.e.
JDBC, LDAP, etc) do not have reactive implementations. What's more is
it is generally considered best practice to store passwords in a hash
that is intentionally slow which would block ever request from coming
in unless it was put on another thread.
First, create a configuration class. This will handle the connectivity to LDAP.
#Configuration
public class ReactiveLdapAuthenticationConfig {
// Set this in your application.properties, or hardcode if you want.
#Value("${spring.ldap.urls}")
private String ldapUrl;
#Bean
ReactiveAuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
BindAuthenticator ba = new BindAuthenticator(contextSource);
ba.setUserDnPatterns(new String[] { "cn={0},ou=people" } );
LdapAuthenticationProvider lap = new LdapAuthenticationProvider(ba);
AuthenticationManager am = new ProviderManager(Arrays.asList(lap));
return new ReactiveAuthenticationManagerAdapter(am);
}
#Bean
BaseLdapPathContextSource contextSource() {
LdapContextSource ctx = new LdapContextSource();
ctx.setUrl(ldapUrl);
ctx.afterPropertiesSet();
return ctx;
}
}
After that, you'll want to configure your security following the patterns here. The most basic chain configuration is about this:
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.httpBasic();
return http.build();
}
For completeness, you'll want to make sure you have these:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
</dependency>
Other References
EnableReactiveMethodSecurity
BindAuthenticator
LdapAuthenticationProvider
ProviderManager
The above example didn't work for me using Windows Active Directory. I could get LDAP Authentication to work in stand-alone (non-Spring) Java, but the above solution always gave me error 52e (user known, but invalid password).
Following on from the example above, I used the same pom.xml and #EnableWebFluxSecurity ... SecurityWebFilterChain(...), but with the following;
#Configuration
public class ReactiveLdapAuthenticatoinConfig {
#Bean
ReactiveAuthenticationManager authenticationManager() {
ActiveDirectoryLdapAuthenticationProvider adlap =
new ActiveDirectoryLdapAuthenticationProvider(
"{my.domain}",
"ldap://{my.ldap.server}.{my.domain}"
);
AuthenticationManager am = new ProviderManager(Arrays.asList(adlap));
return new ReactiveAuthenticationManagerAdapter(am);
}
}
In order to return the signed-in user, one would use something like;
#GetMapping(value = '/user')
public Mono<String> getUser(Mono<Principal> principal) {
return principal.map(Principal::getName);
}

Why does spring rest controller return unauthorized when no security has been set

I have start a spring boot application. I added a rest controller like this
#RestController
public class EmailController {
#RequestMapping(value = "/2", method = RequestMethod.GET)
public int getNumber() {
return 2;
}
}
When I call the url http://localhost:8080/2 I get a 401 exception.
{"timestamp":1498409208660,"status":401,"error":
"Unauthorized","message":"Full authentication is
required to access this resource", "path":"/2"}
That I have absolutely no security set whats so ever. This is a clean spring boot project! Why am I receiving an unauthorized exception.
You configured no authentication (Form Login, HTTP Basic, ...) so the default AuthenticationEntryPointis used, see Spring Security API here:
Sets the AuthenticationEntryPoint to be used.
If no authenticationEntryPoint(AuthenticationEntryPoint) is specified, then defaultAuthenticationEntryPointFor(AuthenticationEntryPoint, RequestMatcher) will be used. The first AuthenticationEntryPoint will be used as the default is no matches were found.
If that is not provided defaults to Http403ForbiddenEntryPoint.
You can set the AuthenticationEntryPoint as #ksokol wrote or configure a authentication, which defines a AuthenticationEntryPoint.
Add #EnableWebSecurity annotation in your main class it will ignore default security to access that application.
sample code is below:
#EnableWebSecurity
#SpringBootApplication
public class SampleApplication {
SampleApplication (){}
public static void main(String[] args) {
SpringApplication.run(SampleApplication .class, args);
}
For people looking for a solution. I added this to my application.yml
security:
ignored: /**
Check that in the pom.xml file you do not include the dependency spring-boot-starter-security.
Because you added Spring Security Dependency to your project but didn't config it.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

Could not resolve view with name 'htmlviews/index.html' in servlet with name 'dispatcher' using javaconfig

I get such exception:
javax.servlet.ServletException: Could not resolve view with name 'htmlviews/index.html' in servlet with name 'dispatcher'
org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1211)
org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1011)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:955)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:852)
javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
when I try to connect to fully java configured spring web service.
My configuration classes:
#Configuration
#EnableWebMvc
#ComponentScan({"config", "controller"})
public class MyWebConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/htmlviews/**").addResourceLocations("/htmlviews/");
}
}
Initializer:
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{MyWebConfig.class};
}
protected Class<?>[] getServletConfigClasses() {
return null;
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
and controller:
#Controller
public class IndexController {
#RequestMapping(value = "/")
public String getIndexPage() {
return "htmlviews/index.html";
}
#RequestMapping(value = "/{[path:[^\\.]*}")
public String index() {
return "forward:/";
}
}
whole file srtucture is simple :
I am using Idea IDE (also tried in eclipse, same exception) and trying to deploy on tomcat. In pom.xml, I added 'jstl' dependency, but that did not help to resolve problem.
Using xml configuration everything works well. I have no idea what is wrong with my spring java configuration, it is super simple, maybe I forgot something?
Fixed it
Everything started working when I changed spring version from 4.1.0.RELEASE to 4.2.3.RELEASE . I do not why it does not work with 4.1.0.RELEASE. Maybe someone can explain, just curious.
Problem
Spring is trying to find views under your webapp directory. Since you do not have any view resolver, Spring cannot resolve "htmlviews/index.html". In other words, Spring does not know what it is.
You have a Resource Resolver for your html page, which is OK because HTML is static.
Possible Solution 1
In your MyWebConfig class, add the following:
#Override
public void configureViewResolvers(final ViewResolverRegistry registry) {
registry.jsp("/htmlviews/", ".jsp");
}
OR you can do this:
#Bean
public InternalResourceViewResolver jspViewResolver() {
InternalResourceViewResolver resolver= new InternalResourceViewResolver();
resolver.setPrefix("/htmlviews/");
resolver.setSuffix(".jsp");
return resolver;
}
Change your html page to jsp page, I recommend that because jsp is simply more powerful than HTML.
Possible Solution 2
Pult all your htmlviews folder under resources so that Spring can find it according to your Resource Resolver.
Update
It's rarely the case that HTML is needed in a Spring boot app. I highly recommend using a template engine (Thymeleaf is preferred). This way, the sensible default setup is sufficient for most of the multi-page applications.
I was trying to implement demo https://spring.io/guides/gs/securing-web/ but i was facing similar problem, To note- this demo only have html with thymleaf (no JSP) and I missed to add thymleaf dependency(reason of error) earlier it showed error
Circular view path []: would dispatch back to the current handler URL ..error
Then I added bean view resolver and it started to give error .
Could not resolve view with name..error
Finally it worked after removing the bean view resolver and adding dependency for thymleaf. Adding this made my project work.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Some more help I found to understand all this working at How to avoid the "Circular view path" exception with Spring MVC test

Resources