How to get oAuth2 access token when user call signup rest API in springboot? - rest

currently I am working on Springboot security, its quite new for me. I followed youtube video tutorial Video
I am getting oauth2 access_token successfully when I use bellow code snippet:-
#SpringBootApplication
public class MathifyApplication {
#Autowired
private PasswordEncoder passwordEncoder;
public static void main(String[] args) {
SpringApplication.run(MathifyApplication.class, args);
}
#Autowired
public void authenticationManager(AuthenticationManagerBuilder builder, UserRepository repository, UserService service) throws Exception {
//Setup a default user if db is empty
User students = new User("stu1", "user", "user", "abc#gmail.com", "1234567890", "12th", "dwarka sec-12",
0, 0 , "may/29/2017", "", Arrays.asList(new Role("USER"), new Role("ACTUATOR")));
if (repository.count()==0){
service.save(students);
}
builder.userDetailsService(userDetailsService(repository)).passwordEncoder(passwordEncoder);
}
private UserDetailsService userDetailsService(final UserRepository repository) {
return userName -> new CustomUserDetails(repository.findByUsername(userName));
}
}
And Controller Class is:-
#RestController
public class LoginController {
#Autowired
private UserService userService;
#RequestMapping(value = "/mathify/getuser/{userId}", method = RequestMethod.GET)
public User getUser(#PathVariable String userId){
System.out.println("Userid "+userId);
return userService.getUser(userId);
}
#RequestMapping(method = RequestMethod.POST, value="/mathify/signup")
public User register(#RequestBody User user){
return userService.doSignup(user);
}
#GetMapping(value="/hi")
public String test(){
return "Oh ! I am fine without secuirity";
}
}
With above code snippet I can get access_token(/oauth/token), and I can also call other controller class private APIs without any issue.
but there is a problem with above code. What? In above code snippet User is hard coded, but when I want to get access_token at the time of user signup it's giving an exception.
2017-06-18 11:04:05.689 ERROR 8492 --- [nio-8080-exec-3] 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.IllegalStateException: Cannot apply org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer#6b66d7ac to already built object] with root cause
java.lang.IllegalStateException: Cannot apply org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer#6b66d7ac to already built object
at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.add(AbstractConfiguredSecurityBuilder.java:196) ~[spring-security-config-4.2.2.RELEASE.jar:4.2.2.RELEASE]
at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.apply(AbstractConfiguredSecurityBuilder.java:133) ~[spring-security-config-4.2.2.RELEASE.jar:4.2.2.RELEASE]
at org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder.apply(AuthenticationManagerBuilder.java:290) ~[spring-security-config-4.2.2.RELEASE.jar:4.2.2.RELEASE]
at org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder.userDetailsService(AuthenticationManagerBuilder.java:187) ~[spring-security-config-4.2.2.RELEASE.jar:4.2.2.RELEASE]
at com.techiesandeep.mathify.controller.LoginController.register(LoginController.java:40) ~[classes/:na]
for achieving above described feature I did some changes in my Application and Controller
Application Class is As:-
#SpringBootApplication
public class MathifyApplication {
#Autowired
private PasswordEncoder passwordEncoder;
public static void main(String[] args) {
SpringApplication.run(MathifyApplication.class, args);
}
}
and Controller class is as:-
#RestController
public class LoginController {
#Autowired
private UserService userService;
#Autowired
AuthenticationManagerBuilder builder;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private UserRepository repository;
#RequestMapping(value = "/mathify/getuser/{userId}", method = RequestMethod.GET)
public User getUser(#PathVariable String userId){
System.out.println("Userid "+userId);
return userService.getUser(userId);
}
#RequestMapping(method = RequestMethod.POST, value="/user/signup")
public User register(#RequestBody User user) throws Exception {
User u = userService.doSignup(user);
builder.userDetailsService(userDetailsService(repository)).passwordEncoder(passwordEncoder);
return u;
}
private UserDetailsService userDetailsService(final UserRepository repository) {
return userName -> new CustomUserDetails(repository.findByUsername(userName));
}
#GetMapping(value="/hi")
public String test(){
return "Oh ! I am fine without secuirity";
}
}
Any help would be appreciable.thanks

You can call another POST request to get access token.
I am not sure it's the best way, but worked fine with me.
Example code snip inside Signup Request mapping:
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", auth_header);
/*auth_header should be Autorization header value that captured from signup request, which is generated by Basic Auth with clientID and secret, for example, "Basic bXktdHJ1c3RlZC1jbGllbnQ6c2VjcmV0" */
HttpEntity<String> entity = new HttpEntity<String>("",headers);
String authURL = "http://localhost:8080/oauth/token?grant_type=password&username=yourusername&password=yourpassword";
ResponseEntity<String> response = restTemplate.postForEntity(authURL, entity, String.class);
System.out.println(response.getBody());

