Конфигурация Spring Security всегда перенаправляется на страницу входа в системуJAVA

Программисты JAVA общаются здесь
Ответить
Anonymous
 Конфигурация Spring Security всегда перенаправляется на страницу входа в систему

Сообщение Anonymous »

Недавно я обновил Java, Springboot, Sprint Security и Tomcat до версий 21, 3.5.6, 6.5.5 и 10 соответственно с версий 8, 2.7.5, 5.3.23 и 9 соответственно, и в результате я обновляю свою конфигурацию безопасности.
В моей локальной среде приложение работает без проблем после внесения изменений. Однако при запуске приложения в развертывании я могу перейти на страницу входа, но каждая конечная точка перенаправляется на несуществующую конечную точку входа «http://localhost:8080/custom-context/login», и я не могу войти в систему. Моя страница входа — «http://localhost:8080/Login». Я обнаружил, что проблема связана с авторизацией и аутентификацией для моих конечных точек, но не смог выяснить, в чем именно заключается проблема и как ее решить.
Я думаю, что проблема многогранна и заключается в разных областях процесса аутентификации и авторизации, как в том, как я настроил Spring Security, так и в моих пользовательских фильтрах.
Я попытался изменить свой конфигурация с несколькими предложениями из текущей документации Springboot и Spring Security, в частности, касающимися настройки класса SecurityConfig. Ниже приведен мой текущий класс SecurityConfig.

Код: Выделить всё

@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean(name="customUserDetailsService")
public AccessController userDetailsService() {return new AccessController();}

