spring-projects / spring-boot

Spring Boot
https://spring.io/projects/spring-boot
Apache License 2.0
74.41k stars 40.51k forks source link

Support for JSP taglibs in freemarker #907

Closed isopov closed 1 month ago

isopov commented 10 years ago

Freemarker autoconfiguration does not pick up tagslibs, if they are present in the classpath.

isopov commented 10 years ago

Test project is here - https://github.com/spring-projects/spring-boot-issues/pull/2

dsyer commented 10 years ago

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.

isopov commented 10 years ago

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"] />

isopov commented 10 years ago

What was the intended way to work with spring-security from freemarker based spring-boot application?

hahashraf commented 9 years ago

any word on this?

dsyer commented 9 years ago

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.

wayshall commented 9 years ago

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

asegarra commented 9 years ago

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.

dsyer commented 9 years ago

+1 for the macros in Spring Security. Do you have a JIRA ticket link?

asegarra commented 9 years ago

Just made one in a haste https://jira.spring.io/browse/SEC-3072

xiaoshuai commented 8 years ago

did you forget to add "jsp-api". http://www.mvnrepository.com/artifact/javax.servlet.jsp/jsp-api/2.2.1-b03.

nE0sIghT commented 7 years ago

Any workaround for this issue with latest stable boot?

zzylekang commented 7 years ago

Has the problem been solved?

zzylekang commented 7 years ago

@Xiaoshuai 这个问题你怎么解决的?

philwebb commented 6 years ago

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.

wilkinsona commented 6 years ago

Our feeling is that this should be tackled in Spring Security (see https://github.com/spring-projects/spring-security/issues/3275).

ggj2010 commented 6 years ago

老铁 666666

xak2000 commented 5 years ago

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.

philwebb commented 5 years ago

We'll take another look at this and reconsider the original decision.

EhanDuan commented 2 years ago

why <#assign security = JspTaglibs["http://www.springframework.org/security/tags"]> do not have close symbol "</>"

Johnferguson1440 commented 2 years ago

Is this going to be implemented for freemarker? Or is there any plan to add support for spring tags for freemarker?

wilkinsona commented 2 years ago

@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.

doctore commented 4 months ago

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 />

snicoll commented 1 month ago

@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.

wilkinsona commented 1 month ago

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.