Spring security get Wrong Principal in Concurrency Environment - java

In my micro service, I have a ResourceServer and an AuthServer at port 19000.
In ResourceServer here is the part of the application.yml
security:
oauth2:
resource:
id: gateway
user-info-uri: http://localhost:19000/user
prefer-token-info: false
the /user end point is simple like this
#RestController
#RequestMapping("/")
public class UserController {
#GetMapping(value = "/user")
public Principal getUser(Principal user) {
return user;
}
}
and I will get UserDetail in the ResourceServer , use this code
void me(Principal principal) {
String name = principal.getName();
}
At the beginning ,the name is always the right name . but If userA and userB access the interface with their token at the almost same time, Things go wrong. Sometimes I will get userA's name when I except userB' name.
I check out the spring security source code , In UserInfoTokenServices.java , I found this code probably cause the mistake. When many querys come in, they multi-threads operate the same this.restTemplate , and the logical of accessToken and existingToken , when they equals, but other thread maybe will change the this.restTemplate before it calls restTemplate.getForEntity
private Map<String, Object> getMap(String path, String accessToken) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Getting user info from: " + path);
}
try {
OAuth2RestOperations restTemplate = this.restTemplate;
if (restTemplate == null) {
BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();
resource.setClientId(this.clientId);
restTemplate = new OAuth2RestTemplate(resource);
}
OAuth2AccessToken existingToken = restTemplate.getOAuth2ClientContext()
.getAccessToken();
if (existingToken == null || !accessToken.equals(existingToken.getValue())) {
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(
accessToken);
token.setTokenType(this.tokenType);
restTemplate.getOAuth2ClientContext().setAccessToken(token);
}
return restTemplate.getForEntity(path, Map.class).getBody();
}
catch (Exception ex) {
this.logger.warn("Could not fetch user details: " + ex.getClass() + ", "
+ ex.getMessage());
return Collections.<String, Object>singletonMap("error",
"Could not fetch user details");
}
}
}
I think this will cause the wrong Principal info.
And In fact. When I use this code
Principal principal = SecurityContextHolder.getContext().getAuthentication();
String name = principal.getName();
the name will suddenly go wrong ,then next time be right again.
Are you guys ever confused with this situation?
What to do I can get the right username all the time.
Thanks for your attention.

when server started,
step 1 if there is not a bean of AuthorizationServerEndpointsConfiguration.class , will goto step 2
step 2: if there is not a bean of ResourceServerTokenServices.class
,run the code below:
#Bean
#ConditionalOnMissingBean(ResourceServerTokenServices.class)
public UserInfoTokenServices userInfoTokenServices() {
UserInfoTokenServices services = new UserInfoTokenServices(
this.sso.getUserInfoUri(), this.sso.getClientId());
services.setRestTemplate(this.restTemplate);
services.setTokenType(this.sso.getTokenType());
if (this.authoritiesExtractor != null) {
services.setAuthoritiesExtractor(this.authoritiesExtractor);
}
if (this.principalExtractor != null) {
services.setPrincipalExtractor(this.principalExtractor);
}
return services;
}
so ResourceServerTokenServices is singleton, and so its restTemplate,
when program run into this code below , multi-thread will concurrent operate restTemplate. Something wrong will come out.
private Map<String, Object> getMap(String path, String accessToken) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Getting user info from: " + path);
}
try {
OAuth2RestOperations restTemplate = this.restTemplate;
if (restTemplate == null) {
BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();
resource.setClientId(this.clientId);
restTemplate = new OAuth2RestTemplate(resource);
}
OAuth2AccessToken existingToken = restTemplate.getOAuth2ClientContext()
.getAccessToken();
if (existingToken == null || !accessToken.equals(existingToken.getValue())) {
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(
accessToken);
token.setTokenType(this.tokenType);
restTemplate.getOAuth2ClientContext().setAccessToken(token);
}
return restTemplate.getForEntity(path, Map.class).getBody();
}
catch (Exception ex) {
this.logger.warn("Could not fetch user details: " + ex.getClass() + ", "
+ ex.getMessage());
return Collections.<String, Object>singletonMap("error",
"Could not fetch user details");
}
}
the right way is : If ResourceServer there is no AuthorizationServerEndpointsConfiguration, you'd better provide an implement of ResourceServerTokenServices.class. This will be controlled better.

