简体   繁体   中英

No plugin found for delimiter text/html;charset=UTF-8! Registered plugins: [org.springframework.hateoas.mediatype.hal.HalLinkDiscoverer@

I need your help on resolution for the following Exception:

Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sun Apr 19 09:45:23 CEST 2020
There was an unexpected error (type=Internal Server Error, status=500).
No plugin found for delimiter text/html;charset=UTF-8! Registered plugins: [org.springframework.hateoas.mediatype.hal.HalLinkDiscoverer@665cd8b1].
java.lang.IllegalArgumentException: No plugin found for delimiter text/html;charset=UTF-8! Registered plugins: [org.springframework.hateoas.mediatype.hal.HalLinkDiscoverer@665cd8b1].
    at org.springframework.plugin.core.SimplePluginRegistry.lambda$getRequiredPluginFor$2(SimplePluginRegistry.java:140)
...

I implemented two WebServices using SpringBoot: a Data Service called ProductMicroService and an Interface Service called ProductMicroServiceClient for consumers at front-ends.

The ProductMicroService is implemented on basis of JPA and uses as persistency back-end a SQL database (in my sample a MariaDB). The Controller provides RESTful API end points in JSON with media support (HATEOAS).

The ProductMicroServiceClient consumes the API end points from ProductMicroService and provides a RESTful API for front-ends also with media support (HATEOAS).

In my samples the client is a WebBrowser running some simple Thymleaf Templates.

When running the pure ProductMicroService and ProductMicroServiceClient implementations on my local machine everything goes well.

Also after introducing security based on JDBC for the ProductMicroServiceClient everything runs fine including access restrictions on the API end-points.

The User and Authority tables are persisted at the same MariaDB as for the Data Service.

But after introducing SecurityService for the ProductMicroService I receive the Exception above after successful authentication (standard login page from SpringBoot).

I am using OpenJDK.

I couldn't find any direction for a solution when searching in internet.

Some relevant code

for ProductMicroServiceClient:

———— pom.xml ———————————————————————————————————

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>eu.mydomain</groupId>
    <artifactId>ProductMicroServiceClient</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ProductMicroServiceClient</name>
    <description>Learn Spring Boot Configuration for SpringData</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-hateoas</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>   

        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
      </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

——— eu.mydomain.product.security.EncodingFilter.java ————————————————

package eu.mydomain.product.security;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.springframework.web.filter.GenericFilterBean;

public class EncodingFilter extends GenericFilterBean {

    @Override
    public void doFilter(   ServletRequest request,
            ServletResponse response,
            FilterChain chain
            ) throws IOException, ServletException {

        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");

        chain.doFilter( request, response);
    }

}

——— eu.mydomain.product.security.WebSecurityConfig.java —————————————

package eu.mydomain.product.security;

import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure( HttpSecurity http) throws Exception {

        http.addFilterBefore( new EncodingFilter(), ChannelProcessingFilter.class);

        http
            .httpBasic()
            .and()
                .authorizeRequests()
                    .antMatchers("/product/**","/products", "/products/**").hasRole("USER")
                    .antMatchers("/create-products", "/products4create", "/products4edit/**","/update-products/**","/products4edit/**","/delete-products/**","/products4delete/**").hasRole("ADMIN")
                    // .antMatchers("/","/**").permitAll()
                    .antMatchers("/").permitAll()
                    .anyRequest().authenticated()
            .and()
                .formLogin()
            // .and().httpBasic()
            ;


    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder(16);
    }

    @Autowired DataSource dataSource;
    public void configure( AuthenticationManagerBuilder auth) throws Exception {

        auth 
            .jdbcAuthentication()
            .passwordEncoder( encoder() )
            .usersByUsernameQuery( "SELECT username, password, enabled FROM users WHERE username = ?")
            .authoritiesByUsernameQuery( "SELECT username, authority FROM authorities WHERE username = ?") 
            .dataSource( dataSource);        
    }
}

As said until now everything works as expected.

For the ProductMicroService

I introduced a database View V_PRODUCT_USERS that provides relevant user authorities from the users and authorities tables and implemented a ProductUser Entity, IProductUserRepository and UserDetailService.

——— eu.mydomain.product.domain.ProductUser.java —————————————

package eu.mydomain.product.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table( name="v_product_users")
public class ProductUser {

