I have a spring service:
#Service
public class AuthorizationServiceImpl implements AuthorizationService {
#Value("${local.address}")
private String localIP;
private final String LOCALHOST_IPV4 = "127.0.0.1";
private final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1";
#CrossOrigin(origins = "*")
#Override
public boolean isAuthorized(String token, HttpServletRequest request) {
try {
TokenUserModel user = new TokenUserModel();
user.setSessionID("");
user = getTokenUserModel(token);
IServiceAAA client = new ServiceAAA().getBasicHttpBindingIServiceAAA();
int systemId = client.getUserSystemId("InvoiceAdmin");
String xRealIP = request.getRemoteAddr();
String ipAddress = xRealIP.equals(LOCALHOST_IPV4) || xRealIP.equals(LOCALHOST_IPV6) ? localIP : xRealIP;
String userAgent = request.getHeader("User-Agent");
int response = client.checkPermissionAndUserData(user.getSessionID(), "Admin",
userAgent,
ipAddress, systemId, systemId, "");
return response == 0;
} catch (Exception ex) {
throw new AuthorizationException(AuthorizationError.ERR_WHILE_AUTHORISATION);
}
}
}
#Value("${local.address}")
private String localIP; - this line of code didn't working, not populating local.address field from application.properties:
local.address=10.10.16.13
Update:
I have 3 application properties file for spring profiles, named:
application.properties,application-dev.properties,application-prod.properties,
my current active profile is application-dev.properties
('spring.profiles.active=dev' - it's inside of my
application.properties file).
I'm sure that 'local.address'
property is defined in application-dev.properties
local.address=10.10.16.13
AuthorizationService is a just interface that AutheroziationServiceImpl impliments.
public interface AuthorizationService {
boolean isAuthorized(String token, HttpServletRequest request) throws UnsupportedEncodingException;
void logout(String token) throws UnsupportedEncodingException;
TokenUserModel getTokenUserModel(String token) throws UnsupportedEncodingException;
}
I'm using it in constructor of one of controllers:
#Autowired
public AuthorizationController(AuthorizationService authorizationService) {
this.authorizationService = authorizationService;
}
private final AuthorizationService authorizationService;
Yes, it's spring managed bean annotated as #Service
Related
I'm developping a Java library that uses Spring framework.
On this library I created a class that will act as application manager. It uses the singleton design pattern.
This class uses dependency injection for external HTTP calls.
public class SharePointManager {
private static SharePointManager instance = null;
private static IAuthenticationResult iAuthenticationResult;
private static String token;
private static SiteService siteService;
public static SharePointManager getInstance() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
SharePointManager.dummyService = context.getBean(DummyService.class);
SharePointManager.siteService = context.getBean(SiteService.class);
if(instance == null) {
instance = new SharePointManager();
}
return instance;
}
private SharePointManager() {
}
public SharePointSiteResponse getAllSites(SharePointCredentialRequest creds) throws Exception {
if( iAuthenticationResult != null && iAuthenticationResult.accessToken() != null ) {
token = iAuthenticationResult.accessToken();
if (Utils.checkToken(iAuthenticationResult.accessToken())) {
token = Utils.getToken(creds).accessToken();
}
} else {
token = Utils.getToken(creds).accessToken();
}
token = iAuthenticationResult.accessToken();
return siteService.getAllSites(creds, token);
}
}
In my Service layer I also want to do dependency injection with a HttpRequest class :
#Service
public class SiteServiceImpl implements SiteService {
private final HttpRequest httpRequest;
public SiteServiceImpl(HttpRequest httpRequest) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
this.httpRequest = context.getBean(HttpRequest.class);
}
#Override
public SharePointSiteResponse getAllSites(SharePointCredentialRequest credentialRequest, String token) throws Exception {
if(Utils.checkToken(token))
token = Utils.createToken(credentialRequest);
URL url = new URL("https://graph.microsoft.com/v1.0/sites?search=*");
return httpRequest.getAllSitesRequest(token, url);
}
}
In my httpRequest class i just build my calls:
#Component
public class HttpRequest {
private final Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH").disableHtmlEscaping().create();
public SharePointSiteResponse getAllSitesRequest(String token, URL url)
throws IOException, RequestException {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
return getSiteConnection(token, url, conn);
}
....
My AppConfig class is just here for configuration:
#Configuration
#ComponentScan(basePackages = {"fr.dsidiff.sharepoint"})
public class AppConfig {
}
I obtain an error message :
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class org.springframework.beans.CachedIntrospectionResults
at org.springframework.context.support.AbstractApplicationContext.resetCommonCaches(AbstractApplicationContext.java:969)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:608)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:93)
at fr.dsidiff.sharepoint.SharePointManager.getInstance(SharePointManager.java:20)
I tried two solutions that solved my issue in my service layer:
#Service
public class SiteServiceImpl implements SiteService {
private final HttpRequest httpRequest;
#Autowired
public SiteServiceImpl(HttpRequest httpRequest) {
this.httpRequest = httpRequest;
}
...
I also tried javax #Inject too and it works
I didn't expect i could inject using autowired...
I'm using azure keyvault to pull my application properties. I'm using spring #value annotation to set the property value from the keyvault by placing the placeholder in the application.properties file. In my main application context I was able to pull the properties and test the application flow. Were as in test context its throwing some issuing saying vault properties aren't injected. Here is my properties bean class looks like, and the stack trace of the issue. I tried to mock the KeyVaultProperties in the ControllerTest class still having same issue.
KeyVault.java
#Data
#Component
public class KeyVaultProperties {
#Value("${by-pass-token}")
private String token;
#Value("${backend-clients}")
private String clients;
}
ControllerTest.java
#SpringBootTest
#SpringBootConfiguration
#AutoConfigureMockMvc
public class ControllerTest {
#Autowired
Controller controller;
#Autowired
private MockMvc mockMvc;
#Test
public void contextLoads() throws Exception {
assertThat(controller).isNotNull();
}
}
Controller.java
#RestController
#Slf4j
#RequestMapping("/api/test")
public class Controller {
#GetMapping(value = "/hello")
public String getString() {
return "Hello";
}
}
AuthConfiguration.java
#Slf4j
#Component
public class AuthConfiguration extends HandlerInterceptorAdapter {
#Autowired
private KeyVaultProperties keyVaultProperties;
private static final String CORRELATION_ID_LOG_VAR_NAME = "correlationId";
private static final String CORRELATION_ID_HEADER_NAME = "Correlation-Id";
#PostConstruct
public void setup() {
System.out.println("-------#PostConstruct------setup----------------");
sub = keyVaultProperties.getClients();
ByPass = keyVaultProperties.getAuthByPassToken();
}
#Override
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler)
throws Exception {
System.out.println("-------preHandle----------------------");
final Boolean isValidToken;
final String correlationId = getCorrelationIdFromHeader(request);
log.info("correlationId:{}",correlationId);
MDC.put(CORRELATION_ID_LOG_VAR_NAME, correlationId);
return true;
}
#Override
public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response,
final Object handler, final Exception ex) {
System.out.println("-------afterCompletion----------------------");
MDC.remove(CORRELATION_ID_LOG_VAR_NAME);
}
private String getCorrelationIdFromHeader(final HttpServletRequest request) {
String correlationId = request.getHeader(CORRELATION_ID_HEADER_NAME);
if (correlationId == null) {
correlationId = generateUniqueCorrelationId();
}
return correlationId;
}
}
app/src/main/resources/application.properties
by-pass-token = ${BY-PASS-TOKEN}
backend-clients = ${CLIENTS}
azure.keyvault.enabled=true
Stack Trace:
2021-04-04 13:28:03.640 [main] ERROR org.springframework.boot.SpringApplication - Application run failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'AuthConfiguration': Unsatisfied dependency expressed through field 'KeyVaultProperties'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'KeyVaultProperties': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'by-pass-token' in value "${by-pass-token}"
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject
You could set the value of properties to Azure Key Vault by authenticating via Azure AD.
Note: In order for your application to have access to the Key Vault contents, you must set the appropriate permissions for your application in the Key Vault. Navigate to Azure Key Vault > Access Policies > Add access policy > select your application in select principal.
Dependencies:
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-keyvault</artifactId>
<version>1.0.0</version>
</dependency>
Connect to key vault via AzureAD based on client credentials flow:
public class ClientSecretKeyVaultCredential extends KeyVaultCredentials
{
private String clientId;
private String clientKey;
public ClientSecretKeyVaultCredential( String clientId, String clientKey ) {
this.clientId = clientId;
this.clientKey = clientKey;
}
#Override
public String doAuthenticate(String authorization, String resource, String scope) {
AuthenticationResult token = getAccessTokenFromClientCredentials(
authorization, resource, clientId, clientKey);
return token.getAccessToken();
}
private static AuthenticationResult getAccessTokenFromClientCredentials(
String authorization, String resource, String clientId, String clientKey) {
AuthenticationContext context = null;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(authorization, false, service);
ClientCredential credentials = new ClientCredential(clientId, clientKey);
Future<AuthenticationResult> future = context.acquireToken(
resource, credentials, null);
result = future.get();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
service.shutdown();
}
if (result == null) {
throw new RuntimeException("authentication result was null");
}
return result;
}
}
Access Key vault:
You could use client.setSecret("Secret-Name", "value") to set your properties.
// ClientSecretKeyVaultCredential is the implementation of KeyVaultCredentials
KeyVaultClient client = new KeyVaultClient(
new ClientSecretKeyVaultCredential(clientId, clientKey));
// KEYVAULT_URL is the location of the keyvault to use: https://<yourkeyvault>.vault.azure.net
SecretBundle secret = client.getSecret( KEYVAULT_URL, "Secret-name" );
log( secret.value() );
I am working with Spring boot and Spring security to build a back end application, I am storing users into Cloud Firestore NonRelational Database, I am using an Admin SDK token provided by Google on the Firebase platform. I am initializing my Firestore in the following way.
#Service
public class UserFirestoreInitialize {
#Value("classpath:static/gamingplatform-c922d-firebase-adminsdk-c25o8-06e92edfd5.json")
Resource resourceFile;
#PostConstruct
public void initialize() {
try {
InputStream serviceAccount = resourceFile.getInputStream();
GoogleCredentials cred = GoogleCredentials.fromStream(serviceAccount)
.createScoped("https://www.googleapis.com/auth/datastore");
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(cred)
.setDatabaseUrl("FIREBASE_URL")
.build();
FirebaseApp.initializeApp(options);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
I also had a Configuration class which looks like this.
#Configuration
public class UserFirestoreConfiguration{
#Bean
public Firestore getFirestore(){
return FirestoreClient.getFirestore();
}
}
After this, I could easily use this bean in my UserService as follows:
#Service
public class UserService{
#Autowired
private Firestore firestore;
public User getUser(){
//Query for user.
}
//Post, Put, delete
}
This worked at some point. The problem came when I added Spring Security into my applicatio. When I ran a Maven Install, the application did not build, the problem looks like this:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'jwtTokenFilter': Unsatisfied dependency expressed through field 'userDetailsService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userServiceDetailsImpl' defined in file [/Users/igorzelaya/SoftwareDev/D1Gaming-User-Back-end1/target/classes/com/d1gaming/user/security/UserServiceDetailsImpl.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userService' defined in file [/Users/igorzelaya/SoftwareDev/D1Gaming-User-Back-end1/target/classes/com/d1gaming/user/user/UserService.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.d1gaming.user.user.UserService]: Constructor threw exception; nested exception is java.lang.IllegalStateException: FirebaseApp with name [DEFAULT] doesn't exist.
I have a tried to isolate the problem, I created the another project and tried connecting to my database like I did in the snippet above^, everything worked just fine, I then copied my configuration classes one by one, I figured my application started to crash the moment I added my JWT Token Filter class, this class looks like this
#Component
public class JwtTokenFilter extends OncePerRequestFilter{
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private UserServiceDetailsImpl userDetailsService;
#Override
//Get authorization header and validate it.
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException, NullPointerException {
try {
String jwt = parseJwt(request);
if(jwt != null && jwtTokenUtil.validate(jwt)) {
String username = jwtTokenUtil.getUserNameFromJwtToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
catch(Exception e) {
logger.error("Cannot set user authentication: {}", e);
}
chain.doFilter(request, response);
}
private String parseJwt(HttpServletRequest request) {
String headerAuth = request.getHeader("Authorization");
if(StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7, headerAuth.length());
}
return null;
}
}
As you can notice on my stack trace shown above, the error starts on the dependencies injected in this class, the jwtTokenUtil class and the UserDetailsImpl
the JwtTokenUtil class is this:
#Component
public class JwtTokenUtil {
#Value("${app.jwtSecret}")
private String jwtSecret;
#Value("${app.jwtExpirationMs}")
private int jwtExpirationMs;
private final Logger logger = LoggerFactory.getLogger(JwtTokenUtil.class);
public String generateJwtToken(Authentication authentication) {
UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();
return Jwts.builder()
.setSubject(userPrincipal.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public String getUserNameFromJwtToken(String token) {
return Jwts.parser().setSigningKey(jwtSecret)
.parseClaimsJws(token).getBody().getSubject();
}
public String getUserId(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
return claims.getSubject().split(",")[0];
}
public String getUsername(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
return claims.getSubject().split(",")[0];
}
public Date getExpirationDate(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
return claims.getExpiration();
}
public boolean validate(String token) {
try {
Jwts.parser()
.setSigningKey(jwtSecret).parseClaimsJws(token);
return true;
}
catch(SignatureException e) {
logger.error("Invalid JWT signature - {}",e.getMessage());
}
catch(MalformedJwtException e) {
logger.error("Invalid JWT token - {}", e.getMessage());
}
catch(ExpiredJwtException e) {
logger.error("Invalid JWT token - {}",e.getMessage());
}
catch(UnsupportedJwtException e) {
logger.error("Invalid JWT token - {}", e.getMessage());
}
catch(IllegalArgumentException e) {
logger.error("Invalid JWT token - {}", e.getMessage());
}
return false;
}
}
The UserServiceDetailsImpl class:
public class UserDetailsImpl implements UserDetails{
private static final long serialVersionUID = 1L;
private String userId;
private String userRealName;
private String userName;
#JsonIgnore
private String userPassword;
private String userEmail;
private UserStatus userStatusCode;
private Team userTeam;
private Map<String,Object> userBilling;
private String userCountry;
private int userTokens;
private double userCash;
private Map<String, Object> userBirthDate;
private Collection<? extends GrantedAuthority> authorities;
public UserDetailsImpl(String userId, String userRealName, String userName, String userPassword, String userEmail,
UserStatus userStatusCode, Team userTeam, Map<String, Object> userBilling, String userCountry,
int userTokens, double userCash, Map<String, Object> userBirthDate,
Collection<? extends GrantedAuthority> authorities) {
this.userId = userId;
this.userRealName = userRealName;
this.userName = userName;
this.userPassword = userPassword;
this.userEmail = userEmail;
this.userStatusCode = userStatusCode;
this.userTeam = userTeam;
this.userBilling = userBilling;
this.userCountry = userCountry;
this.userTokens = userTokens;
this.userCash = userCash;
this.userBirthDate = userBirthDate;
this.authorities = authorities;
}
public static UserDetailsImpl build(User user) {
List<GrantedAuthority> authorities = user.getUserRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getRoleType().name()))
.collect(Collectors.toList());
return new UserDetailsImpl(user.getUserId(),user.getUserRealName(),user.getUserName(),user.getUserPassword(),user.getUserEmail()
,user.getStatusCode(),user.getUserTeam(),user.getUserBilling(),user.getUserCountry(),
user.getUserTokens(),user.getUserCash(),user.getUserBirthDate(),authorities
);
}
//Getters and setters
}
As you can see, these classes both have nothing to do with Firestore, At the moment I was pretty confused so I did the following. I noticed that for some strange reason the #PostConstruct annotation was not working, Spring was not initializing my initializeFirestore() method on the first snippet of code, so I placed a initialize() method directly on spring boot as follows:
#SpringBootApplication
public class UserApplication(){
public static void main(String[] args) {
//Basically does the same as the initialize method mentioned before.
UserFirestoreUtils.initialize(UserFirestoreUtils.getOptions());
SpringApplication.run(D1GamingUserBackEnd1Application.class, args);
}
}
The interesting thing is that when I debugged this I had the following error:
The dependencies of some of the beans in the application context form a cycle:
jwtTokenFilter (field private com.d1gaming.user.security.UserServiceDetailsImpl com.d1gaming.user.security.JwtTokenFilter.userDetailsService)
┌─────┐
| userServiceDetailsImpl defined in file [/Users/igorzelaya/SoftwareDev/D1Gaming-User-Back-end1/target/classes/com/d1gaming/user/security/UserServiceDetailsImpl.class]
↑ ↓
| userService (field private org.springframework.security.crypto.password.PasswordEncoder com.d1gaming.user.user.UserService.passwordEncoder)
↑ ↓
| userSecurityConfiguration (field com.d1gaming.user.security.UserServiceDetailsImpl com.d1gaming.user.security.UserSecurityConfiguration.userDetailsService)
└─────┘
The weird thing is that when I run a Maven install on it, the stack trace is the same as the one I showed at the beginning of the question, I feel there is not enough documentation on Cloud Firestore and its normal considering it is "New", but still it was annoying because I couldn't find the "Right way" of doing this and Google Documentation was clearly not enough. I am sorry If I included way too much code, I think it is necessary, but anyways thank you for your time, I appreciate if anyone could help me. Have a nice day.
This looks like a collision between Spring dependencies and Firebase Admin. You can try with Firestore client library. Firebase admin SDK is a wider library that some part of code can collision with Spring security
I am writing Unit Tests for the below REST Controller which takes a UserID and grants a List of Authorities to that user.
#RestController
#RequestMapping("/user")
#Api(value = "User", description = "User API")
public class UserController{
// some code
#RequestMapping(method = RequestMethod.POST, value = "/{userId}/grantAuthz")
#ApiOperation(value = "GrantAuthz", notes = "Grant Authorization")
public Collection<UserEntity.UserAuthz> grantAuthz(#PathVariable("userId") String userId,
#RequestBody ArrayList<String> authorities) {
UserEntity userEntity = userRepository.findOne(userId);
if(userEntity == null) {
//TODO: throw and send resource not found
return null;
}
log.debug("Authorities to be granted to user " + userId + " are : " + authorities);
for(String authz : authorities) {
log.debug("Adding Authorization " + authz);
userEntity.addUserAuthz(authz);
}
userRepository.save(userEntity);
return userEntity.getAuthorities();
}
}
I wrote the below Unit Test for the UserController
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
public class UserControllerTest {
private final Log log = LogFactory.getLog(getClass());
private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
Charset.forName("utf8"));
private MockMvc mockMvc;
private HttpMessageConverter mappingJackson2HttpMessageConverter;
private final String USER_URL = "/{userId}/grantAuthz";
private final String USER_ID = "111";
private final String USER_NAME = "MockUser";
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private UserRepository userRepository;
private String createdToken = null;
#Autowired
void setConverters(HttpMessageConverter<?>[] converters) {
this.mappingJackson2HttpMessageConverter = Arrays.asList(converters).stream().filter(
hmc -> hmc instanceof MappingJackson2HttpMessageConverter).findAny().get();
Assert.assertNotNull("the JSON message converter must not be null",
this.mappingJackson2HttpMessageConverter);
}
#Before
public void setup() throws Exception {
this.mockMvc = webAppContextSetup(webApplicationContext).build();
}
#Test
public void testGrantAuthorizationForUser() throws Exception{
Optional<UserEntity> userEntityAuthz = userRepository.findOneByUsername(USER_NAME);
Set<String> expectedAuthzList = (LinkedHashSet)userEntityAuthz.get().getAuthorizations();
List<String> grantList = new ArrayList<>();
grantList.add("ABC");
grantList.add("DEF");
grantList.add("GHI");
grantList.add("JKL");
grantList.add("MNO");
grantList.add("PQR");
grantList.add("STU");
grantList.add("VWX");
grantList.add("YZA");
JSONObject json = new JSONObject();
json.put("grantList",grantList);
MvcResult grantAuthzResult = mockMvc.perform(MockMvcRequestBuilders.post(USER_URL)
.contentType(contentType)
.param("userId",USER_ID)
.param("authorities",json.toString()))
.andExpect(status().isOk())
.andDo(print())
.andReturn();
}
}
When executed, my test is throwing an Illegal Argument Exception:
"Not enough variable values available to expand 'userId'"
I am sending the required URL Parameters using the .param() method in the test, what am I doing wrong ? I reffered this possible duplicate question but did not find it much useful. Using RestTemplate in Spring. Exception- Not enough variables available to expand
I found out what I am doing wrong, using param() method is not the right way here as I have #PathVariable and #RequestBody in my Controller Methods as the parameters.
public Collection<UserEntity.UserAuthz> grantAuthz(#PathVariable("userId") String userId,
#RequestBody ArrayList<String> authorities) {
So I passed the #PathVariable in the post() method of the test.
MockMvcRequestBuilders.post(USER_URL,USER_ID)
As the required type is #RequestBody ArrayList<String> instead of using the JSONObject I used JSONArrayand used the content() method to send the JSONArray as the string.
Here are the changes I have made to the Test Method.
#Test
public void testGrantAuthorizationForUser() throws Exception{
Optional<UserEntity> userEntityAuthz = userRepository.findOneByUsername(USER_NAME);
Set<String> expectedAuthzList = (LinkedHashSet)userEntityAuthz.get().getAuthorizations();
List<String> grantList = new ArrayList<>();
grantList.add("ABC");
grantList.add("DEF");
grantList.add("GHI");
grantList.add("JKL");
grantList.add("MNO");
grantList.add("PQR");
grantList.add("STU");
grantList.add("VWX");
grantList.add("YZA");
JSONArray json = new JSONArray();
MvcResult grantAuthzResult = mockMvc.perform(MockMvcRequestBuilders.post(USER_URL,USER_ID)
.contentType(contentType)
.content(json.toString()))
.andExpect(status().isOk())
.andDo(print())
.andReturn();
}
#Test
public void getOneContactAPI() throws Exception {
String id = "8";
mvc.perform(MockMvcRequestBuilders.get("/api/contact/{id}",id).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("id").exists());
}
I'm using spring boot and i need to implement spring security with 3 fields authentication process username, password and corporate identifier as a hidden input in a form.
I implemented a custom usernamepasswordauthenticationfilter but it not seems to be enough to setup the security config.
EDIT :
Users don't seem to be authenticated ! because a can access to authenticated request defined in web config
EDIT 2 :
in my custom filter when a enter a valid user it's do execute on succesfulAuthentication. What i'm missing please provide me any help :(
Here were i am
#Repository
public class AuthenticationUserDetailsService implements UserDetailsService {
private static final Logger LOGGER = Logger.getLogger(AuthenticationUserDetailsService.class);
#Autowired
private UserRepository users;
private org.springframework.security.core.userdetails.User userdetails;
#Override
public UserDetails loadUserByUsername(String input) throws UsernameNotFoundException {
// TODO Auto-generated method stub
System.out.println(input);
String[] split = input.split(":");
if (split.length < 2) {
LOGGER.debug("User did not enter both username and corporate domain.");
throw new UsernameNotFoundException("no corporate identifier is specified");
}
String username = split[0];
String corporateId = split[1];
System.out.println("Username = " + username);
System.out.println("Corporate identifier = " + corporateId);
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
com.ubleam.corporate.server.model.User user;
user = checkUserDetail(username, corporateId);
if (user == null)
throw new NotAuthorizedException("Your are not allowed to access to this resource");
LOGGER.info("User email : " + user.getEmail() + "#User corporate : " + user.getCorporateId());
userdetails = new User(user.getEmail(), user.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, getAuthorities("ROLE_USER"));
return userdetails;
}
/**
*
* #param roles
* roles granted for user
* #return List of granted authorities
*
*/
public List<GrantedAuthority> getAuthorities(String roles) {
List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
authList.add(new SimpleGrantedAuthority(roles));
return authList;
}
/**
* User authentication details from database
*
* #param username
* to use for authentication
* #param coporateId
* corporate identifier of user
* #return found user in database
*/
private com.ubleam.corporate.server.model.User checkUserDetail(String username, String corporateId) {
com.ubleam.corporate.server.model.User user = users.findByEmailAndCorporateId(username, corporateId);
return user;
}
My custom filter :
public class PlatformAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static final Logger LOGGER = Logger.getLogger(PlatformAuthenticationFilter.class);
private static final String LOGIN_SUCCESS_URL = "{0}/bleamcards/{1}/home";
private static final String LOGIN_ERROR_URL = "{0}/bleamcards/{1}/login?error";
private String parameter = "corporateId";
private String delimiter = ":";
private String corporateId;
#Override
protected String obtainUsername(HttpServletRequest request) {
String username = request.getParameter(getUsernameParameter());
String extraInput = request.getParameter(getParameter());
String combinedUsername = username + getDelimiter() + extraInput;
setCorporateId(extraInput);
LOGGER.info("Combined username = " + combinedUsername);
return combinedUsername;
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException, ServletException {
String contextPath = request.getContextPath();
String url = MessageFormat.format(LOGIN_SUCCESS_URL, contextPath, corporateId);
response.sendRedirect(url);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
String contextPath = request.getContextPath();
String url = MessageFormat.format(LOGIN_ERROR_URL, contextPath, corporateId);
response.sendRedirect(url);
}
public String getParameter() {
return parameter;
}
public void setParameter(String corporateId) {
this.parameter = corporateId;
}
public String getDelimiter() {
return delimiter;
}
public void setDelimiter(String delimiter) {
this.delimiter = delimiter;
}
public String getCorporateId() {
return corporateId;
}
public void setCorporateId(String corporateId) {
this.corporateId = corporateId;
}
}
And finally the web security config :
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Inject
private AuthenticationManagerBuilder auth;
#Inject
private UserDetailsService userDS;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/bleamcards/**/login", "/bleamcards/**/forgetpassword", "/bleamcards/**/register", "/css/**", "/js/**", "/images/**", "/webjars/**")
.permitAll().anyRequest().authenticated().and().addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class).formLogin().loginPage("/login")
.defaultSuccessUrl("/").permitAll().and().logout().permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.eraseCredentials(false);
auth.userDetailsService(userDS).passwordEncoder(new BCryptPasswordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManager() throws Exception {
return auth.build();
}
#Bean
public PlatformAuthenticationFilter authenticationFilter() throws Exception {
PlatformAuthenticationFilter authFilter = new PlatformAuthenticationFilter();
authFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login", "POST"));
authFilter.setAuthenticationManager(authenticationManager());
authFilter.setUsernameParameter("username");
authFilter.setPasswordParameter("password");
authFilter.setParameter("corporateId");
return authFilter;
}
#Override
protected UserDetailsService userDetailsService() {
return userDS;
}
I want users to be able to connect only to /login /register /forgetpasswod urls for their respective corporate platforms
Actually i manage to find a solution to my issue.
I added successHandler on successfulAuthentication was missing ! And a failureHandler too on unsuccessfulAuthentication methods.
Here is my new Authentication filter :
public class TwoFactorAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static final String LOGIN_SUCCESS_URL = "{0}/bleamcards/{1}/home";
private static final String LOGIN_ERROR_URL = "{0}/bleamcards/{1}/login?error";
private String parameter = "corporateId";
private String delimiter = ":";
private String corporateId;
#Override
protected String obtainUsername(HttpServletRequest request) {
String username = request.getParameter(getUsernameParameter());
String extraInput = request.getParameter(getParameter());
String combinedUsername = username + getDelimiter() + extraInput;
setCorporateId(extraInput);
System.out.println("Combined username = " + combinedUsername);
return combinedUsername;
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain , Authentication authResult) throws IOException, ServletException {
String contextPath = request.getContextPath();
String url = MessageFormat.format(LOGIN_SUCCESS_URL, contextPath, corporateId);
setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler(url));
super.successfulAuthentication(request, response, chain, authResult);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
String contextPath = request.getContextPath();
String url = MessageFormat.format(LOGIN_ERROR_URL, contextPath, corporateId);
setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(url));
super.unsuccessfulAuthentication(request, response, failed);
}
public String getParameter() {
return parameter;
}
public void setParameter(String corporateId) {
this.parameter = corporateId;
}
public String getDelimiter() {
return delimiter;
}
public void setDelimiter(String delimiter) {
this.delimiter = delimiter;
}
public String getCorporateId() {
return corporateId;
}
public void setCorporateId(String corporateId) {
this.corporateId = corporateId;
}
}
Did you check that your AuthenticationUserDetailsService code is actually been executed? If the framework is not invoking it this means that your configuration is not properly hooking that UserDetailsService. In your WebSecurityConfig I think you need to have this:
#Bean
public AuthenticationManager getAuthenticationManager() throws Exception {
return super.authenticationManagerBean(); //not return auth.build();
}
I suggest you to take a look at this branch from Stormpath. There, they are configuring Spring Boot to use a custom AuthenticationProvider (similar to an UserDetailsService). That module uses and depends on this other Spring Security module.
Then, this sample Spring Security Example (note that it is not Spring Boot, but just Spring) will give you a complete example of the way the Spring Security Java Config is done. Please note that this Java Config extends this one which actually hides much of the actual internal configuration.
Disclaimer, I am an active Stormpath contributor.