portletmvc4spring icon indicating copy to clipboard operation
portletmvc4spring copied to clipboard

Spring http security

Open BaranovNikita opened this issue 4 years ago • 16 comments

Hello! I'm try to use spring security for some methods of my REST controller. But i have 403 error all time. My security config: image in web.xml: image

Or Is it possible to protect some methods? Thanks!

BaranovNikita avatar Apr 16 '20 18:04 BaranovNikita

@BaranovNikita Are your REST controller endpoints packaged within a PortletMVC4Spring based WAR project? If so, is it being deployed to Liferay, Pluto, or some other portal?

ngriffin7a avatar Apr 16 '20 21:04 ngriffin7a

@BaranovNikita Are your REST controller endpoints packaged within a PortletMVC4Spring based WAR project? If so, is it being deployed to Liferay, Pluto, or some other portal?

Yes, ofcourse. I have a WAR artifact that deployed in Liferay 7.3.0 portal

BaranovNikita avatar Apr 18 '20 15:04 BaranovNikita

@BaranovNikita When you get an opportunity, please paste an example of a URL that is used to invoke the REST endpoint. Also, could you paste a link to an SSCCE that I can build with Maven or Gradle and deploy locally in order to try and reproduce the problem?

ngriffin7a avatar Apr 18 '20 16:04 ngriffin7a

@BaranovNikita When you get an opportunity, please paste an example of a URL that is used to invoke the REST endpoint. Also, could you paste a link to an SSCCE that I can build with Maven or Gradle and deploy locally in order to try and reproduce the problem?

security-test.zip

Hello! I'm try create simple project with this error, but I'm change Order property for Security Config and got other error (CSRF). Can you check it, please? Thanks

BaranovNikita avatar Apr 19 '20 22:04 BaranovNikita

@BaranovNikita Thank you for providing the SSCCE. After commenting out the <parent>...</parent> part from pom.xml, I was able to get the WAR artifact to build with mvn clean package. It also deploys fine to Liferay Portal CE 7.3 GA2.

However, I cannot add the portlet named security-test to a portal page without seeing the following error in the console log:

com.liferay.portletmvc4spring.NoHandlerFoundException: No handler found for portlet request: mode 'view', phase 'RENDER_PHASE', parameters map[[empty]]

Having said that, I think the main problem you are describing is not a portlet-related problem but a instead it is a problem with usage of @RestController

Please paste an example of a URL that is used to invoke the REST endpoint so that I can try to reproduce the problem. Thanks.

ngriffin7a avatar Apr 21 '20 22:04 ngriffin7a

@BaranovNikita Thank you for providing the SSCCE. After commenting out the <parent>...</parent> part from pom.xml, I was able to get the WAR artifact to build with mvn clean package. It also deploys fine to Liferay Portal CE 7.3 GA2.

However, I cannot add the portlet named security-test to a portal page without seeing the following error in the console log:

com.liferay.portletmvc4spring.NoHandlerFoundException: No handler found for portlet request: mode 'view', phase 'RENDER_PHASE', parameters map[[empty]]

Having said that, I think the main problem you are describing is not a portlet-related problem but a instead it is a problem with usage of @RestController

Please paste an example of a URL that is used to invoke the REST endpoint so that I can try to reproduce the problem. Thanks.

I dont wanna use it for visible portlet. Only for REST Spring service. In this sample we have 2 endpoints: /o/security-test/test/ping (GET) - this endpoint not secured, work fine /o/security-test/test/secured (POST) - secured, but not work

BaranovNikita avatar Apr 22 '20 13:04 BaranovNikita

@BaranovNikita I think I know the reason why your TestController.secure(String) method was not being invoked. You need to specify the CSRF token in order for the Spring security framework to call through to a controller annotated with @RequestMapping(method = RequestMethod.POST). See https://docs.spring.io/spring-security/site/docs/3.2.0.CI-SNAPSHOT/reference/html/csrf.html#csrf-include-csrf-token

ngriffin7a avatar Apr 23 '20 20:04 ngriffin7a

Here is a new project that I created that shows how to make it work -- it defines a portlet with a JSP that has test URLs that you can invoke:

com.liferay.test.portletmvc4spring.portlet-src.zip