@Bean
public BCryptPasswordEncoder customPasswordEncoder() {return new BCryptPasswordEncoder();}

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(AbstractHttpConfigurer::disable).authorizeHttpRequests(auth -> auth
.requestMatchers("/access/token", "/access/login", "/access/license"
"/access/refreshToken", "/licensing/**", "/ui/saveChecksumLogs",
"/ws-message/**", "/swagger-ui/**", "/v3/**", "/ui/disabledProfileUpdatedStatus",
"/ui/profileUpdatedStatus", "/ui/test").permitAll()
.anyRequest().authenticated()
).sessionManagement(httpSecuritySessionManagementConfigurer ->
httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

http.with(new CustomAuthenticationManager(), customAuthenticationManager -> {});
http.addFilterBefore(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
http.addFilterAfter(new DomainAuthorizationFilter(), BasicAuthenticationFilter.class);
http.addFilterAfter(new RoleAuthorizationFilter(), BasicAuthenticationFilter.class);

return http.build();
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
config.setAllowedOriginPatterns(List.of("http://*:8080", "http://*:3000", "https://*:443", "https://*:8443"));
config.setAllowCredentials(true);
config.setAllowedHeaders(List.of("*"));
config.setExposedHeaders(List.of("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}

class CustomAuthenticationManager extends AbstractHttpConfigurer  {
@Autowired
UserDetailsService service;

@Override
public void configure(HttpSecurity http) throws Exception {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(service);
provider.setPasswordEncoder(htePasswordEncoder());
CustomAuthenticationFilter auth = new CustomAuthenticationFilter(new ProviderManager(provider));
auth.setFilterProcessesUrl("/access/login");
http.addFilter(auth);
}
}

public CustomAuthenticationFilter customAuthenticationFilter() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(userDetailsService());
provider.setPasswordEncoder(customPasswordEncoder());
CustomAuthenticationFilter auth = new CustomAuthenticationFilter(new ProviderManager(provider));
auth.setfilterProcessesUrl("/access/login");
return auth;
}
}
Далее идет мой класс CustomAuthenticationFilter.

Код: Выделить всё

public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private LicenseHelper licenseHelper;
private DomainHelper domainHelper;
private UIController uiController;
public CustomAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {

if (licenseHelper == null) { // This class type can't use injection, so do a lazy set
ServletContext servletContext = request.getServletContext();
WebApplicationContext webApplicationContext = WebApplicationContextUtils
.getWebApplicationContext(servletContext);
assert webApplicationContext != null;
licenseHelper = webApplicationContext.getBean(LicenseHelper.class);
}

if (domainHelper == null) { // This class type can't use injection, so do a lazy set
ServletContext servletContext = request.getServletContext();
WebApplicationContext webApplicationContext = WebApplicationContextUtils
.getWebApplicationContext(servletContext);
assert webApplicationContext != null;
domainHelper = webApplicationContext.getBean(DomainHelper.class);
}

String username = request.getParameter("username");
String password = request.getParameter("password");
String decodedUser;
String decodedPass;
try {
decodedUser = URLDecoder.decode(username, StandardCharsets.UTF_8.name());
decodedPass = URLDecoder.decode(password, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException ex) {
throw new BadCredentialsException(ex.getMessage(), ex);
}

UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(decodedUser,
decodedPass);
return authenticationManager.authenticate(authenticationToken);

}

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
User user = (User) authResult.getPrincipal();
// TODO Save token string elsewhere
TokenHelper tokenHelper = new TokenHelper(user, request);
String access_token = tokenHelper.createAccessToken();
String refresh_token = tokenHelper.createRefreshToken();

// User has logged in, check if they're enabled before granting access
boolean isEnabled = false;
for (GrantedAuthority auth : user.getAuthorities()) {
String authority = auth.getAuthority();
String userEnabled = authority.substring(USER_ENABLED_SUBSTRING.length());
if (userEnabled.equals("true") || userEnabled.equals("TRUE")) {
isEnabled = true;
break;
}
}

// If admin then allow the login
if (!licenseHelper.validLicenseInstalled() &&  !domainHelper.isRegisteredHobartAdmin(user.getUsername())) {
response.setStatus(FORBIDDEN.value());
response.setContentType(APPLICATION_JSON_VALUE);
response.setHeader("Access-Control-Allow-Origin", "*");
OperationStatusModel result = new OperationStatusModel("Login");
result.setResult(RequestOperationResult.ERROR.name());
result.setErrorDescription("A valid license is not installed");
new ObjectMapper().writeValue(response.getOutputStream(), result);
logger.error("A valid license is not installed, prevent login");
return;
}

if (!isEnabled) {
response.setStatus(FORBIDDEN.value());
response.setContentType(APPLICATION_JSON_VALUE);
response.setHeader("Access-Control-Allow-Origin", "*");
OperationStatusModel result = new OperationStatusModel("Login");
result.setResult(RequestOperationResult.ERROR.name());
result.setErrorDescription("Could not login, user is not enabled");
new ObjectMapper().writeValue(response.getOutputStream(), result);
logger.error("User is not enabled, cannot login");
return;
}

logger.info("Login Success!");
Map tokens = new HashMap();
response.setHeader("Access-Control-Allow-Origin", "*");

tokens.put("access_token", access_token);
tokens.put("refresh_token", refresh_token);
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), tokens);

}

@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {

response.setStatus(FORBIDDEN.value());
response.setContentType(APPLICATION_JSON_VALUE);
response.setHeader("Access-Control-Allow-Origin", "*");
OperationStatusModel result = new OperationStatusModel("Login");
result.setResult(RequestOperationResult.ERROR.name());
result.setErrorDescription("Could not login, incorrect username or password");
new ObjectMapper().writeValue(response.getOutputStream(), result);
logger.error("Unsuccessful Login attempt");
}
}
Далее идет мой класс CustomAuthorizationFilter.

Код: Выделить всё

