When I call my API to validate a token for the frontend I am running into an issue when calling my UserDetailsService's loadUserByUsername method. I am able to pass the username to the method but my userRepository fails to execute the findByUsername method, and I am not sure what is going on. I am printing the name out before the call and it is returning the correct username, also a user of that name exists in the DB.
This is what I am getting in the console:
2020-07-09 22:46:55.121 INFO 18048 --- [nio-8080-exec-4] o.s.web.servlet.DispatcherServlet : Completed initialization in 10 ms
2020-07-09 22:46:55.153 INFO 18048 --- [nio-8080-exec-4] c.g.Apollo.security.jwt.JwtFilter : token not presented...
2020-07-09 22:46:55.759 INFO 18048 --- [nio-8080-exec-4] c.g.Apollo.service.UserService : success...
2020-07-09 22:47:10.885 INFO 18048 --- [nio-8080-exec-5] c.g.Apollo.security.jwt.JwtFilter : token not presented...
2020-07-09 22:47:10.898 INFO 18048 --- [nio-8080-exec-5] c.g.A.s.jwt.JwtUserDetailsService : load user... max123
2020-07-09 22:47:10.909 WARN 18048 --- [nio-8080-exec-5] g.e.SimpleDataFetcherExceptionHandler : Exception while fetching data (/verifyToken) : null
java.lang.NullPointerException: null
at com.**.Apollo.security.jwt.JwtUserDetailsService.loadUserByUsername(JwtUserDetailsService.java:23) ~[classes/:na]
at com.**.Apollo.service.UserService.verifyToken(UserService.java:173) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
at io.leangen.graphql.metadata.execution.SingletonMethodInvoker.execute(SingletonMethodInvoker.java:21) ~[spqr-0.9.9.jar:na]
The UserRepository method works just fine when I login a user but fails here.
JwtUserDetailsService, this is called from the verifyToken method:
#Slf4j
#Service
public class JwtUserDetailsService implements UserDetailsService {
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) {
log.info("load user... {}", username);
Optional<User> user = userRepository.findByUsername(username);
log.info("after");
if (user.isPresent()) {
log.info("user:: {}", user.get().getUsername());
return getJwtUser(user.get());
} else {
log.info("user not found");
return null;
}
}
public JwtUser getJwtUser(User user) {
return new JwtUser(
user.getId(),
user.getUsername(),
user.getFirstName(),
user.getLastName(),
user.getEmail(),
user.getPassword(),
List.of(new SimpleGrantedAuthority(user.getRole().getRoleName().name())),
user.getEnabled(),
null
);
}
UserRepository:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
boolean existsByUsername(String username);
Optional<User> findByToken(String token);
}
UserSerice, this is what is exposed to the frontend:
#GraphQLQuery
public User verifyToken(String token) {
Optional<User> optionalUser = userRepository.findByToken(token);
if(optionalUser.isPresent()) {
UserDetails userDetails = jwtUserDetailsService.loadUserByUsername(optionalUser.get().getUsername());
if(jwtTokenUtil.isTokenValid(token, userDetails)) {
return optionalUser.get();
}
}
return null;
}
It is throwing a NullPointerException because the userRepository in JwtUserDetailsService was not injected and is null.
Create a constructor like the following:
public JwtUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
You need to add #Autowired in class JwtUserDetailsService
#Autowired
private UserRepository userRepository;
It seems that your UserRepository dependency is not getting injected in JwtUserDetailsService as you did not autowire it.
You can do the following:
Autowire the dependency using the constructor injection which is the recommended way:
private final UserRepository userRepository;
public JwtUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
It is not recommended to autowire your private members, checkout this post
As you are already using project Lombok, more elegant way to do it instead of writing a constructor would be just annotate your class with #RequiredArgsConstructor.
#Slf4j
#RequiredArgsConstructor
#Service
public class JwtUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
..
}
Here Project lombok will generate a constructor for you which will take care of the dependency injection.
Related
I have added Http cookie Authentication using authentication manager to my Spring Boot REST API
I have a controller that exposes a rest service allowing authentication to /api/auth/signin resource via Spring security cookies session.
Here is the the Controller and the security configuration This exemple.
After running the application, I noticed that it is important to carry out the unit test part, so I wanted to create mocks for the authenticateUser method (resource: /signin), but unfortunately I encountered problems.
Voici la classe AuthControllerTest:
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes=Application.class)
#WebMvcTest(AuthController.class)
public class AuthControllerTest {
#MockBean
UserRepository userRepository;
#MockBean
AuthenticationManager authenticationManager;
#MockBean
private UserDetailsServiceImpl userDetailsServiceImpl;
#Autowired
private MockMvc mockMvc;
private static UserDetailsImpl dummy;
#MockBean
private JwtUtils jwtUtil;
#Autowired
WebApplicationContext webApplicationContext ;
private ResponseCookie cookies;
#BeforeEach
public void setUp() {
dummy = new UserDetailsImpl(10L,"test1","test1#mail.com","123456",new ArrayList<>());
Authentication authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();*/
cookies = jwtUtil.generateJwtCookie(dummy) ;
}
#Test
#DisplayName("POST /signin")
void authenticateUser() throws Exception
{
LoginRequest authenticationRequest = new LoginRequest("mod", "123456") ;
String jsonRequest = asJsonString(authenticationRequest);
RequestBuilder request = MockMvcRequestBuilders
.post("/api/auth/signin")
.content(jsonRequest)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON);
Authentication auth = Mockito.mock(Authentication.class);
Mockito.when(auth.getName()).thenReturn("authName");
auth.setAuthenticated(true);
Mockito.when(auth.isAuthenticated()).thenReturn(true);
Mockito.when(authenticationManager.authenticate(auth)).thenReturn(auth); // Failing here
Mockito.when(jwtUtil.generateJwtCookie(dummy)).thenReturn(cookies);
Mockito.when(userDetailsServiceImpl.loadUserByUsername("test1")).thenReturn(dummy);
MvcResult mvcResult = mockMvc.perform(request)
.andExpect(status().is2xxSuccessful())
.andReturn();
}
public static String asJsonString(final Object obj) {
try {
return new ObjectMapper().writeValueAsString(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Here is the encountered errors after running the class AuthControllerTest:
java.lang.AssertionError: Range for response status value 403
expected: but was:<CLIENT_ERROR> Expected :SUCCESSFUL
Actual :CLIENT_ERROR
at
org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
at
org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122)
at
org.springframework.test.web.servlet.result.StatusResultMatchers.lambda$is2xxSuccessful$3(StatusResultMatchers.java:78)
at
org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:212)
at AuthControllerTest.authenticateUser(AuthControllerTest.java:102)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498) at
org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
at
org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at
org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at
org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
at
org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
at
org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
at
org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
at
org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at
org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
If you willing to change your code, then do this and hopefully everything will work fine:
A. Create a package in your test main package, it should include both words test and integration
package com.<yourApplication>.test.integration;
B.This is how your test class should be like:
#AutoConfigureMockMvc
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#Import({ ObjectMapper.class, <YourController>.class })
#TestMethodOrder(OrderAnnotation.class)
class YourTestClass {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
// user authentication
private static String jwt; // can use this for your next test request
#Test
#Order(1)
#DisplayName("User Authentication token")
void authenticationTest() throws JsonProcessingException, Exception {
final String link = "/api/auth/signin";
AuthenticationRequest defaultAuth = new AuthenticationRequest("admin", "admin");
System.out.println(objectMapper.writeValueAsString(defaultAuth));
// perform the request
MvcResult result = this.mockMvc
.perform(MockMvcRequestBuilders.post(link)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsBytes(defaultAuth)))
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String response = result.getResponse().getContentAsString();
System.out.println("from response: " + response); //
JsonNode root = objectMapper.readTree(response);
JsonNode jwtvalue = root.get("jwt");
jwt = jwtvalue.textValue();
System.out.println("jwt deserlized: " + jwt);
}
}
C. If the request returned an error, then the problem is either in your controller or the way you setup the JWT authentication.
I'm trying to test a comment_post method.
Comment has many - to - one relationship with User Entity which comes from Spring Security.
I connected this relationship by using Principal.
I think I made it working properly, but having trouble applying it to test.
Problem is that Comment Posting method gets user by finding User in Repository using Principal's email attribute, So I need to apply SecurityContext to test,
but I have no idea how to apply this function to test.
By Searching, I found out that I can make SpringSecurityContext by #WithSecurityContext
annotation, so I'm trying to apply it but having this error
java.lang.RuntimeException: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'springboot.web.CommentsApiControllerTest$WithUserDetailsSecurityContextFactory': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'springboot.web.CommentsApiControllerTest' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
I'm not even sure that my approach is correct.
tbh, I kind of feel lost, maybe it's because I'm new to SpringBoot, also Security.
Here's my codes.
CommentService
#RequiredArgsConstructor
#Service
public class CommentService {
private final CommentRepository commentRepository;
private final PostsRepository postsRepository;
private final UserDetailService userDetailService;
#Transactional
public Long commentSave(CommentSaveRequestDto requestDto, Long id) {
Posts post = postsRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다"));
requestDto.setPosts(post);
User user = userDetailService.returnUser();
requestDto.setUser(user);
return commentRepository.save(requestDto.toEntity()).getId();
}
`
UserDetailService
#RequiredArgsConstructor
#Service
public class UserDetailService {
private final UserRepository userRepository;
public User returnUser() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String userName;
if (principal instanceof UserDetails) {
userName = ((UserDetails) principal).getUsername();
} else {
userName = principal.toString();
}
int start = userName.indexOf("email")+6;
int end = userName.indexOf(".com,")+4;
String email = userName.substring(start, end);
User user = userRepository.findByEmail(email).orElse(null);
return user;
}
CommentSaveRequestDto
#Data
#NoArgsConstructor
#Builder
#AllArgsConstructor
public class CommentSaveRequestDto {
private String comment;
private Posts posts;
private User user;
/* Dto -> Entity */
public Comment toEntity() {
return Comment.builder()
.comment(comment)
.posts(posts)
.user(user)
.build();
}
}
And here is my CommentsApiControllrTest
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#Transactional
public class CommentsApiControllerTest {
#LocalServerPort
private int port;
#Autowired
private PostsRepository postsRepository;
#Autowired
private CommentRepository commentRepository;
#Autowired
private UserRepository userRepository;
#Autowired
private PostsService postsService;
#Autowired
private CommentService commentService;
#Autowired
private UserDetailService userDetailsService;
#Autowired
private WebApplicationContext context;
#Autowired ObjectMapper objectMapper;
private MockMvc mvc;
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.apply(sharedHttpSession())
.build();
}
#Retention(RetentionPolicy.RUNTIME)
#WithSecurityContext(factory = WithUserDetailsSecurityContextFactory.class)
public #interface WithMockCustomUser {
String name() default "testName";
String email() default "testemail#gmail.com";
Role role() default Role.USER;
}
final class WithUserDetailsSecurityContextFactory implements WithSecurityContextFactory<WithUserDetails> {
private final UserDetailsService userDetailsService;
#Autowired
public WithUserDetailsSecurityContextFactory(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
public org.springframework.security.core.context.SecurityContext createSecurityContext(WithUserDetails withUser) {
String username = withUser.value();
Assert.hasLength(username, "value() must be non-empty String");
UserDetails principal = userDetailsService.loadUserByUsername(username);
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
return context;
}
}
#After
public void tearDown() throws Exception {
postsRepository.deleteAll();
commentRepository.deleteAll();
}
#Test
#WithMockCustomUser
#Transactional // 프록시 객체에 실제 데이터를 불러올 수 있게 영속성 컨텍스트에서 관리
public void comment_등록() throws Exception {
// given
String title = "title";
String content = "content";
User user = userRepository.save(User.builder()
.name("name")
.email("fake#naver.com")
.picture("fakePic.com")
.role(Role.USER)
.build());
PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder()
.title(title)
.content(content)
.user(user)
.build();
postsRepository.save(requestDto.toEntity());
String comment = "comment";
Posts posts = postsRepository.findAll().get(0);
CommentSaveRequestDto saveRequestDto = CommentSaveRequestDto.builder()
.comment(comment)
.posts(posts)
.build();
Long id = posts.getId();
String url = "http://localhost:"+ port + "/api/posts/" + id + "/comments";
//when
mvc.perform(post(url)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(objectMapper.writeValueAsString(saveRequestDto)))
.andExpect(status().isOk())
.andDo(print());
}
All I want is to make a mock Security User in test, so that
User user = userDetailService.returnUser();
this line in CommentService don't make any error.
Just a little tip would be really helpful to me.
Thank you in advance.
I couldn't use userRepo to saveUser properly, the mysql query showed wrong order of insertion, therefore not working. Here's my UserRepository:
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Integer> {
Optional<User> findUserByUsername(String username);
}
Here's my user service:
#Service
public class UserServiceImpl implements IUserService, UserDetailsService {
private Logger logger = LogManager.getLogger(UserServiceImpl.class);
#Autowired
private UserRepository userRepo;
#Autowired
private BCryptPasswordEncoder encoder;
//field name not in correct order, can not use it
#Override
public Integer saveUser(User user) {
String passwd = user.getPassword();
String encodedPasswod = encoder.encode(passwd);
logger.log(Level.ERROR, "passwd:" + encodedPasswod);
user.setPassword(encodedPasswod);
ArrayList<String> roles = new ArrayList<String>();
roles.add("Member");
user.setRoles(roles);
user = userRepo.save(user);
return user.getUserid();
}
public UserServiceImpl(UserRepository userRepository) {
this.userRepo = userRepository;
}
...
Someone can help? So frustrated with springboot.
M.
Look like you missing #Transactional on the saveUser method.
I am new to Spring data jpa, trying to create a simple spring boot - data jpa- hibernate application. I am getting 2021-04-03 20:24:40.478 WARN 33252 --- [nio-8081-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation]
in my console when I hit the url http://localhost:8081/users Below is my controller code,
#RestController
public class UserController {
#Autowired
private UserService userService;
#GetMapping(value="/users")
public List<User> getAllUsers() {
List<User> result = userService.getAllUsers();
return result;
}
}
Service class -
#Service
public class UserService {
#Autowired
private UserRepository userRepository;
public List<User> getAllUsers() {
List<User> r = (List<User>) userRepository.findAll();
return r;
}
}
Repository interface-
#Repository
public interface UserRepository extends CrudRepository<User, Integer>{
}
I have gone through various solutions available online but nothing seems to work. Thanks in advance for the help.
There is some problem with "Accept" header with which your browser is sending the request. Can you include your http request browser trace?
I am running a spring mvc application with classes annotated as #controller,#service,#component and for the view layer thymeleaf is used. However on navigating to the path localhost:8080/owners no owner data is displayed.Looks like even though data is getting saved in owner object but ownerservicemap is null for no reason.
Below is the error
Owner data is loaded
2019-06-26 12:39:47.237 INFO 5776 --- [ restartedMain]
.ConditionEvaluationDeltaLoggingListener : Condition evaluation
unchanged
2019-06-26 12:39:50.475 INFO 5776 --- [nio-8080-exec-1]
o.a.c.c.C.[Tomcat-1].[localhost].[/] : Initializing Spring
DispatcherServlet 'dispatcherServlet'
2019-06-26 12:39:50.475 INFO 5776 --- [nio-8080-exec-1]
o.s.web.servlet.DispatcherServlet : Initializing Servlet
'dispatcherServlet'
2019-06-26 12:39:50.483 INFO 5776 --- [nio-8080-exec-1]
o.s.web.servlet.DispatcherServlet : Completed initialization in
8 ms
2019-06-26 12:39:50.507 ERROR 5776 --- [nio-8080-exec-1]
o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for
servlet [dispatcherServlet] in context with path [] threw exception
[Request processing failed; nested exception is
java.lang.NullPointerException] with root cause
java.lang.NullPointerException: null
at com.example.Project.controllers.OwnerController.listOwners(OwnerController.java:33)
~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_211]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
~[na:1.8.0_211]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
~[na:1.8.0_211]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_211]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
DataLoader class to load the data
#Component public class DataLoader implements CommandLineRunner{
private final OwnerService ownerService;
public DataLoader()
{
ownerService=new OwnerServiceMap();
}
#Override
public void run(String... args) throws Exception {
// TODO Auto-generated method stub
Owner owner1=new Owner();
owner1.setId(1L);
owner1.setFirstName("ally");
owner1.setLastName("nilson");
Owner sa1=ownerService.save(owner1);
}}
Below is the ownercontroller class
#RequestMapping("/owners") #Controller public class OwnerController {
private OwnerService ownerservice;
#Autowired
public OwnerController(OwnerService ownerservice)
{
this.ownerservice=ownerservice;
}
#RequestMapping({"","/","/index","/index.html"})
public String listOwners(Model model)
{
model.addAttribute("owner",ownerservice.findAll());// System.out.println(ownerservice.findById(1L).getLastName());
return "owner/index";
}}
OwnerService interface
public interface OwnerService extends CrudService<Owner, Long>{
Owner findByLastName(String lastname);}
AbstractmapService class
public abstract class AbstractMapService<T,ID> {
protected Map<ID,T> map=new HashMap<>();
Set<T> findAll()
{
return new HashSet<>(map.values());
}
T findById(ID id)
{
return map.get(id);
}
T save(ID id,T object)
{
map.put(id, object);
return object;
}
void deleteById(ID id)
{
map.remove(id);
}
void delete(T object)
{
map.entrySet().removeIf(entry->entry.getValue().equals(object));
}}
OwnerServiceMap class
#Service public class OwnerServiceMap extends AbstractMapService<Owner,Long>implements OwnerService{
#Override
public Set<Owner> findAll() {
// TODO Auto-generated method stub
return super.findAll();
}
#Override
public Owner findById(Long id) {
// TODO Auto-generated method stub
return super.findById(id);
}
#Override
public Owner save(Owner object) {
// TODO Auto-generated method stub
return super.save(object.getId(),object);
}
#Override
public void delete(Owner object) {
// TODO Auto-generated method stub
super.delete(object);
}
#Override
public void deleteById(Long id) {
// TODO Auto-generated method stub
super.deleteById(id);
}
#Override
public Owner findByLastName(String lastname) {
// TODO Auto-generated method stub
return null;
}
}
#Service
public class OwnerServiceMap extends AbstractMapService<Owner,Long> implements OwnerService{ ... // }
To be autowired, you must register with the bean.
EDIT1
You did to save another service's Map.
#Component public class DataLoader implements CommandLineRunner{
private final OwnerService ownerService;
public DataLoader()
{
ownerService=new OwnerServiceMap();
}
// ...
And here,
#RequestMapping("/owners") #Controller public class OwnerController {
private OwnerService ownerservice;
#Autowired
public OwnerController(OwnerService ownerservice)
{
this.ownerservice=ownerservice;
}
check this please.
EDIT2
If you register Object to bean, container has that by singleton object.
then, Using #Autowired get singleton object from container.
NOTE : it's different with GOF's singleton.
To summarize, what you use with the new keyword like ownerService=new OwnerServiceMap(); was to create a new instance, not to use the bean instance that you assigned to the Container. So, using the different instances, the above problem occurs.