Related

Spring boot login with email password

I was developing a backend with spring boot for the first time. I chose email password login over username password login which is the default behavior of spring boot.
I have two objects: AppUser and User. AppUser is used for authentication and User holds every other data necessary for the application.
AppUser:
public class AppUser implements UserDetails {
#Autowired
AuthRepository repository;
#Id
private String id;
#NonNull
#Indexed(unique = true)
#Field(value = "email")
String email;
#NonNull
#Field(value = "password")
String password;
#Field(value = "authorities")
private List<Role> authorities;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> auth = new HashSet<>();
authorities.forEach(index -> auth.add(new SimpleGrantedAuthority(String.valueOf(index.getRole()))));
return auth;
}
#Override
public String getUsername() {
return email;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
AuthController looks like:
#RestController
#RequestMapping("/api/v1/Auth")
public class AuthController {
Logger logger = LoggerFactory.getLogger(AuthController.class);
#Autowired
UserDetailsManager userDetailsManager;
#Autowired
UserService userService;
#Autowired
AuthService authService;
#Autowired
TokenGenerator tokenGenerator;
#Autowired
DaoAuthenticationProvider daoAuthenticationProvider;
#Autowired
#Qualifier("jwtRefreshTokenAuthProvider")
JwtAuthenticationProvider refreshTokenAuthProvider;
#PostMapping("/SignUp")
public ResponseEntity signup(#RequestBody SignUpRequest signUpRequest) {
logger.info(signUpRequest.getUsername());
User user = new User(
signUpRequest.getEmail(),
signUpRequest.getPassword(),
signUpRequest.getUsername(),
signUpRequest.getFirstName(),
signUpRequest.getLastName(),
signUpRequest.getCountry(),
null,
false,
false
);
AppUser appUser = new AppUser(
signUpRequest.getEmail(),
signUpRequest.getPassword()
);
boolean alreadyExists = authService.existsByEmail(signUpRequest.getEmail());
if(alreadyExists){
return ResponseEntity.status(HttpStatus.CONFLICT).body("Email Already Exists");
}else{
userDetailsManager.createUser(appUser);
User newUser = userService.addUser(user);
Authentication authentication = UsernamePasswordAuthenticationToken.authenticated(appUser, signUpRequest.getPassword(), Collections.EMPTY_LIST);
return ResponseEntity.ok(tokenGenerator.signup(authentication, newUser));
}
}
#PostMapping("/SignIn")
public ResponseEntity signin(#RequestBody SignInRequest signInRequest) {
logger.info(signInRequest.getEmail());
logger.info(signInRequest.getPassword());
Authentication authentication = daoAuthenticationProvider.authenticate(UsernamePasswordAuthenticationToken.unauthenticated(signInRequest.getEmail(), signInRequest.getPassword()));
logger.info(authentication.getCredentials().toString());
User user = new User();
return ResponseEntity.ok(tokenGenerator.signin(authentication, user));
}
}
The issue is with signin. SignUp works fine when I am providing email and password. Login is not working.
I am quite new so have zero idea where the issue is happening. Any reference code with explanation will help a lot.
Github Link
N.B: using spring boot, mongodb, oauth2
In my understanding based on your WebSecurity.class, your are using OAuth2 authentication.
What I would do is create 2 Configurations inside the same class with a different Order and in the second one a custom AuthenticationProvider. This was Spring will try to authenticate first based on the first class and then on the second one
#Configuration
#EnableWebSecurity
public class SecurityConfiguration {
//here we set as the first in order the below config if this fails the next one in order will be used until any of them succeeds
#Configuration
#Order(1)
public static class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private MyLoginRequestFilter myLoginRequestFilter ;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
http
.antMatchers("/api/v1/Auth/*").permitAll()
.addFilterBefore(myLoginRequestFilter, UsernamePasswordAuthenticationFilter.class )
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.cors()
.disable()
.csrf()
.disable()
;
}
}
Then insert your class below with Order 2 and then close the whole file/class
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Slf4j
#Order(2)
//this is your class that is uploaded in the git. We actually encapsulate inside a generic Security Config class
public class WebSecurity {
#Autowired
JwtToUserConverter jwtToUserConverter;
...
}
} //to close the java class that was started in the previous block of code
You could even add a new function that will handle the HttpSecurity item so that you dont duplicate your code:
public HttpSecurity myHandler(HttpSecurity http) {
return http
.authorizeHttpRequests((authorize) -> authorize
.antMatchers("/api/v1/Auth/*").permitAll()
.antMatchers("/api/v1/Home/*").permitAll()
.anyRequest().authenticated()
)
.csrf().disable()
.cors().disable()
;
}
Note that in the Order(1) example, I have added a filter, but you can leave it without a filter and add a custom AuthenticationProvider with inside the class:
#Autowired
private YourAuthenticationProvider authenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
YourAuthenticationProvider could look like
#Component
public class YourAuthenticationProvider implements AuthenticationProvider {
#Autowired
AuthService authService; //need to move here the authenticate method from the controller Authentication authentication = daoAuthenticationProvider.authenticate(UsernamePasswordAuthenticationToken.unauthenticated(signInRequest.getEmail(), signInRequest.getPassword()));
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
final String email = authentication.getName();
final Object credentials = authentication.getCredentials();
if ( credentials == null ) {
return authentication;
}
//I suggest using a password encoder to save the password and then check it with PasswordEncoder (you could create a bean)
final Authentication auth = authService.authenticate(email, credentials.toString());
if (auth != null) {
return auth;
}
throw new BadCredentialsException( "Wrong username and/or password" );
}
}
Hope these help