I had same problem: concurrent requests mashed up principals.
Today applied advice from this article: https://www.baeldung.com/spring-security-oauth2-authentication-with-reddit .
1) Added #EnableOAuth2Client annotation,
2) Added OAuth2ClientContext clientContext to rest remplate.
Me final RestTemplate bean looks this way:
#Bean
#LoadBalanced
public OAuth2RestOperations restTemplate(UserInfoTokenServices remoteTokenServices,
OAuth2ClientContext clientContext) {
ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
resourceDetails.setClientId(securityProperties.getServiceClientId());
resourceDetails.setClientSecret(securityProperties.getServiceClientSecret());
OAuth2RestOperations restTemplate = new OAuth2RestTemplate(resourceDetails, clientContext);
remoteTokenServices.setRestTemplate(restTemplate);
return restTemplate;
}
And my tests show that bug has gone and no mash happens in principals.

Related

authenticate a user inside a test class

I am new to spring boot and testing and I have spring boot app (generated with JHipster) that uses authentication. I need to get the id of the current user.
so this method inside userRepository returns the current user
#Query(value = "select u.* from user u where u.username=?#{principal.username}", nativeQuery = true)
User findConnectedUser();
here is the method I want to test in my controller:
#PostMapping("/rdvs")
public ResponseEntity<Rdv> createRdv(#RequestBody Rdv rdv) throws URISyntaxException {
log.debug("REST request to save Rdv : {}", rdv);
if (rdv.getId() != null) {
throw new BadRequestAlertException("A new rdv cannot already have an ID", ENTITY_NAME, "idexists");
}
User user = userRepository.findConnectedUser();
rdv.setIdUser(user.getId());
Rdv result = rdvRepository.save(rdv);
return ResponseEntity
.created(new URI("/api/rdvs/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert(applicationName, true, ENTITY_NAME, result.getId().toString()))
.body(result);
}
here is my test method
#Autowired
private RdvRepository rdvRepository;
#Autowired
private UserRepository userRepository;
....
#Test
#Transactional
void createRdv() throws Exception {
int databaseSizeBeforeCreate = rdvRepository.findAll().size();
// Create the Rdv
restRdvMockMvc
.perform(post(ENTITY_API_URL).contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(rdv)))
.andExpect(status().isCreated());
// Validate the Rdv in the database
User user = userRepository.findConnectedUser();// this generate the NullPointer exception
List<Rdv> rdvList = rdvRepository.findAll();
assertThat(rdvList).hasSize(databaseSizeBeforeCreate + 1);
Rdv testRdv = rdvList.get(rdvList.size() - 1);
assertThat(testRdv.getDescription()).isEqualTo(DEFAULT_DESCRIPTION);
assertThat(testRdv.getIdUser()).isEqualTo(user.getId());
}
So this method generate a NullPointer, I guess because the method can't find the current user which should be authenticated first. So how can I authenticate a user inside that test method please I spend a lot of time with it but nothing seems to be working
note: I tried to call this api that authenticate users
#PostMapping("/authenticate")
public ResponseEntity<JWTToken> authorize(#Valid #RequestBody LoginVM loginVM) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
loginVM.getUsername(),
loginVM.getPassword()
);
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.createToken(authentication, loginVM.isRememberMe());
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);
return new ResponseEntity<>(new JWTToken(jwt), httpHeaders, HttpStatus.OK);
}
like this in test method
User user = new User();
user.setLogin("user-jwt-controller");
user.setEmail("user-jwt-controller#example.com");
user.setActivated(true);
user.setPassword(passwordEncoder.encode("test"));
userRepository.saveAndFlush(user);
LoginVM loginVM = new LoginVM();
loginVM.setUsername("user-jwt-controller");
loginVM.setPassword("test");
//I don't know how to call the api #PostMapping("/authenticate")
Thanks in advance
Have a look at #WithMockUser annotation, see https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/test-method.html
You can see an example in the project that was generated by JHipster:
#AutoConfigureMockMvc
#WithMockUser(value = TEST_USER_LOGIN)
#IntegrationTest
class AccountResourceIT {

Controller in DGS Netflix Graphql

We are developing a project Using Spring boot with DGS Netflix graphql. We are created all the schemas and datafethers which is working absolutely fine with a default endpoint "/graphql". we would like to expose this app with custom endpoing so we are trying to add a controller with a custom endpoint as below. But When i run the application and send a query, my data fetcher is called twice . first time called from controller and second time i believe from framework it self. Anybody has any thoughts on this why its being called twice and how to avoid it? You help is highly appreciated. Please see the below Datafetcher and Controller.
Controller:
#RestController
#RequestMapping("/sample-information/model")
#Slf4j
public class CustomController {
#Autowired
DgsQueryExecutor dgsQueryExecutor;
#PostMapping(consumes = {MediaType.APPLICATION_JSON_VALUE, "application/graphql"})
public Mono<ResponseEntity<Object>> getDetails(#RequestBody String query,
#RequestHeader HttpHeaders headers
) {
GraphQLQueryInput inputs = null;
try {
inputs = ObjectMapperHelper.objectMapper.readValue(query, GraphQLQueryInput.class);
} catch (Exception e) {
log.error("TraceId: {} - Application Error: Error message: Error converting query to GraphQLQueryInput: {} "+ query);
}
if(inputs.getVariables() == null) {
inputs.setVariables(new HashMap<>());
}
if(inputs.getOperationName() == null) {
inputs.setOperationName("");
}
final String que = inputs.getQuery();
final Map<String, Object> var = inputs.getVariables();
final String opn = inputs.getOperationName();
ExecutionInput.Builder executionInput = ExecutionInput.newExecutionInput()
.query(inputs.getQuery())
.operationName(inputs.getOperationName())
.variables(inputs.getVariables());
return Mono.fromCallable(()-> {
return dgsQueryExecutor.execute(que, var, opn);
}).subscribeOn(Schedulers.elastic()).map(result -> {
return new ResponseEntity<>(result, HttpStatus.OK);
});
}
}
Datafetcher:
#DgsComponent
#Slf4j
public class SampleDataFetcher {
#Autowired
SampleBuilder sampleBuilder;
#DgsData(parentType = DgsConstants.QUERY_TYPE, field = DgsConstants.QUERY.SampleField)
public CompletableFuture<StoreDirectoryByStateResponse> getStoreDirectoryByState(#InputArgument String state, DataFetchingEnvironment dfe) throws BadRequestException {
Mono<StoreDirectoryByStateResponse> storeDirectoryResponse = null;
try {
sampleResponse = sampleBuilder.buildResponse(modelGraphQLContext);
} catch (BaseException e) {
}
return sampleResponse.map(response -> {
return response;
}).toFuture();
}
}

How to send Status Codes Along with my Custom Class Using Spring?

I am trying to make a log in system using spring. Problem is if username is not in the database I want to send a different status code and if username is in the database but password is wrong I want to send different status code. Because in my front end i am going to inform user using different alerts according to status code.
I cannot use HttpStatus.NOT_ACCEPTABLE or something like that because my controller is returning a User(my custom class). It will either return User or null.
#GetMapping("/users")
public User userLogin(#RequestParam String username,#RequestParam String password) {
User user = userService.findByUsername(username);
if(user==null) {
return null;
}
if(user.getPassword().equals(password)) {
return user;
} else {
return null;
}
}
Here I am trying to change status while returning nulls.
you can return ResponseEntity to meet your requirement
#GetMapping("/users")
public ResponseEntity<User> userLogin(#RequestParam String username,#RequestParam String password) {
User user = userService.findByUsername(username);
if(user==null) {
return new ResponseEntity<>(null,HttpStatus.NOT_FOUND);
}
if(user.getPassword().equals(password)) {
return new ResponseEntity<>(user,HttpStatus.OK);
} else {
return new ResponseEntity<>(null,HttpStatus.FORBIDDEN);
}
}
Spring 5 introduced the ResponseStatusException class. We can create an instance of it providing an HttpStatus and optionally a reason and a cause:
#GetMapping(value = "/{id}") public Foo findById(#PathVariable("id") Long id, HttpServletResponse response) {
try {
Foo resourceById = RestPreconditions.checkFound(service.findOne(id));
eventPublisher.publishEvent(new SingleResourceRetrievedEvent(this, response));
return resourceById;
}
catch (MyResourceNotFoundException exc) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Foo Not Found", exc);
} }
Maybe this is which you looking for?
Detail in https://www.baeldung.com/exception-handling-for-rest-with-spring#controlleradvice

