Freemarker autoconfiguration does not pick up tagslibs, if they are present in the classpath.
Comment From: isopov
Test project is here - https://github.com/spring-projects/spring-boot-issues/pull/2
Comment From: dsyer
I'm not sure that was ever intended to work. If you want to propose a change that enables it in a web app, feel free to send a pull request.
Comment From: isopov
BTW - I hacked this issue the following way - https://github.com/isopov/fan/blob/master/fan-web/src/main/java/com/sopovs/moradanen/fan/WebApplicationConfiguration.java It required to refer to taglibs with <#assign security=JspTaglibs["/META-INF/security.tld"] /> instead of <#assign security=JspTaglibs["http://www.springframework.org/security/tags"] />
Comment From: isopov
What was the intended way to work with spring-security from freemarker based spring-boot application?
Comment From: hahashraf
any word on this?
Comment From: dsyer
Still waiting for a pull request. JSP is an inferior technology and using it undermines Freemarker's advantages (in my opinion), but if anyone needs this feature we are open for contributions.
Comment From: wayshall
a simple solution to fixed this issue https://github.com/wayshall/spring-boot-pit/blob/master/src/test/java/org/onetwo/boot/core/web/ftl/FreemarkerTaglibsConfigTest.java
Comment From: asegarra
I would like to see this incorporated as well so we can have the full freemarker experience when using it outside of boot, I would prefer to see Spring Security shipping a macro library though.
Comment From: dsyer
+1 for the macros in Spring Security. Do you have a JIRA ticket link?
Comment From: asegarra
Just made one in a haste https://jira.spring.io/browse/SEC-3072
Comment From: xiaoshuai
did you forget to add "jsp-api". http://www.mvnrepository.com/artifact/javax.servlet.jsp/jsp-api/2.2.1-b03.
Comment From: nE0sIghT
Any workaround for this issue with latest stable boot?
Comment From: zzylekang
Has the problem been solved?
Comment From: zzylekang
@Xiaoshuai 这个问题你怎么解决的?
Comment From: philwebb
We're cleaning out the issue tracker and closing issues that we've not seen much demand to fix. Feel free to comment with additional justifications if you feel that this one should not have been closed.
Comment From: wilkinsona
Our feeling is that this should be tackled in Spring Security (see https://github.com/spring-projects/spring-security/issues/3275).
Comment From: ggj2010
老铁 666666
Comment From: xak2000
I agree that JSP Tags is old tech and should not be used for new projects, but it can be heavily used in old legacy spring projects still.
If someone wants to migrate old spring project, which uses JSP Tags in FreeMarker templates, to spring-boot, they can't easily do it. And one of the reasons is this issue - it is hard to understand how to use spring's and spring-security's and even custom JSP Tags from classpath instead of servlet context.
But it is actually easy task if you know how to do it. I spend several hours to investigate that problem, so I want to share a solution.
I implemented it as BeanPostProcessor because now there is no way to set TaglibFactory before FreeMarkerConfigurer bean is initialized.
import freemarker.ext.jsp.TaglibFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import java.util.Arrays;
import java.util.regex.Pattern;
/**
* A {@link BeanPostProcessor} that enhances {@link FreeMarkerConfigurer} bean, adding
* {@link freemarker.ext.jsp.TaglibFactory.ClasspathMetaInfTldSource} to {@code metaInfTldSources}
* of {@link TaglibFactory}, containing in corresponding {@link FreeMarkerConfigurer} bean.
*
* <p>
* This allows JSP Taglibs ({@code *.tld} files) to be found in classpath ({@code /META-INF/*.tld}) in opposition
* to default FreeMarker behaviour, where it searches them only in ServletContext, which doesn't work
* when we run in embedded servlet container like {@code tomcat-embed}.
*
* @author Ruslan Stelmachenko
* @since 20.02.2019
*/
@Component
public class JspTagLibsFreeMarkerConfigurerBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof FreeMarkerConfigurer) {
FreeMarkerConfigurer freeMarkerConfigurer = (FreeMarkerConfigurer) bean;
TaglibFactory taglibFactory = freeMarkerConfigurer.getTaglibFactory();
TaglibFactory.ClasspathMetaInfTldSource classpathMetaInfTldSource =
new TaglibFactory.ClasspathMetaInfTldSource(Pattern.compile(".*"));
taglibFactory.setMetaInfTldSources(Arrays.asList(classpathMetaInfTldSource));
// taglibFactory.setClasspathTlds(Arrays.asList("/META-INF/tld/common.tld"));
}
return bean;
}
}
The only restriction is that *.tld files must have <uri> xml tag inside. All standard spring/spring-security TLDs have it. And also these files must be inside META-INF folder of classpath, like META-INF/mytaglib.tld. All standard spring/spring-security TLDs are also follow this convention.
Commented line is just for example of how you can add "custom" paths of *.tld files if for some reason you can't place them into standard location (maybe some external jar, which doesn't follow the convention). It can be extended to some sort of classpath scanning, searching for all *.tld files and adding them into classpathTlds. But usually it is just doesn't required if your TLDs follow JSP conventions to be placed inside META-INF directory.
I have tested this in my FreeMarker template and it works:
<#assign common = JspTaglibs["http://my-custom-tag-library/tags"]>
<#assign security = JspTaglibs["http://www.springframework.org/security/tags"]>
<#assign form = JspTaglibs["http://www.springframework.org/tags/form"]>
<#assign spring = JspTaglibs["http://www.springframework.org/tags"]>
For custom tag ("http://my-custom-tag-library/tags") to work, it must be *.tld file in src/main/resources/META-INF/some.tld and it must contain the <uri> xml tag, like <uri>http://my-custom-tag-library/tags</uri>. It will be found by FreeMarker then.
I hope it helps someone to save several hours to find "right" solution for this problem.
@dsyer @philwebb @wilkinsona Please consider to include some kind of this solution into spring-boot's freemarker autoconfiguration, if you don't see any problems with it. Of course it should be configurable and switchable, but I think in spring-boot it can be even enabled by default because without it, JSP tags just don't work with embedded servlet containers.
Comment From: philwebb
We'll take another look at this and reconsider the original decision.
Comment From: EhanDuan
why <#assign security = JspTaglibs["http://www.springframework.org/security/tags"]> do not have close symbol ""
Comment From: Johnferguson1440
Is this going to be implemented for freemarker? Or is there any plan to add support for spring tags for freemarker?
Comment From: wilkinsona
@Johnferguson1440 We don't have any immediate plans to address this in Spring Boot. https://github.com/spring-projects/spring-security/issues/3275 is tracking things on the Spring Security side. In the meantime, you should be able to use the approach above.
Comment From: doctore
I had this problem upgrading an old project from Spring 3 to Spring 6, by now we have no time to replace the technology used to render the views, so my solution for JSP security tags was rewrite them using FreeMarker macros:
<#ftl output_format="HTML" strip_whitespace=true>
<#--
* security.ftl
*
* This file consists of a collection of FreeMarker macros aimed at easing
* some of the common requirements of web applications - in particular
* handling of security.
-->
<#--
* isAnonymous
*
* Verifies if there is no a logged user.
-->
<#macro isAnonymous>
<#assign anonymous = true>
<#if SPRING_SECURITY_CONTEXT??>
<#assign anonymous = false>
</#if>
<#if anonymous>
<#nested>
</#if>
</#macro>
<#--
* isAuthenticated
*
* Checks if there is a logged user and he/she is authenticated.
-->
<#macro isAuthenticated>
<#assign authenticated = false>
<#if SPRING_SECURITY_CONTEXT??>
<#assign authentication = SPRING_SECURITY_CONTEXT.authentication
isUserAuthenticated = authentication.isAuthenticated()>
<#if isUserAuthenticated>
<#assign authenticated = true>
</#if>
</#if>
<#if authenticated>
<#nested>
</#if>
</#macro>
<#--
* hasRole
*
* Verifies if there is a logged user and he/she has the given role/authority.
*
* Example:
*
* <@security.hasRole role="ROLE_ADMIN">
* <br><span>User has the role: ROLE_ADMIN</span>
* </@security.hasRole>
*
* @param role
* The role and/or authority to verify
-->
<#macro hasRole role>
<#assign authorized = false>
<#if SPRING_SECURITY_CONTEXT?? && role??>
<#list SPRING_SECURITY_CONTEXT.authentication.authorities as authority>
<#if authority == role>
<#assign authorized = true>
</#if>
</#list>
</#if>
<#if authorized>
<#nested>
</#if>
</#macro>
<#--
* ifAnyGranted
*
* Checks if there is a logged user and he/she has one of the given roles/authorities.
*
* Example:
*
* <@security.ifAnyGranted roles="ROLE_ADMIN,ROLE_SUPERADMIN">
* <br><span>User has one of the roles: ROLE_ADMIN, ROLE_SUPERADMIN</span>
* </@security.ifAnyGranted>
*
* @param roles
* Roles and/or authorities separated by commas to verify
-->
<#macro ifAnyGranted roles>
<#assign authorized = false>
<#if SPRING_SECURITY_CONTEXT?? && roles??>
<#list SPRING_SECURITY_CONTEXT.authentication.authorities as authority>
<#list roles?split(",") as role>
<#if authority == role>
<#assign authorized = true>
</#if>
</#list>
</#list>
</#if>
<#if authorized>
<#nested>
</#if>
</#macro>
<#--
* ifNotGranted
*
* Checks if there is a logged user and he/she does not have any of the given roles/authorities.
*
* Example:
*
* <@security.ifNotGranted roles="ROLE_ADMIN,ROLE_SUPERADMIN">
* <br><span>User does not have any of the roles: ROLE_ADMIN, ROLE_SUPERADMIN</span>
* </@security.ifNotGranted>
*
* @param roles
* Roles and/or authorities separated by commas to verify
-->
<#macro ifNotGranted roles>
<#assign authorized = false>
<#if SPRING_SECURITY_CONTEXT?? && roles??>
<#assign authorized = true>
<#list SPRING_SECURITY_CONTEXT.authentication.authorities as authority>
<#list roles?split(",") as role>
<#if authority == role>
<#assign authorized = false>
</#if>
</#list>
</#list>
</#if>
<#if authorized>
<#nested>
</#if>
</#macro>
I added it to the file security.ftl and import it in a common ftl file using:
<#import "security.ftl" as security />
Comment From: snicoll
@doctore that's probably because Freemarker itself did not support Jakarta and, as a result, support for that was removed. It's been reintroduced very recently in Spring Framework 6.2 and available as of Spring Boot 3.4 so you may want to give that another try.
Comment From: wilkinsona
There's been minimal interest in this since it was re-opened just over 5 years ago. There's also little sign of strong demand for the related Spring Security issue so I don't think we can justify spending our limited time on this one.