configure spring security and test api postman - spring-boot

Iam working in springboot application and iam trying to save the data in database, code is executing properly and not getting any error during execution but when iam trying to post the url in postman iam getting status: 401 unauthorized
any quick suggestion
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
#Autowired
UsersService userService;
#Autowired
PasswordEncoder passwordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http.csrf().disable() //TODO implementer csrf
.cors().and().authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
.antMatchers(HttpMethod.POST,"/ add-users").permitAll()
.and().authorizeRequests().anyRequest().authenticated();
}
#Service
public class UsersService implements UserDetailsService{
#Autowired
UsersRepository repo;
// #Autowired
// private PasswordEncoder passwordEncoder;
public Users save(Users u) {
String encodpass=new BCryptPasswordEncoder().encode(u.getPassword());
String confpass=new BCryptPasswordEncoder().encode(u.getConfirmepass());
u.setConfirmepass(confpass);
u.setPassword(encodpass);
u.setLock(false);
u.setEnable(true);
return repo.save(u);
}
#RestController
public class UsersController {
#Autowired
private UsersService service;
#PostMapping("/add-users")
public Users add(#RequestBody Users u) {
return service.save(u);
}`

First of all
remove this space and retest :
.antMatchers(HttpMethod.POST,"/ add-users").permitAll()
to
.antMatchers(HttpMethod.POST,"/add-users").permitAll()

Related

AuthenticationFailureBadCredentialsEvent never called

I use spring-boot 2.6.8 with spring security
When user don't enter correct information, i would like to do an operation.
So I created this class.
#Component
public class AuthenticationFailureEventListener implements ApplicationListener<AuthenticationFailureBadCredentialsEvent {
private LoginAttemptService loginAttemptService;
#Override
public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent e) {
WebAuthenticationDetails auth = (WebAuthenticationDetails) e.getAuthentication().getDetails();
loginAttemptService.loginFailed(e.getAuthentication().getName(), auth.getRemoteAddress());
}
}
If a user enter a bad password, this event is never called
Edit
For the security, I have this
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationEventPublisher authenticationEventPublisher;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationEventPublisher(authenticationEventPublisher).userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
...
}
The events are not published out of the box. You need to also declare an AuthenticationEventPublisher with code like this:
#Bean
public AuthenticationEventPublisher authenticationEventPublisher(
ApplicationEventPublisher applicationEventPublisher
) {
return new DefaultAuthenticationEventPublisher(applicationEventPublisher);
}
Please also have a look at this part of the reference documentation: https://docs.spring.io/spring-security/reference/servlet/authentication/events.html

Test spring boot controllers with JUnit5+Spring Security

I have a spring boot application and want to write integration tests for controllers. It is my SecurityConfig:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final MyUserDetailsService userDetailsService;
private final SessionAuthenticationProvider authenticationProvider;
private final SessionAuthenticationFilter sessionAuthenticationFilter;
#Override
public void configure(WebSecurity web) {
//...
}
#Override
protected void configure(HttpSecurity http) throws Exception {
/...
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
auth.userDetailsService(userDetailsService);
}
}
It is my controller:
#RestController
public class MyController {
//...
#GetMapping("/test")
public List<TestDto> getAll(){
List<TestDto> tests= testService.findAll(authService.getLoggedUser().getId());
return mapper.toTestDtos(tests);
}
}
I Created a test(JUnit 5):
#WebMvcTest(TestController.class)
class TestControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean(name = "mockTestService")
private TestService testService;
#Autowired
private TestMapper mapper;
#MockBean(name = "mockAuthService")
private AuthService authService;
private Test test;
#BeforeEach
void setUp() {
User user = new Test();
user.setId("userId");
when(authService.getLoggedUser()).thenReturn(user);
test = new Facility();
test.setId("id");
test.setName("name");
when(testService.findAll("userId")).thenReturn(singletonList(test));
}
#Test
void shouldReturnAllIpaFacilitiesForCurrentTenant() throws Exception {
mockMvc.perform(get("/test").contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$..id").value(test.getId()))
.andExpect(jsonPath("$..timeOut").value(test.getName()));
}
}
When I start the test I get an exception: Consider defining a bean of type 'com.auth.MyUserDetailsService' in your configuration.
It happens because I have not UserDetailsService bean in the test. What should I do:
Add 3 beans are required for SecurityConfig, like:
#MockBean
MyUserDetailsService userDetailsService;
#MockBean
SessionAuthenticationProvider authenticationProvider;
#MockBean
SessionAuthenticationFilter sessionAuthenticationFilter;
Add test implementation of SecurityConfig
something else
Which approach is better?
For writing tests for your controller endpoints using #WebMvcTest I would use the great integration from MockMvc and Spring Security.
Make sure you have the following dependency:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
Next, you can mock the authenticated user for your MockMvc requests and also set the username, roles, etc.
Either use an annotation to populate the authenticated user inside the SecurityContext for the test:
#Test
#WithMockUser("youruser")
public void shouldReturnAllIpaFacilitiesForCurrentTenant() throws Exception {
// ...
}
Or use one of the SecurityMockMvcRequestPostProcessors:
this.mockMvc
.perform(
post("/api/tasks")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"taskTitle\": \"Learn MockMvc\"}")
.with(csrf())
.with(SecurityMockMvcRequestPostProcessors.user("duke"))
)
.andExpect(status().isCreated());
You can find more information on this here.

Why is my /books/book POST endpoint returning unauthorized

I am learning Spring Security and am trying to make a simple web service that holds the title of a book. I am trying to implement my security so that everyone can use GET on my /books/book endpoint and authorized users can POST to /books/book. For some reason my POST endpoint is saying that my user is authorized even through i have added the authentication header with the correct credentials.
I have been looking at different examples online but I just can't seem to find out where my mistake was.
My UserDetailsService holds one user named batman that has a USER role that is required for POSTing to /books/book
#Component
public class CustomUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String string) throws UsernameNotFoundException {
return User.withDefaultPasswordEncoder().username("batman").password("pass").roles("ADMIN", "USER").build();
}
}
My SecurityConfig should allow a GET request for unauthorized users to /book/books. This part works.
A POST method to /book/books should be allowed only for authorized users. This part does not work and is returning unauthorized for everyone.
#EnableWebSecurity
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().and().cors().disable().authorizeRequests()
.antMatchers(HttpMethod.GET, "/books/book").permitAll()
.antMatchers(HttpMethod.POST, "/books/**").hasRole("ADMIN")
.and()
.httpBasic();
}
}
My BookController looks like this
#RestController
#RequestMapping("/books")
public class BookController {
#Autowired
private BookService bookService;
#GetMapping("/book")
public ResponseEntity<Object> getAll() {
return new ResponseEntity<>(bookService.getAll(), HttpStatus.OK);
}
#PostMapping("/book")
public ResponseEntity<Object> add(#RequestBody Book book) {
bookService.addBook(book);
return ResponseEntity.ok().build();
}
}
My BookService
#Service
public class BookService {
#Autowired
private BookRepository bookRepository;
public Collection getAll() {
return bookRepository.findAll();
}
public void addBook(Book book) {
bookRepository.save(book);
}
public void deleteBook(Long id) {
bookRepository.deleteById(id);
}
public void updateBook(Long id, Book book){
bookRepository.findById(id).map((entry) -> {
entry.setTitle(book.getTitle());
bookRepository.save(book);
return entry;
});
}
}
My BookRepository
#Repository
public interface BookRepository extends JpaRepository<Book, Long>{
List<Book> findBookByTitle(String title);
}
My Book class
#AllArgsConstructor
#NoArgsConstructor
#Data
#Entity
public class Book {
#Id
#GeneratedValue
private Long id;
private String title;
}
The expected result is unauthorized GET request to /books/book and authorized POST request to /book/books
Are you using Spring Boot?
One thing that seems strange to me is that you are not doing anything with your CustomUserDetailsService.
try adding to CustomSecurityConfig
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}
The problem was with the version of my Spring dependencies. When I upgraded the version the problem no longer occured.

How to renew access token with the refresh token in oauth2 in spring?

I am very new to spring and it is my first attempt at spring security with oauth2. I have implemented OAuth2 with spring security and I do get the access token and the refresh token. However, while sending the refresh token to get the new access token I got "o.s.s.o.provider.endpoint.TokenEndpoint - IllegalStateException, UserDetailsService is required."
The solution to similar problem by other users appeared to be attaching UserDetailsService with the endpoint.
So I did the same and now when I try to send the request to with grant_type: refresh_token and refresh_token: THE TOKEN along with the client id and secret, I get an error that the user was not found.
Please refer the WebSecurityConfiguration class below:
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService customUserDetailsService;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean ();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService)
.passwordEncoder(encoder());
}
#Override
protected void configure (HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/login**")
.permitAll()
.anyRequest()
.authenticated();
}
public PasswordEncoder encoder() {
return NoOpPasswordEncoder.getInstance();
}
}
Please refer the AuthorizationServerConfiguration class below:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore());
.userDetailsService(userDetailsService);
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
}
Please refer the ResourceServerConfiguration class below:
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter{
#Autowired
DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("scout").tokenStore(tokenStore());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests (). antMatchers ("/oauth/token", "/oauth/authorize **").permitAll();
// .anyRequest (). authenticated ();
http.requestMatchers (). antMatchers ("/api/patients/**") // Deny access to "/ private"
.and (). authorizeRequests ()
.antMatchers ("/api/patients/**"). access ("hasRole ('PATIENT')")
.and (). requestMatchers (). antMatchers ("/api/doctors/**") // Deny access to "/ admin"
.and (). authorizeRequests ()
.antMatchers ("/api/doctors/**"). access ("hasRole ('DOCTOR')");
}
}
The CustomUserDetailsService class for reference if required:
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UsersRepository userRepository;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Optional<Users> usersOptional = userRepository.findByEmail(email);
Users user = null;
if(usersOptional.isPresent()) {
System.out.println(usersOptional.isPresent());
user = usersOptional.get();
}else {
throw new RuntimeException("Email is not registered!");
}
return new CustomUserDetails(user);
}
}
As I think, the server should only check for the validity of the refresh token as we don't pass the user details with refresh token. So I don't know why it requires the userDetails in the first place.
Please help and guide if I am missing something!
Thanks in advance.
I don't sure. But as I see your code in WebSecurityConfiguration could wired default InMemoryUserDetailsManager UserDetailsService .That could be reason why you have 2 different provider. In one you write, from the other you read users. Please try change your code as I show below and let me know if it help:
Was:
#Autowired
private UserDetailsService customUserDetailsService;
My vision how should be:
#Autowired
private CustomUserDetailsService customUserDetailsService;

Client authorization not working for unit tests

Okay, so first thing that's important to know here is that I can use the actual web client as intended. I am able to register a user and login with it.
However, as I try to implement some unit tests, things are not working for me. This is how I am creating a user in my unit test:
#Autowired
private TestRestTemplate template;
#Value("${security.jwt.client-id}")
private String clientId;
#Value("${security.jwt.client-secret}")
private String clientSecret;
private static String USERS_ENDPOINT = "http://localhost:8081/users/";
public AppUser registerUser(String username, String password) {
AppUser appUser = new AppUser();
appUser.setUsername(username);
appUser.setPassword(password);
ResponseEntity<AppUser> appUserResponseEntity = template.withBasicAuth(clientId, clientSecret)
.postForEntity(USERS_ENDPOINT, appUser, AppUser.class);
AppUser registeredAppUser = appUserResponseEntity.getBody();
assertNotNull("Trying to register new user but the user ID is null which indicates it didn't work.",
registeredAppUser.getId());
return registeredAppUser;
}
The problem is that the status I read in appUserResponseEntity is HTTP 401 and the returned AppUser is invalid (the user is not created in the database).
I am also getting aInsufficientScopeException:
Caused by: org.springframework.security.oauth2.common.exceptions.InsufficientScopeException: Insufficient scope for this resource
at org.springframework.security.oauth2.provider.expression.OAuth2SecurityExpressionMethods.throwOnError(OAuth2SecurityExpressionMethods.java:71) ~[spring-security-oauth2-2.2.0.RELEASE.jar:na]
... 82 common frames omitted
Below you can find my AuthorizationServerConfigurerAdapter for the OAuth2 configuration.
#Configuration
#EnableAuthorizationServer
#EnableResourceServer
public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {
#Configuration
#EnableResourceServer
public class ResourceServer extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/**")
.and()
.authorizeRequests().anyRequest().access("#oauth2.hasScope('write')");
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(resourceIds);
}
}
#Value("${security.jwt.client-id}")
private String clientId;
#Value("${security.jwt.client-secret}")
private String clientSecret;
#Value("${security.jwt.grant-type}")
private String grantType;
#Value("${security.jwt.scope-read}")
private String scopeRead;
#Value("${security.jwt.scope-write}")
private String scopeWrite;
#Value("${security.jwt.resource-ids}")
private String resourceIds;
#Autowired
private TokenStore tokenStore;
#Autowired
private JwtAccessTokenConverter accessTokenConverter;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
configurer
.inMemory()
.withClient(clientId)
.secret(clientSecret)
.authorizedGrantTypes(grantType)
.scopes(scopeRead, scopeWrite)
.resourceIds(resourceIds);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
enhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
endpoints.tokenStore(tokenStore)
.accessTokenConverter(accessTokenConverter)
.tokenEnhancer(enhancerChain)
.authenticationManager(authenticationManager);
}
}
I have no idea why this is not working. Does anybody have an idea what the problem might be?
What I think is weird is that it fails while asking for write access for an anonymous user. WebSecurity should actually ignore the /users endpoint:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/pub/**", "/users");
}
So I'm not quite sure why the security filter chain is actually being active here.
Update:
What I've tried now is I started the server on 8080 and used TestRestTemplate template during the same debug session and tried to POST an AppUser to 8080 and 8081 - take a look:
As you can see the very same call works on 8080 (an actual server instance running) but not on 8081 - the server instance started for unit tests.
Obviously there's a problem with my configuration or something but I can't pin it down ..
I have no more words for this one:
http://localhost:8081/users

Resources