    /*
     * create view if not exists v_product_users as select u.is id, u.username username, u.'password' 'password', a.authority rolefrom users u, authorities a where u.username = a.username;
     * commit;
     */

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(nullable = false, updatable = false)
    private Long id;

    @Column(nullable = false, unique = true, updatable = false)
    private String username;

    @Column(nullable = false, updatable = false)
    private String password;

    @Column(nullable = false, updatable = false)
    private String role;

    public ProductUser()
    {}

    public ProductUser(Long id, String username, String password, String role)
    {
        super();
        this.id = id;
        this.username = username;
        this.password = password;
        this.role = role; 
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }   
}

——— eu.mydomain.product.repository.IProductUserRepository.java ——————————

package eu.mydomain.product.repository;

import org.springframework.data.repository.CrudRepository;
import eu.mydomain.product.domain.ProductUser;

public interface IProductUserRepository extends CrudRepository< ProductUser, Long> {

        ProductUser findByUsername( String username);
}

——— eu.mydomain.product.service.UserDetailServiceImpl.java ——————————

package eu.mydomain.product.service;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import org.springframework.stereotype.Service;

import eu.mydomain.product.domain.ProductUser;
import eu.mydomain.product.repository.IProductUserRepository;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private IProductUserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername( String username) throws UsernameNotFoundException {


        ProductUser productUser = userRepository.findByUsername( username);
        return new User(
                username,
                productUser.getPassword(),
                AuthorityUtils.createAuthorityList( productUser.getRole())      // @TODO: comma separated list of all roles granted
                );
    }

}

Finally, I introduced for security an EncodingFilter and a WebSecurityConfig:

——— eu.mydomain.product.security.EncodingFilter.java ————————————————

package eu.mydomain.product.security;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.springframework.web.filter.GenericFilterBean;

public class EncodingFilter extends GenericFilterBean {

    @Override
    public void doFilter(   ServletRequest request,
            ServletResponse response,
            FilterChain chain
            ) throws IOException, ServletException {

        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");

        chain.doFilter( request, response);
    }

}

——— eu.mydomain.product.security.WebSecurityConfig.java ————————————————

package eu.mydomain.product.security;

import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import eu.nydomain.product.service.UserDetailsServiceImpl;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsServiceImpl;


    @Override
    protected void configure( HttpSecurity http) throws Exception {

        /*
         *  For Tyhmleaf Templates add <meta>-tag to the HTML-Header for the CSRF Token
         * 
         *      <meta name="_csrf" th:content="${_csrf.token}" />
         *      <meta name="_csrf_header" th:content="${_csrf.headerName}" />
         */

        http.addFilterBefore( new EncodingFilter(), ChannelProcessingFilter.class);

        http
            .authorizeRequests()
                .anyRequest().authenticated()
            .and()
                .formLogin()
            .and()
                .httpBasic()
            ;
    }

    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder(16);
    }

    @Autowired
    @Override
    public void configure( AuthenticationManagerBuilder auth) throws Exception {        

        auth 
            .userDetailsService( userDetailsServiceImpl)
            .passwordEncoder( encoder() );      

    }

}

Now, after introducing Security to the Data Service I get the Exception after successful authentication by SpringBoot Security and while before loading the following page.

<!DOCTYPE html5>
<html>
    <head>
        <title>Spring Boot Introduction Sample - Products</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <meta name="_csrf" th:content="${_csrf.token}" />
        <meta name="_csrf_header" th:content="${_csrf.headerName}" />
        <!-- <link rel="stylesheet" type="text/css" media="all" href="../css/my.css" data-th-href="@{/css/my.css}" />  -->
    </head>

    <body>

        <p>This is a <i>Product</i> database - as a Client for the Spring Boot Test Sample for RESTful Product services</p> 

        <table>
            <tr>
                <td>
                    <form action="#" th:action="@{/products}" th:object="${product}" method="get">
                        <input type="submit" value="Show All Products" />
                    </form>
                </td>
                <td>
                    <form action="#" th:action="@{/products4create}" th:object="${product}" method="get">
                        <input type="submit" value="Create a new Product" />
                    </form>
                </td>
            </tr>
        </table>
        <hr/>

        <p>All Products:</p> 
        <table>
            <thead>
                <tr>
                    <th>Id</th>
                    <th>Product id</th>
                    <th>Product Name</th>
                    <th>Product Type</th>
                    <th>Description</th>
                    <th>Brand</th>
                    <th colspan="2">Action</th>
                </tr>
            </thead>
            <tbody>          
                <!-- <tr th:each="product, rowStat: ${products}" th:style="${rowStat.odd} ? 'color: gray' : 'color: blue;'"> -->
                <tr th:each="product : ${products}">
                    <td th:text="${product.content.id}">1</td>
                    <td th:text="${product.content.prodId}">Prod Id</td>
                    <td th:text="${product.content.name}">Name</td>
                    <td th:text="${product.content.type}">Type</td>
                    <td th:text="${product.content.description}">Description</td>
                    <td th:text="${product.content.brand}">Brand</td>
                    <td><a th:href="@{|/products4edit/${product.content.id}|}">Edit</a></td>                    
                    <td><a th:href="@{|/products4delete/${product.content.id}|}">Delete</a></td>
                </tr>   
            </tbody>
        </table>
    </body>