Also, please take special note of the filter-mapping entries in web.xml:

	<filter-mapping>
		<filter-name>delegatingFilterProxy</filter-name>
		<url-pattern>/WEB-INF/servlet/view</url-pattern>
		<dispatcher>FORWARD</dispatcher>
		<dispatcher>INCLUDE</dispatcher>
	</filter-mapping>
	<filter-mapping>
		<filter-name>delegatingFilterProxy</filter-name>
		<url-pattern>/test/*</url-pattern>
		<dispatcher>ERROR</dispatcher>
		<dispatcher>REQUEST</dispatcher>
	</filter-mapping>

I had to create two mappings in order to distinguish between the @RestController and the portlet @Controller.

ngriffin7a avatar Apr 23 '20 20:04 ngriffin7a

@BaranovNikita Note that the reason why CSRF is enforced is because you specified it in the configuration:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf()
                .disable()
                .cors()
                .disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.POST, "/test/secured").hasRole("administrator")
                .antMatchers(HttpMethod.GET, "/test/ping").hasRole("guest");
    }

ngriffin7a avatar Apr 23 '20 20:04 ngriffin7a

@BaranovNikita Note that the reason why CSRF is enforced is because you specified it in the configuration:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf()
                .disable()
                .cors()
                .disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.POST, "/test/secured").hasRole("administrator")
                .antMatchers(HttpMethod.GET, "/test/ping").hasRole("guest");
    }

but..I use disable() for that?

BaranovNikita avatar Apr 24 '20 12:04 BaranovNikita

@BaranovNikita You're right, I didn't see the call to disable().

Do you have the following bean defined in one of your Spring XML config files?

<bean id="springSecurityPortletConfigurer" class="com.liferay.portletmvc4spring.security.SpringSecurityPortletConfigurer" />

If so, then CSRF will be enabled because of the following: https://github.com/liferay/portletmvc4spring/blob/master/security/src/main/java/com/liferay/portletmvc4spring/security/SpringSecurityPortletConfigurer.java#L94

If that's the case, then you could define your RestSecurityConfig like the following:

<bean id="springSecurityPortletConfigurer" class="portlet1.configuration.RestSecurityConfig" />
<bean id="delegatingFilterProxy" class="org.springframework.web.filter.DelegatingFilterProxy">
	<property name="targetBeanName" value="springSecurityFilterChain" />
</bean>

(Note that in my testing I had to put it just before the delegatingFilterProxy)

However, there is a problem with your usage of the following:

http.authorizeRequests()
	.antMatchers(HttpMethod.POST, "/test/secured").hasRole("administrator")
	.antMatchers(HttpMethod.GET, "/test/ping").hasRole("guest");

Background: You are trying to use a Spring REST controller like you would in a typical webapp environment. PortletMVC4Spring portlet applications are invoked with PortletURLs, which causes the portlet lifecycle to be invoked using PortletRequests. Spring REST goes through a servlet, meaning it does not go through the portlet lifecycle. You are basically using the Servlet API in order to receive HttpServletRequests from Liferay's Web Application Bundle (WAB) Extender, just like you would with a WAR deployed in plain Tomcat.

The problem is that the call to authorizeRequests() requires that there be some kind of underlying authentication -- and to my knowledge, there is no existing authentication that knows how to ask Liferay who the current user is, and whether or not they have been authenticated.

So... I decided to try and make this work. Attached you will find com.liferay.test.portletmvc4spring.portlet-try2-src.zip which is an updated project with some new source code. The configure(HttpSecurity) method now looks like this:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .csrf()
            .disable()
            .cors()
            .disable()
            .anonymous()
            .disable()
            .apply(new LiferayConfigurer<>());

    http
            .authorizeRequests()
            .antMatchers(HttpMethod.POST, "/test/secured").hasRole("Administrator")
            .antMatchers(HttpMethod.GET, "/test/ping").hasRole("Guest");
}

(BTW, I changed the role names to contain an UPPER-case first letter: Administrator instead of administrator, and Guest instead of guest)

And there is some new source code for LiferayConfigurer.java and some associated classes.

When you get an opportunity, please build this project and give it a try in your environment. If it works, then we can publish a new JAR in the PortletMVC4Spring project that contains the LiferayConfigurer. That way you would be able to reuse it in multiple projects.

Kind Regards,

Neil

ngriffin7a avatar Apr 25 '20 18:04 ngriffin7a

Thanks for that! May be you know, how I can do request from client side? I try use Liferay.Util.fetch('/o/com.liferay.test.portletmvc4spring.portlet/test/secured', { method: 'POST', body: JSON.stringify({ test: 'test'}), headers: { 'Content-Type': 'application/json' } }) But have 403 error :(

My user roles: image

BaranovNikita avatar Apr 26 '20 08:04 BaranovNikita

You might need to use an absolute URL that contains the scheme, server, port etc like "http://localhost:8080/o/com.liferay.test.portletmvc4spring.portlet/test/secured"

Here is an example that I recently got to work in portlet JSP: https://github.com/ngriffin7a/liferay-revived-modules/blob/master/modules/powwow/powwow-web/src/main/resources/META-INF/resources/meetings/view.jsp#L159

You're probably not using JSP from within a portlet view so you wouldn't be able to use JSP tags like <portlet:actionURL/> etc so you would have to determine the scheme, server, port a different way.

Please let me know if you get things working and I start scheduling a time to publish a new JAR in the PortletMVC4Spring project that contains the LiferayConfigurer.

Kind Regards,

Neil

ngriffin7a avatar Apr 27 '20 16:04 ngriffin7a

You might need to use an absolute URL that contains the scheme, server, port etc like "http://localhost:8080/o/com.liferay.test.portletmvc4spring.portlet/test/secured"

Here is an example that I recently got to work in portlet JSP: https://github.com/ngriffin7a/liferay-revived-modules/blob/master/modules/powwow/powwow-web/src/main/resources/META-INF/resources/meetings/view.jsp#L159

You're probably not using JSP from within a portlet view so you wouldn't be able to use JSP tags like <portlet:actionURL/> etc so you would have to determine the scheme, server, port a different way.

Please let me know if you get things working and I start scheduling a time to publish a new JAR in the PortletMVC4Spring project that contains the LiferayConfigurer.

Kind Regards,

Neil

Hello! I have small fix your code. I check User in request, not ThemeDisplay. May be it not work in JSP/Thymeleaf... But it work for simple REST controller :) Thank you very match!

LiferayAuthentication.java.zip

BaranovNikita avatar Apr 27 '20 19:04 BaranovNikita

Hi @ngriffin7a ,

Thanks for the example ('com.liferay.test.portletmvc4spring.portlet-try2-src.zip') attached above. However, you're invoking the REST endpoints from JSP (or withing session scope). I would like to have it invoke from client (say Postman, or third party), so what kind of changes we would need to do in LiferayAuthentication.java? We want to extract user information from JWT token as part of JWT authentication.

Is there any plan to add LiferayConfigurer and other classes you wrote in distrubution?

ahujadipak3 avatar Jul 30 '20 08:07 ahujadipak3