public class CustomAuthorizationFilter extends OncePerRequestFilter {

@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (request.getServletPath().equals("/access/login") ||
request.getServletPath().equals("/access/refreshToken") ||
request.getServletPath().equals("/access/license") ||
request.getServletPath().equals("/ui/test")) {
filterChain.doFilter(request, response);
} else {
String authorizationHeader = request.getHeader(AUTHORIZATION);
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
try {
String token = authorizationHeader.substring("Bearer ".length());
Algorithm algorithm = Algorithm.HMAC256(TokenHelper.secret);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = verifier.verify(token);
String username = decodedJWT.getSubject();
String[] roles = decodedJWT.getClaim("roles").asArray(String.class);

Collection authorities = new ArrayList();
stream(roles).forEach(role ->  {
authorities.add(new SimpleGrantedAuthority(role));
});
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
} catch (Exception e) {
logger.error("ERROR Authenticating : " + e.getMessage());

response.setStatus(FORBIDDEN.value());
response.setContentType(APPLICATION_JSON_VALUE);

OperationStatusModel error = new OperationStatusModel(request.getMethod());
error.setResult(RequestOperationResult.ERROR.name());
error.setErrorDescription(e.getMessage());
new ObjectMapper().writeValue(response.getOutputStream(), error);
}
} else {
filterChain.doFilter(request, response);
}
}
}
}
И наконец, мой класс AccessController, демонстрирующий реализацию UserDetailsService. Класс TokenHelper генерирует JWT с необходимыми полномочиями.

Код: Выделить всё

@Tag(name = "access", description = "Endpoints for logging in and verifying access of the user")
@RestController("customUserDetailsService")
@RequestMapping("/access")
@CrossOrigin(origins = "*", allowedHeaders = "*") // added to let react reach the service correctly
public class AccessController implements UserDetailsService {
private static final Logger log = LogManager.getLogger(AccessController.class);

@Autowired
UserRepository user_repo;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
OperationStatusModel result = new OperationStatusModel("RequestLogin");
HTeUser user = user_repo.findByUsername(username);
if (user == null) {
log.error("User not found: " + username);
result.setErrorDescription("The Name fields cannot be null or empty.");
result.setResult(RequestOperationResult.ERROR.name());
throw new UsernameNotFoundException("User not found in the database");
} else {
log.info("User found in database: " + username + " and domainId: " + user.getDomain().getDomainId());

}
TokenHelper tokenHelper = new TokenHelper(user, null);

Collection authorities = tokenHelper.generateAuthorities();

return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
authorities);
}
}
Одна из таких попыток исправить проблему заключалась в использовании приведенного ниже кода в SecurityConfig и замене реализации CustomAuthenticationManager подходом, более стилизованным под Spring Security. Опять же, это не сработало, и я все равно получил перенаправления.

Код: Выделить всё

@Bean
public AuthenticationManager authManager(UserDetailsService userService, PasswordEncoder passEncoder) {
DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider(userDetailsService);
daoProvider.setPasswordEncoder(passEncoder);
return new ProviderManager(daoProvider);
}
Затем я попытался добавить этот AuthenticationManager в качестве параметра для моего CustomAuthenticationFilter, но это не сработало.

Код: Выделить всё

http.addFilterBefore(new CustomAuthenticationFilter(authManager(userDetailsService(), customPasswordEncoder())), UsernamePasswordAuthenticationFilter.class);
Я также пытался указать URL-адрес страницы входа, но это не сработало. Это было между отключением CSRF и определениями сопоставителей запросов. Я могу без проблем открыть страницу в своем браузере, но любая попытка входа приведет к перенаправлению.

Код: Выделить всё

.formLogin(form -> form.loginPage("/Login").permitAll())
Наконец, я попытался просмотреть журналы своего проекта и журналы сервера Tomcat, но не смог найти ничего, что указывало бы на то, в чем может быть проблема. При необходимости я могу опубликовать их.
Прошу прощения, если что-то покажется вам непонятным. Я исправлю все, что неверно, чтобы внести больше ясности. Любая помощь приветствуется, спасибо.

Подробнее здесь: https://stackoverflow.com/questions/797 ... login-page
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «JAVA»