Generated yaml(swagger json to yaml) from Spring Swagger throws error. Doesnt get ref to Java List or Integer

I am using spring Spring Fox swagger2 2.5 to get the Swagger UI. Its a spring MVC project. I am am able to get Swagger Json by hitting /v0/apidocs.
But when i am converting the json to yaml in swagger editor, there are errors thrown by the studio about not having the #ref of List and #ref of Integer.This is error message screenshot
Not sure how to resolve this.
`#DeleteMapping("/api/v0/consumers/{consumerId}/address/{addressId}")
public ResponseEntity<RestResponse<Map<String, Integer>>> deleteByConsumerId(
#PathVariable("consumerId") Integer consumerId, #PathVariable("addressId") Integer addressId) {
ResponseEntity<RestResponse<Map<String, Integer>>> responseEntity = null;
try {
logger.debug(strConsumerId + consumerId + ", address ID: " + addressId);
if (consumerId != null && addressId != null) {
RestStatus status = new RestStatus("200",
consumerAddressDao.deleteAddressById(consumerId, addressId) + " Consumer Address Deleted ");
responseEntity = new ResponseEntity<>(new RestResponse<Map<String, Integer>>(null, status),
HttpStatus.OK);
}
} catch (Exception exception) {
logger.error(logError, exception);
RestStatus status = new RestStatus("500", "No Consumer address found for ID ");
responseEntity = new ResponseEntity<>(new RestResponse<>(null, status), HttpStatus.NOT_MODIFIED);
}
return responseEntity;
}
`
'#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Bean
public Docket api() {
ApiSelectorBuilder apiBuilder = new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any());'
'registry.addResourceHandler("swagger-
ui.html").addResourceLocations("classpath:/META-INF/resources/");'

