Мне нужна ваша помощь по разрешению для следующего исключения:
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.
Вопрос ко мне сейчас: Что-то отсутствует или неверно в конфигурации безопасности ? Почему я не получаю несанкционированное исключение?