</html>

the mention Exception at the top of this question.

What I tried already:

  • Put the different UTF-8 configurations to files, pom.xml etc.
  • Changed database fields to CHARSET utf8 COLLATE utf8_bin and also CHARSET utf8mb4 COLLATE utf8mb4_bin
  • I implemented my personal login page (and related handling)
  • I identified that the ProductMicroServiceClient after authentication works until calling the ProductMicroService API end-point via:


     ...
            CollectionModel<EntityModel<Product>> productResources = myTraverson
                    .follow( "/products")                                                                   // JSON element             
                    .toObject(new ParameterizedTypeReference<CollectionModel<EntityModel<Product>>>() {});

      ...

which doesn't enter the API end-point:


@GetMapping(value = "/products", produces = "application/hal+json")
public CollectionModel<ProductRepresentationModel> findAll() {

    List<Product> products = new ArrayList<>();
    productRepository.findAll().forEach( (p -> products.add(p)));
    CollectionModel<ProductRepresentationModel> productsModelList = new ProductRepresentationAssembler().toCollectionModel(products);

    productsModelList.add( WebMvcLinkBuilder.linkTo( WebMvcLinkBuilder.methodOn(ProductController.class).findAll()).withRel("/products"));      

    return productsModelList;
}

  • I introduced at the ProductMicroService an access denied handler
    @Override
    protected void configure( HttpSecurity http) throws Exception {

        /*
         *  For Tyhmleaf Templates add <meta>-tag to the HTML-Header for the CSRF Token
         * 
         *      <meta name="_csrf" th:content="${_csrf.token}" />
         *      <meta name="_csrf_header" th:content="${_csrf.headerName}" />
         */

        http.addFilterBefore( new EncodingFilter(), ChannelProcessingFilter.class);

        http
            // .httpBasic()
            // .and()
            .authorizeRequests()
                .anyRequest().authenticated()
            .and()
                .formLogin()
            .and()
                .exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler)
            .and()
                .httpBasic()
            ;

    }

I switched for debugging to call the the API end-points of the ProductMicrosService using Postman App with Basic-Authentication. While debugging I identified that (a) the correct user, (encrypted) password and role(s) get used for authentication (b) no access denied handler gets called (c) Method Call for the API end point ( findAll() method ) doesn't get entered (d) Response Header contains "HttP 401 - Unauthorized" (e) Response in Postman is empty

I assume now that the Exception above gets thrown as result of receiving an empty response and HttP 401 Unauthorizedfrom the API call.

Question to me now: Is there something missing or wrong with the security configuration? Why don't I get an Unauthorized Exception?

The issue with the reported Exception could be resolved now. After certain changes in configuration of the Security the Exception has been resolved. Calling the Data Service API End Point (ProductMicroService) from Postman App works now and provides as response the expected JSON Object. Calling the Data Service API End Point (ProductMicroService) from a Interface Service (ProdctMicroServiceClient) throws another Exception:

401 : [{"timestamp":"2020-04-24T19:22:48.851+0000","status":401,"error":"Unauthorized","message":"Unauthorized","path":"/my-products/products"}]
org.springframework.web.client.HttpClientErrorException$Unauthorized: 401 : [{"timestamp":"2020-04-24T19:22:48.851+0000","status":401,"error":"Unauthorized","message":"Unauthorized","path":"/my-products/products"}]

I am now looking after JWT implementation (works well at the ProductMicroService) and OAuth2 (learning in progress).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM