Why is my /books/book POST endpoint returning unauthorized - spring

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.

Related

configure spring security and test api postman

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()

I'm trying to use spring security with PostgreSQL, I want get users from database but getting StackOverflowError: null

#ComponentScan(basePackages = {"conf"})
#ComponentScan(basePackages = {"application.controller"})
#ComponentScan(basePackages = {"applicaion.model"})
#ComponentScan(basePackages = {"applicaion.dao"})
#ComponentScan(basePackages = {"usersDetails"})
#SpringBootApplication
#EnableJpaRepositories
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Security config part
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Bean
#Override
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
#Override
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll();
}
#Bean
public PasswordEncoder passwordEncoder() {return NoOpPasswordEncoder.getInstance();}
}
User Entity
"felhasznalonev"==username and "felhasznalo"==user
in hungarian
in the database table has theese names
#Entity
#Table( name="felhasznalo")
public class User {
#Id
#GeneratedValue
private int id;
#Column( unique=true, nullable=false )
private String felhasznalonev;
#Column( nullable=false )
private String jelszo;
private int statusz;
public User() {}
public User(String felhasznalonev,String jelszo,int statusz) {
this.felhasznalonev=felhasznalonev;
this.jelszo=jelszo;
this.statusz=statusz;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFelhasznalonev() {
return felhasznalonev;
}
public void setFelhasznalonev(String email) {
this.felhasznalonev = email;
}
public String getJelszo() {
return this.jelszo;
}
public void setPassword(String password) {
this.jelszo = password;
}
#Override
public String toString() {
return null;
}
public int getStatusz() {
return statusz;
}
public void setStatusz(int statusz) {
this.statusz = statusz;
}
}
userServiceimpl part
#Service("userDetailsService")
public class UserServiceImpl implements UserService, UserDetailsService {
#Autowired
private UserRepository userRepository;
#Autowired
public UserServiceImpl(UserRepository userRepository){
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = findByUsername(username);
return new UserDetailsImpl(user);
}
#Override
public User findByUsername(String username) {
return userRepository.findByUsername(username);
}
}
UserDetailsImpl part
public class UserDetailsImpl implements UserDetails {
private User user;
public UserDetailsImpl(User user) {
this.user = user;
}
public UserDetailsImpl() {}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("USER"));
}
#Override
public String getPassword() {
return user.getJelszo();
}
#Override
public String getUsername() {
return user.getFelhasznalonev();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
UserService part
public interface UserService {
public User findByUsername(String username);
}
UserRepository
public interface UserRepository extends JpaRepository<User,Integer> {
User findByUsername(String username);
}
When i run the code everything looks fine, the basic login page come in, i enter the username/password from the database but nothing happen
and IntellIj write this:
2021-11-25 13:12:48.870 ERROR 13928 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Filter execution threw an exception] with root cause
java.lang.StackOverflowError: null
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:472) ~[spring-security-config-5.3.4.RELEASE.jar:5.3.4.RELEASE]
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:472) ~[spring-security-config-5.3.4.RELEASE.jar:5.3.4.RELEASE]
-||-
the connection with database is good, i can list users as well
Thanks for reading all this and sorry for bad english and mistakes, have a good day!
java.lang.StackOverflowError error tell you method declaration in service layer is not linked with any JpaRepository. Problem is came up from loadUserByUsername method in userServiceimpl. You declare method findByUsername without linked with Repository.
Change
User user = findByUsername(username);
To
User user = userRepository.findByUsername(username);
And UserServiceImpl Implements with UserDetailsService only. You need to change inSecurity config code because it has more problem like add wrong annotation and two method declare with same name etc...
Modified Security config
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService userDetailsService;
#Bean
public AuthenticationProvider authProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll();
}
#Bean
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
}
You have doubly declared userDetailsService with the same name,
First:
#Bean
#Override
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
Second:
#Service("userDetailsService")
public class UserServiceImpl implements UserService, UserDetailsService {
It may cause the problem. You should have only one instance of userDetailService.
In your SecurityConfig Can you try removing
#Bean
#Override
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
And changing the implementation for
#Override
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
to
#Autowired
private UserDetailsService userDetailsService;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}

After Spring Boot 2 upgade authorization server returns "At least one redirect_uri must be registered with the client."

I upgraded our authorization server from Spring Boot 1.5.13.RELEASE to 2.1.3.RELEASE, and now I can authenticate, but I can no longer access the site. Here is the resulting URL and error after the POST to /login.
https://auth-service-test-examle.cfapps.io/oauth/authorize?client_id=proxy-service&redirect_uri=http://test.example.com/login&response_type=code&state=QihbF4
OAuth Error
error="invalid_request", error_description="At least one redirect_uri must be registered with the client."
To troubleshoot, I started a fresh project based on the Spring Security 5.1.4.RELEASE sample "oauth2authorizationserver." I layered on the features used in our Spring Boot 1.5.13 authorization server making sure the unit tests passed (except one test class). If I #Ignore the failing tests and deploy the code I get the problem described above.
The problem is reproducible in the AuthenticationTests.loginSucceeds() JUnit test that passed before the upgrade. It expects a 302, but now it gets a 403 because it goes to the root of the authentication server. I published the entire example on GitHub
spring-security-5-upgrade_sso-auth-server
Clone the project and run the unit tests and you will see the failures.
Here are some of the key settings that can be found in the project on GitHub.
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
private final String privateKey;
private final String publicKey;
private final AuthClientDetailsService authClientDetailsService;
private final AuthenticationManager authenticationManager;
private final AuthUserDetailsService authUserDetailsService;
#Autowired
public AuthServerConfig(
#Value("${keyPair.privateKey}") final String privateKey,
#Value("${keyPair.publicKey}") final String publicKey,
final AuthClientDetailsService authClientDetailsService,
final AuthUserDetailsService authUserDetailsService,
final AuthenticationConfiguration authenticationConfiguration) throws Exception {
this.privateKey = privateKey;
this.publicKey = publicKey;
this.authClientDetailsService = authClientDetailsService;
this.authUserDetailsService = authUserDetailsService;
this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(authClientDetailsService);
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)
.accessTokenConverter(accessTokenConverter())
.userDetailsService(authUserDetailsService)
.tokenStore(tokenStore());
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(privateKey);
converter.setVerifierKey(publicKey);
return converter;
}
}
public class GlobalAuthenticationConfig extends GlobalAuthenticationConfigurerAdapter {
private final AuthUserDetailsService authUserDetailsService;
#Autowired
public GlobalAuthenticationConfig(final AuthUserDetailsService authUserDetailsService) {
this.authUserDetailsService = authUserDetailsService;
}
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(authUserDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
}
#Configuration
#Order(-20)
protected class LoginConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.requestMatchers().antMatchers(LOGIN, "/oauth/authorize", "/oauth/confirm_access")
.and()
.logout().permitAll()
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin().loginPage(LOGIN).permitAll();
// #formatter:on
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManager);
}
}
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final AuthUserDetailsService authUserDetailsService;
#Autowired
public WebSecurityConfig(AuthUserDetailsService authUserDetailsService) {
this.authUserDetailsService = authUserDetailsService;
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(authUserDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
}
What else needs to be done in Spring Boot 2.1.3.RELEASE to redirect the user back to the original webpage?
It's important that OAuth 2.0 clients register a redirect_uri with Authorization Servers as an Open Redirector mitigation. As such, Spring Boot 2.1.x has this as its default behavior, which is why you're seeing the error.
You can do one of two things:
Add redirect_uris, one for each client
Ideally, you'd update your clients to each have a registered redirect_uri, which would likely be retrieved in an implementation of ClientDetailsService:
public class MyClientDetailsService implements ClientDetailsService {
private final MyRespository myRepository;
public ClientDetails loadClientByClientId(String clientId) {
return new MyClientDetails(this.myRepository.getMyDomainObject(clientId));
}
private static class MyClientDetails extends MyDomainObject implements ClientDetails {
private final MyDomainObject mine;
public MyClientDetails(MyDomainObject delegate) {
this.delegate = delegate;
}
// implement ClientDetails methods, delegating to your domain object
public Set<String> getRegisteredRedirectUri() {
return this.delegate.getRedirectUris();
}
}
}
This setup with the private subclass - while not necessary - is nice because it doesn't tie the domain object directly to Spring Security.
Add a custom RedirectResolver
Or, you can customize the RedirectResolver, though this wouldn't secure against Open Redirects, which was the original reason for the change.
public MyRedirectResolver implements RedirectResolver {
private final RedirectResolver delegate = new DefaultRedirectResolver();
public String resolveRedirect(String redirectUri, ClientDetails clientDetails) {
try {
return this.delegate.resolveRedirect(redirectUri, clientDetails);
} catch ( InvalidRequestException ire ) {
// do custom resolution
}
}
}

String Boot REST security Error : type=Forbidden, status=403

This project I have used Spring boot, security authentication, JPA and REST
This gives me 403 error, which is of role base error. I have tried for 2 days could not solve please help.
Here I am sharing code.
This is my Security config class have roles based /hello for all user and /admin/all and /admin/add for admin user.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(encodePsw());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/api/secure/admin/**").hasRole("ADMIN").antMatchers("/api/secure/**")
.hasAnyRole("USER", "ADMIN").anyRequest().authenticated().and().formLogin().permitAll();
}
#Bean
public BCryptPasswordEncoder encodePsw() {
return new BCryptPasswordEncoder();
}
}
This is my Controller class have 3 method which is or 3 role based /hello for all user and /admin/all and /admin/add for admin user.
#RestController
#RequestMapping("/api/secure")
public class AdminController {
#Autowired
private UserRepository userRepo;
#Autowired
private BCryptPasswordEncoder passEncp;
#RequestMapping(value = "/hello")
public String hello() {
return "Hello..";
}
//#PreAuthorize("hasAnyRole('ADMIN')")
#RequestMapping(value = "/admin/add", method = RequestMethod.POST)
public String addUserByAdmin(#RequestBody User user) {
user.setPassword(passEncp.encode(user.getPassword()));
userRepo.save(user);
return "Add User Successfully";
}
//#PreAuthorize("hasAnyRole('ADMIN')")
#GetMapping("/admin/all")
public String securedHello() {
return "Secured Hello";
}
}
This is Role bean
#Entity
#Data
public class Role {
#Id
#GeneratedValue
private int roleId;
private String role;
}
This User Bean
#Entity
#Setter
#Getter
public class User {
#Id
private int userId;
private String username;
private String password;
private String email;
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(name="user_role", joinColumns = #JoinColumn(name="user_id"), inverseJoinColumns = #JoinColumn(name="role_id"))
private Set<Role> roles;
}
UserRepository interface
public interface UserRepository extends JpaRepository<User, Integer> {
User findByUsername(String username);
}
CustomUserDetails class
I think the problem in this part. I have save role like ROLE_USER, ROLE_ADMIN also tried without ROLE_
#Getter
#Setter
public class CustomUserDetails implements UserDetails {
private User user;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getRoles().stream().map(role -> new SimpleGrantedAuthority(""+role))
.collect(Collectors.toList());
}
#Override
public String getPassword() {
// TODO Auto -generated method stub
return user.getPassword();
}
#Override
public String getUsername() {
// TODO Auto-generated method stub
return user.getUsername();
}
Service class CustomUserDetailsService
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepo;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepo.findByUsername(username);
CustomUserDetails userDetails = null;
if(user!=null) {
userDetails = new CustomUserDetails();
userDetails.setUser(user);
}else {
throw new UsernameNotFoundException("User not found with name "+username);
}
return userDetails;
}
}
I have another Controller class having different URL mapping which is running
WebSecurityConfigurerAdapter has a overloaded configure message that takes WebSecurity as argument which accepts ant matchers on requests to be ignored.
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/hello");
}
You can ignore /hello based url as its for all users.
Actual:
return user.getRoles().stream().map(role->new SimpleGrantedAuthority("ROLE_"+role)).collect(Collectors.toList());
Expected:
return user.getRoles().stream().map(role->new SimpleGrantedAuthority("ROLE_"+role.getRole_name())).collect(Collectors.toList());

Custom AuthenticationProvider is not called

I want to have a basic auth-protected REST app. I followed the general instructions from http://www.baeldung.com/spring-security-authentication-provider in order to get the security working.
I ended up creating my implementation of AuthenticationProvider, but it never gets called by Spring. All requests end up with an error:
{"timestamp":1460199213227,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/test"}
without the AuthenticationProvider ever doing anything.
The app is annotation-based and here are the relevant bits:
Security setup
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Autowired
CustomAuthenticationProvider authenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authenticationProvider(authenticationProvider)
.authorizeRequests()
.anyRequest().authenticated().and().httpBasic();
}
}
AuthenticationProvider
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserDAO userDAO;
#Autowired
private Authenticator authenticator;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// This never gets called, I checked with debugger
String username = authentication.getName();
String password = authentication.getCredentials().toString();
User user = userDAO.findByUsername(username);
User authenticatedUser = authenticator.authenticate(user, password);
if (authenticatedUser == null){
throw new RESTAuthenticationException("Auth failed");
}
List<GrantedAuthority> authorityList = new ArrayList<>();
return new UsernamePasswordAuthenticationToken(user, authorityList);
}
#Override
public boolean supports(Class<?> aClass) {
return aClass.equals(UsernamePasswordAuthenticationToken.class);
}
}
Controller
#RestController
public class UserController {
#RequestMapping(value = "/test")
public ResponseEntity test(#AuthenticationPrincipal User user) {
return ResponseEntity.ok().body(user);
}
}
You receive a response with status code 401. This is the "unauthorized" http status code. It is probably caused by a missing/malformed Authorization header in your request.
You are using Http-Basic: it requires the following header in the request :
Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l
where the string QWxhZGRpbjpPcGVuU2VzYW1l is the string <user>:<password> base64 encoded.

Resources