JUnit5 - Rest API controller having custom spring validator is failing

I have a controller that accepts path parameter called 'jobName'. The #ValidateJobName is the custom validator that validates the user input. If the input is wrong then it throws the error below
"Invalid Job name, valid job names are: vendor, service, product,
pricing, currency, contract"
The issue I am facing is that, when I am testing my rest controller API the test case always fails by returning the above error even when the job name is one of the acceptable values but when I remove #ValidateJobName custom annotation from the controller my test cases gets passed.
The #ValidateJobName and Controller works all good when triggered from Postman client but when I do unit testing the test case fails.
I have tried lot of blogs and googled but could not get a solution, Below are my Controller and JUnit testcase.
Please help!
JobController.java
#Validated
#Slf4j
#RestController
public class JobController {
#Autowired
ReportService reportService;
#Autowired
ReportConfig reportConfig;
#RequestMapping(value = "/importjob/{jobName}", method = RequestMethod.GET)
ResponseEntity<DataIntegrationResponse> getReport(#PathVariable #ValidateJobName String jobName) throws Exception {
log.info("Received a request to launch the " + jobName + " Job");
return reportService.getReport(jobName);
}
}
JobControllerTest.java
#ExtendWith(MockitoExtension.class)
#WebMvcTest(JobController.class)
#AutoConfigureMockMvc
public class JobControllerTest {
#MockBean
ReportService reportService;
#MockBean
ReportConfig rep;
#MockBean
JobMapping jmap;
#Autowired
public MockMvc mockMvc;
#Test
public void testGetReport() throws Exception {
String jobNameInput="vendor";
HttpStatus httpStatus = HttpStatus.OK;
String fitsReportName = "idex_fits_vendor.csv";
String jobName = "WFitsVendorJob";
String jobStatus = "STARTED";
Long jobInstanceId = 1022L;
String message = "WFitsVendorJob triggered successfully.";
DataIntegrationResponse response = new DataIntegrationResponse(LocalDateTime.now(), httpStatus, fitsReportName, jobName, jobStatus, jobInstanceId, message);
ResponseEntity<DataIntegrationResponse> responseEntity = new ResponseEntity<DataIntegrationResponse>(response, HttpStatus.OK);
Mockito.when(reportService.getReport(jobNameInput)).thenReturn(responseEntity);
mockMvc.perform(get("/importjob/{jobName}", "vendor")).andExpect(status().isOk());
}
JobNameValidator.java
#Component
public class JobNameValidator implements ConstraintValidator<ValidateJobName, String>{
#Autowired
private JobMapping jobMap;
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value!=null && !jobMap.getMappings().containsKey(value)) { return false; }
return true;
}
}
ValidateJobName.java - interface
#Documented
#Constraint(validatedBy = JobNameValidator.class)
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RUNTIME)
public #interface ValidateJobName {
String message() default "Invalid Job name, valid job names are: vendor, service, product, pricing, currency, contract";
Class<?>[] groups() default {};
Class<? extends Payload> [] payload() default {};
}
This is because you use a mock of JobMapping
#MockBean
JobMapping jmap;
Your JobNameValidator receive a Mock and doesn't know what to return when calling the containsKey method.
First solution is to tell what to do with this mock :
Mockito.when(jobMapping.getMappings()).thenReturn(// Map containing "vendor");
The second solution is to import your real JobMapping class instead of a mock:
#ExtendWith(MockitoExtension.class)
#WebMvcTest(JobController.class)
#AutoConfigureMockMvc
#Import(JobMapping.class)
class JobControllerTest {
// #MockBean
// JobMapping jmap;
}

Assertion error for testing Junit with reactive WebTestClient Status Expected :201 Actual :404

#ExtendWith(SpringExtension.class)
#WebFluxTest(controllers = EventsControllerTest.class)
class EventsControllerTest {
#MockBean
UserRepo repo;
#Autowired
private WebTestClient webClient;
#Test
void testAssignUserRoles() {
UserInfo user = new UserInfo();
user.setId(Long.valueOf(1));
user.setFirstname("Test");
user.setLastname("Test2");
user.setActiveuser(true);
user.setEmailid("tet#test.com");
user.setRolename("test");
user.setUserpassword("test");
Mockito.when(repo.save(user)).thenReturn(Mono.just(user));
webClient.post().uri("/assignRoles").contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(user)).exchange().expectStatus().isCreated();
Mockito.verify(repo, times(1)).save(user);
}
Controller
#RestController
public class EventsController {
#Autowired
UserInfoRepository userInfoRepository;
#PostMapping(value = "/saveRole")
public Mono<UserInfo> assignUserRoles(#RequestBody UserInfo userInfo) {
return userInfoRepository.save(userInfo).log();
}
Access URI should match value attribute of #PostMapping: saveRole.

Secure Rest API Spring MVC

been trying to implement spring security to REST api but they work even without the username & password
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static String REALM="MY_TEST_REALM";
#Autowired
RestAuthenticationEntryPoint restAuthenticationEntryPoint;
#Autowired
public void ConfigureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("USER", "ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/user").hasRole("ADMIN")
.and().httpBasic().realmName(REALM).authenticationEntryPoint(gEntryPoint())
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public RestAuthenticationEntryPoint gEntryPoint() {
return new RestAuthenticationEntryPoint();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/*");
}
}
Rest authetication entry point
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{
#Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized" );
}
}
Rest Controller
#RestController
#RequestMapping(value = "/dray")
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class dray {
#RequestMapping(value = "/user", method = RequestMethod.GET)
#ResponseBody
public Auser getUser() {
return new Auser("john", "carter");
}
}
requests to jsp pages work fine, if user is not authenticated, he's redirected to the login form of spring security but rest api work without even asking for credentials, so how to send response 401 unauthorized if api used doesn't have the credentials?

Spring restful service test case fail HTTP status is 500

I want to implement test case for spring restful web services which return a json
MY controller test class is :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {WebAppContext.class,JpaTestConfiguration.class
})
#WebAppConfiguration
public class DominProfileRestControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
Charset.forName("utf8"));
#Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void testGetDomainProfile() throws Exception {
String profileId = domainProfile.getId().toString();
System.out.print("testing restful"+profileId);
mockMvc.perform(get("/service/domainprofile/get/{id}", profileId) )
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.city", is("Chandigrah")));
/* mockMvc.perform(get("/service/domainprofile/get/{id}",profileId).accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andExpect(content().contentType("text/plain;charset=ISO-8859-1"))
.andExpect(content().string("hello Prashant"));
*/
}
My Controller class Is :
#RestController
#RequestMapping("/service/domainprofile")
public class DominProfileRestController {
#Autowired
private JpaDomainProfileRepository jpaDomainProfileRepository;
#RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
public DomainProfileResource getDomainProfile(#PathVariable String id) {
JpaDomainProfile domainProfile = jpaDomainProfileRepository.findOne(Long.valueOf(id));
DomainProfileResource domainProfileResource = new DomainProfileResource();
System.out.println("domainProfile.getCity()*************" + domainProfile.getCity());
System.out.println("domainProfile.getAddress()*************" + domainProfile.getAddress());
domainProfileResource.setCity(domainProfile.getCity());
domainProfileResource.setAddress(domainProfile.getAddress());
// return new ResponseEntity<DomainProfileResource>(domainProfileResource, HttpStatus.OK);
return domainProfileResource;
// return domainProfile;
}
}
When I run test case I got An error while we got values in domainprofile.city and domainprofile.address.
Error Is :
java.lang.AssertionError: Status
Expected :200
Actual :500
It Is Working Fine When I return a plain text
can you do this
mockMvc.perform(get("/service/domainprofile/get/{id}", profileId) )
.andDo(print());
this will print the full response with exception , now if you see HttpMessageNotWritableException which was the issue I was facing , you should try to serialize your object using jackson and see if it works (spring internally uses Jackson ). For example , If any of your fields are null the Serialization will fail.