Не найден плагин для разделителя текста / html; charset = UTF-8! Зарегистрированные плагины: [org.springframework.hateoas.mediatype.hal.HalLinkDiscoverer @ - PullRequest
0 голосов
/ 19 апреля 2020

Мне нужна ваша помощь по разрешению для следующего исключения:

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

Я реализовал два WebServices с использованием SpringBoot: Служба данных с именем ProductMicroService и Служба интерфейса с именем ProductMicroServiceClient для потребителей на интерфейсных устройствах.

ProductMicroService реализован на основе JPA и использует в качестве фонового хранилища базу данных SQL (в моем примере - MariaDB). Контроллер предоставляет конечные точки API RESTful в JSON с поддержкой носителей (HATEOAS).

ProductMicroServiceClient использует конечные точки API из ProductMicroService и предоставляет API-интерфейс RESTful для внешних интерфейсов также с поддержкой носителей (HATEOAS).

В моих примерах клиент является веб-браузером, на котором выполняются несколько простых шаблонов Thymleaf.

При запуске реализаций ProductMicroService и ProductMicroServiceClient на моем локальном компьютере все идет хорошо.

Также после Внедряя защиту на основе JDB C для ProductMicroServiceClient, все работает нормально, включая ограничения доступа к конечным точкам API.

Таблицы User и Authority сохраняются в той же MariaDB, что и для службы данных.

Но после введения SecurityService для ProductMicroService после успешной аутентификации я получаю исключение выше (стандартная страница входа из SpringBoot ).

Я использую OpenJDK.

Не могу найти какое-либо направление для решения при поиске в inte rnet.

Какой-то соответствующий код

для ProductMicroServiceClient:

———— пом. 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);        
    }
}

Как говорилось до сих пор, все работает, как ожидалось.

Для ProductMicroService

Я представил представление базы данных V_PRODUCT_USERS, которое предоставляет соответствующие полномочия пользователя из таблиц пользователей и полномочий, и реализовал сущность ProductUser, IProductUserRepository и 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
                );
    }

}

Наконец, я представил для безопасности EncodingFilter и 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() );      

    }

}

Теперь, после введения безопасности в службу данных, я получаю исключение после успешная аутентификация SpringBoot Security и перед загрузкой следующей страницы.

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

Исключение упоминания в верхней части этого вопроса.

Что я уже пробовал:

  • Поместите различные UTF-8 конфигурации в файлы, пом. xml e t c.
  • Изменены поля базы данных на CHARSET utf8 COLLATE utf8_bin , а также CHARSET utf8mb4 COLLATE utf8mb4_bin
  • Я реализовал свою персональную страницу входа ( и связанная с этим обработка)
  • Я определил, что ProductMicroServiceClient после аутентификации работает до вызова конечной точки API ProductMicroService с помощью:


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

      ...

, который не входит в конечную точку API :


@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;
}

  • Я представил в ProductMicroService обработчик отказа в доступе
    @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()
            ;

    }

Я переключился на отладку для вызова конечных точек API ProductMicrosService с помощью Почтальон приложение с Basi c -Аутентификация. Во время отладки я обнаружил, что (а) правильный пароль пользователя (зашифрованный) и роль (и) используются для проверки подлинности (б) не вызывается обработчик отказа в доступе (c) Вызов метода для конечной точки API (метод findAll ()) не вводится (d) Заголовок ответа содержит «HttP 401 - Unauthorized» (e) Ответ в Почтальоне пуст

I Предположим теперь, что вышеприведенное исключение генерируется в результате получения пустого ответа и HttP 401 Unauthorized от вызова API.

Вопрос ко мне сейчас: Что-то отсутствует или неверно в конфигурации безопасности ? Почему я не получаю несанкционированное исключение?

1 Ответ

0 голосов
/ 24 апреля 2020

Проблема с сообщенным исключением теперь может быть решена. После определенных изменений в конфигурации Безопасности исключение было разрешено. Вызов конечной точки API службы данных (ProductMicroService) из приложения Postman теперь работает и предоставляет в качестве ответа ожидаемый объект JSON. При вызове конечной точки API службы данных (ProductMicroService) из службы интерфейса (ProdctMicroServiceClient) выдается еще одно исключение:

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

Сейчас я смотрю после реализации JWT (хорошо работает в ProductMicroService) и OAuth2 (обучение продолжается) ).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...