В моей локальной среде приложение работает без проблем после внесения изменений. Однако при запуске приложения в развертывании я могу перейти на страницу входа, но каждая конечная точка перенаправляется на несуществующую конечную точку входа «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;
}
}
Код: Выделить всё
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");
}
}
Код: Выделить всё
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);
}
}
}
}
Код: Выделить всё
@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);
}
}
Код: Выделить всё
@Bean
public AuthenticationManager authManager(UserDetailsService userService, PasswordEncoder passEncoder) {
DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider(userDetailsService);
daoProvider.setPasswordEncoder(passEncoder);
return new ProviderManager(daoProvider);
}
Код: Выделить всё
http.addFilterBefore(new CustomAuthenticationFilter(authManager(userDetailsService(), customPasswordEncoder())), UsernamePasswordAuthenticationFilter.class);
Код: Выделить всё
.formLogin(form -> form.loginPage("/Login").permitAll())
Прошу прощения, если что-то покажется вам непонятным. Я исправлю все, что неверно, чтобы внести больше ясности. Любая помощь приветствуется, спасибо.
Подробнее здесь: https://stackoverflow.com/questions/797 ... login-page
Мобильная версия