| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package userguide.springboot.webservices.secure; |
| |
| import java.util.UUID; |
| import java.math.BigDecimal; |
| import java.time.LocalDateTime; |
| import java.time.ZoneId; |
| |
| import org.apache.commons.lang3.RandomStringUtils; |
| |
| import org.apache.logging.log4j.LogManager; |
| import org.apache.logging.log4j.Logger; |
| import org.springframework.security.authentication.dao.DaoAuthenticationProvider; |
| import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; |
| import org.springframework.security.core.Authentication; |
| import org.springframework.security.crypto.password.NoOpPasswordEncoder; |
| |
| import userguide.springboot.security.webservices.WSSecUtils; |
| import userguide.springboot.security.webservices.LoginDTO; |
| import userguide.springboot.security.webservices.RequestAndResponseValidatorFilter; |
| import userguide.springboot.hibernate.dao.SpringSecurityDAOImpl; |
| |
| import javax.servlet.http.HttpServletRequest; |
| |
| import org.apache.axis2.context.MessageContext; |
| import org.apache.axis2.transport.http.HTTPConstants; |
| import org.apache.commons.lang.StringEscapeUtils; |
| import org.owasp.esapi.ESAPI; |
| import org.owasp.esapi.Validator; |
| import org.springframework.beans.factory.annotation.Autowired; |
| import org.springframework.stereotype.Component; |
| |
| @Component |
| public class LoginService { |
| |
| private static final Logger logger = LogManager.getLogger(LoginService.class); |
| |
| @Autowired |
| SpringSecurityDAOImpl springSecurityDAOImpl; |
| |
| @Autowired |
| NoOpPasswordEncoder passwordEncoder; |
| |
| @Autowired |
| private WSSecUtils wssecutils; |
| |
| @SuppressWarnings({ "unchecked", "rawtypes" }) |
| public LoginResponse doLogin(LoginRequest request) { |
| |
| Long startTime = System.currentTimeMillis(); |
| |
| String uuid = UUID.randomUUID().toString(); |
| |
| String logPrefix = "LoginService.doLogin() , " |
| + " , uuid: " + uuid + " , request: " + request.toString() + " , "; |
| |
| logger.warn(logPrefix + "starting ... "); |
| LoginResponse response = new LoginResponse(); |
| |
| try { |
| if (request == null) { |
| logger.error(logPrefix + "returning with failure status on null LoginRequest"); |
| response.setStatus("FAILED"); |
| return response; |
| } |
| if (request.getEmail() == null) { |
| logger.error(logPrefix + "returning with failure status on null email"); |
| response.setStatus("FAILED"); |
| return response; |
| } |
| request.email = request.email.trim(); |
| |
| MessageContext ctx = MessageContext.getCurrentMessageContext(); |
| HttpServletRequest httpServletRequest = (HttpServletRequest) ctx.getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST); |
| if (httpServletRequest == null) { |
| logger.error(logPrefix + "returning with failure status on null httpServletRequest"); |
| response.setStatus("FAILED"); |
| return response; |
| } |
| String currentUserIPAddress = null; |
| |
| if (httpServletRequest.getHeader("X-Forwarded-For") != null) { |
| currentUserIPAddress = httpServletRequest.getHeader("X-Forwarded-For"); |
| } else { |
| logger.warn(logPrefix + "cannot find X-Forwarded-For header, this field is required for proper IP auditing"); |
| currentUserIPAddress = httpServletRequest.getRemoteAddr(); |
| logger.warn(logPrefix + "found currentUserIPAddress from httpServletRequest.getRemoteAddr() :" + currentUserIPAddress); |
| } |
| // All data is evil! |
| Validator validator = ESAPI.validator(); |
| |
| String email = request.getEmail().trim(); |
| boolean emailstatus = validator.isValidInput("userInput", email, "Email", 100 , false); |
| if (!emailstatus) { |
| logger.error(logPrefix + "returning with failure status on invalid email (in quotes): '" + email + "'"); |
| response.setStatus("FAILED"); |
| return response; |
| } |
| |
| String creds = ""; |
| // handle unicode escaped chars that were sent that way for '@' etc |
| if (request.getCredentials().contains("u00")) { |
| String uu = request.getCredentials(); |
| String uut = uu.replaceAll("u00", "\\\\u00"); |
| creds = StringEscapeUtils.unescapeJava(uut); |
| } else { |
| creds = request.getCredentials(); |
| if (logger.isTraceEnabled()) { |
| logger.trace(logPrefix + "found creds: " +creds); |
| } |
| } |
| // passwords require special char's ... just do some minimal validation |
| boolean credentialsstatus = RequestAndResponseValidatorFilter.validate(creds); |
| if (!credentialsstatus || creds.length() > 100) { |
| logger.error(logPrefix + "returning with failure status, credentials failed validation, credentialsstatus: " + credentialsstatus + " , length: " + creds.length()); |
| response.setStatus("FAILED"); |
| |
| return response; |
| } |
| |
| LoginDTO loginDTO = null; |
| try { |
| loginDTO = wssecutils.findUserByEmail(email); |
| } catch (Exception ex) { |
| logger.error(logPrefix + "cannot create LoginDTO from email: " + email + " , " + ex.getMessage(), ex); |
| response.setStatus("FAILED"); |
| return response; |
| } |
| |
| if (loginDTO == null) { |
| logger.error(logPrefix + "returning with failure status on failed creation of LoginDTO from email: " + email); |
| response.setStatus("FAILED"); |
| return response; |
| } |
| |
| logger.warn(logPrefix + "found loginDTO: " + loginDTO.toString()); |
| |
| response.setUuid(uuid); |
| UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(email, creds); |
| |
| logger.warn(logPrefix |
| + "calling authenticate(authRequest) with username: " + email); |
| |
| boolean hasFailedLogin = false; |
| String failedStr = ""; |
| try { |
| // will throw an Exception if it fails |
| DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); |
| daoAuthenticationProvider.setUserDetailsService(springSecurityDAOImpl); |
| daoAuthenticationProvider.setPasswordEncoder(passwordEncoder); |
| Authentication authResult = daoAuthenticationProvider.authenticate(authRequest); |
| logger.warn(logPrefix + "authenticate(authRequest) completed successfully with username: " + email); |
| } catch (Exception ex) { |
| if (ex.getMessage() == null) { |
| failedStr = "Authentication Exception failed state is undefined"; |
| } else { |
| failedStr = ex.getMessage(); |
| } |
| logger.error(logPrefix + "failed: " + failedStr, ex); |
| hasFailedLogin = true; |
| } |
| |
| if (hasFailedLogin) { |
| logger.error(logPrefix + "returning with failure status on failed login"); |
| response.setStatus("LOGIN FAILED"); |
| return response; |
| } |
| |
| |
| if (!generateTokenForReturn(httpServletRequest, request, response, currentUserIPAddress, email, uuid)) { |
| logger.warn(logPrefix + "generateTokenForReturn() failed, goodbye"); |
| response.setStatus("TOKEN GENERION FAILED"); |
| } |
| |
| return response; |
| |
| } catch (Exception ex) { |
| logger.error(logPrefix + "failed: " + ex.getMessage(), ex); |
| response.setStatus("FAILED"); |
| return response; |
| } |
| |
| } |
| |
| @SuppressWarnings({ "unchecked", "rawtypes" }) |
| private boolean generateTokenForReturn(HttpServletRequest httpServletRequest, LoginRequest request, LoginResponse response, String currentUserIPAddress, String email, String uuid) { |
| |
| String logPrefix = "LoginService.generateTokenForReturn() , " |
| + " , uuid: " + uuid + " , "; |
| |
| try { |
| String token = null; |
| |
| // JWT and JWE are specifications to generate and validate tokens |
| // securely, however they require a public / private key pair using |
| // elliptic curve cryptography and that is beyond the scope of this |
| // userguide. |
| // See below: |
| // https://datatracker.ietf.org/doc/html/rfc7516 |
| // https://datatracker.ietf.org/doc/html/rfc7519 |
| |
| // this is an example only for demo purposes - do not use this for |
| // production code |
| token = RandomStringUtils.randomAlphanumeric(20); |
| |
| response.setStatus("OK"); |
| response.setToken(token); |
| |
| return true; |
| |
| } catch (Exception ex) { |
| logger.error(logPrefix + "failed: " + ex.getMessage(), ex); |
| response.setStatus("FAILED"); |
| return false; |
| } |
| |
| } |
| |
| |
| } |