Shiro not redirecting to loginUrl after failed attempts to log in

Using Spring Boot I am configuring the following filter
#Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter() {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
Map<String, String> filterChainDefinitionMapping = new HashMap<>();
/*
* URL path expressions are evaluated against an incoming request in the order they are defined and the FIRST MATCH WINS. For example, let's asume that there are the following chain definitions:
/account/** = ssl, authc
/account/signup = anon
If an incoming request is intended to reach /account/signup/index.html (accessible by all 'anon'ymous users), it will never be handled!. The reason is that the /account/** pattern matched the incoming request first and 'short-circuited' all remaining definitions.
Always remember to define your filter chains based on a FIRST MATCH WINS policy!
* */
filterChainDefinitionMapping.put("/login.html", "authc");
filterChainDefinitionMapping.put("/logout", "logout");
filterChainDefinitionMapping.put("/css/**", "anon");
filterChainDefinitionMapping.put("/register/**", "anon");
filterChainDefinitionMapping.put("/**", "authc");
shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMapping);
shiroFilter.setSecurityManager(securityManager());
shiroFilter.setLoginUrl("/login.html");
shiroFilter.setSuccessUrl("/");
shiroFilter.setUnauthorizedUrl("/unauthorized.html");
Map<String, Filter> filters = new HashMap<>();
filters.put("anon", new AnonymousFilter());
filters.put("authc", new FormAuthenticationFilter());
LogoutFilter logoutFilter = new LogoutFilter();
logoutFilter.setRedirectUrl("/login.html?logout");
filters.put("logout", logoutFilter);
filters.put("roles", new RolesAuthorizationFilter());
filters.put("user", new UserFilter());
shiroFilter.setFilters(filters);
return shiroFilter;
}
However, whenever I try to login with wrong credentials the redirection never happens. I do get the "shiroLoginFailure" attribute holding the UnknownUserException.
(Logging in with the correct credentials works fine)
Any ideas?
Mariosk89, how do you resolve the /login.html?
It might be need to resolve redirect like this:
#RequestMapping("/login")
public String login(String username, String password) {
Subject currentUser = SecurityUtils.getSubject();
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
try {
currentUser.login(new UsernamePasswordToken(username, password));
} catch (Exception e) {
logger.error(e.getLocalizedMessage(), e);
return "login";
}
return "redirect:index";
} else {
return "login";
}
}
Reference: https://github.com/lenicliu/examples/tree/master/examples-spring-boot/examples-spring-boot-shiro
For more exception solution, refer http://shiro.apache.org/10-minute-tutorial.html
try {
currentUser.login( token );
//if no exception, that's it, we're done!
} catch ( UnknownAccountException uae ) {
//username wasn't in the system, show them an error message?
} catch ( IncorrectCredentialsException ice ) {
//password didn't match, try again?
} catch ( LockedAccountException lae ) {
//account for that username is locked - can't login. Show them a message?
}
... more types exceptions to check if you want ...
} catch ( AuthenticationException ae ) {
//unexpected condition - error?
}

Categories