I am trying to integrate spring security into my application and for some reason (unknown to me) I keep on getting a 403 error on every request. I am convinced it has something to do with spring security. Below is a snippet of my code for further details.
This is my first attempt of integrating spring security to my application so I could be missing something.
I have this in my WebSecurity class
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private JwtTokenProvider jwtTokenProvider;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.csrf().disable()
.cors().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers( HttpMethod.POST, "/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.apply(new JwtConfigurer(jwtTokenProvider));
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
And in my controller I have this
#RestController
#RequestMapping("/auth")
public class LoginController {
#Autowired
private UserService userService;
#RequestMapping(value = "/register", method = RequestMethod.POST, produces = "application/json", consumes = "application/json")
#ResponseBody
public Response<UserDto> register(#RequestBody Request<UserDto> request){
MessengerUser user = userService.saveUser(request.getData());
return new Response<>(new ModelMapper().map(user, UserDto.class));
}
#RequestMapping(value = "/sign-in", method = RequestMethod.POST, produces = "application/json", consumes = "application/json")
#ResponseBody
public Response<LoginDto> signin(#RequestBody Request<UserDto> request) {
LoginDto loginDto = userService.authenticateUser(request.getData());
return new Response<>(loginDto);
}
}
With my Request
{
"data":{
"username": "username",
"password": "password"
}
}
My suspicion is that it has something to do with my configuration but I'm not sure what else to try.
As it turns out, it was my mistake. I had the wrong request type, Silly mistake on my part.
Everything about was right but the request type was GET instead of POST
Related
I have added BCryptPasswordEncoder to my project and I store every user's password in my DB in encoded way. And now I can't log in using not encoded password but if I use encoded password I still can log in. As I know, AuthenticationManager have to encode entered password according to bean PasswordEncoder and compare with each password in my DB.
So the question is: why AuthenticationManager doesn't encode entered password?
P.S: As I know, authenticationConfiguration.getAuthenticationManager() replaces authenticationConfiguration.userDetailsService(personDetailsService).passwordEncoder(getPasswordEncoder()) since Spring Security 5.7.0-M2.
My SecurityConfig class:
#EnableWebSecurity
public class SecurityConfig {
private final PersonDetailsService personDetailsService;
#Autowired
public SecurityConfig(PersonDetailsService personDetailsService) {
this.personDetailsService = personDetailsService;
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests().antMatchers("/auth/login", "/error", "/auth/registration").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/auth/login")
.loginProcessingUrl("/process_login")
.defaultSuccessUrl("/hello", true)
.failureUrl("/auth/login?error")
.and()
.logout().logoutUrl("/logout").logoutSuccessUrl("/auth/login");
return http.build();
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
My RegistrationService:
#Service
public class RegistrationService {
private final PeopleRepository peopleRepository;
private final PasswordEncoder passwordEncoder;
#Autowired
public RegistrationService(PeopleRepository peopleRepository, PasswordEncoder passwordEncoder) {
this.peopleRepository = peopleRepository;
this.passwordEncoder = passwordEncoder;
}
#Transactional
public void register(Person person) {
person.setPassword(passwordEncoder.encode(person.getPassword()));
peopleRepository.save(person);
}
}
EDIT:
If I extend my SecurityConfig from deprecated WebSecurityConfigurerAdapter and change my code to this:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final PersonDetailsService personDetailsService;
#Autowired
public SecurityConfig(PersonDetailsService personDetailsService) {
this.personDetailsService = personDetailsService;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests().antMatchers("/auth/login", "/error", "/auth/registration").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/auth/login")
.loginProcessingUrl("/process_login")
.defaultSuccessUrl("/hello", true)
.failureUrl("/auth/login?error")
.and()
.logout().logoutUrl("/logout").logoutSuccessUrl("/auth/login");
}
#Bean
public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(personDetailsService).passwordEncoder(getPasswordEncoder());
}
}
then it works like it have to (entered by user password is getting encrypted by configure(AuthenticationManagerBuilder auth) and then it compares to other passwords from DB and if there is the same password found user is authenticated).
This is my admin controllers which i am not able to access
import mis.entity.User;
import mis.services.AdminService;
#Controller
#RequestMapping("/admin/")
public class AdminController {
#Autowired
private AdminService adminService;
#RequestMapping("adduser")
public String adduser() {
return "admin/adduser";
}
#RequestMapping(value = "do_register", method = RequestMethod.POST)
public String registerUser(#ModelAttribute("user") User user) {
System.out.println(user);
adminService.addUser(user);
return "redirect:/admin/adduser";
}}
This is happening after i configured spring security before activating spring security iwas able to access all routes.
#Configuration
#EnableAutoConfiguration
#EnableWebSecurity
public class MyConfig extends WebSecurityConfigurerAdapter {
#Bean
public UserDetailsService userDetailsService() {
return new UserDetailsServiceImpl();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/new").hasAnyAuthority("ADMIN", "CREATOR")
.antMatchers("/admin/**").hasAnyAuthority("ADMIN", "EDITOR")
.antMatchers("/delete/**").hasAuthority("ADMIN")
.antMatchers("/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.logout().permitAll()
.and()
.exceptionHandling().accessDeniedPage("/403")
;
}}
There is one more controller which i can access.
But i cant access this admincontroller i think that /admin/ is causing problem.
Edited code in MyConfig
http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN").antMatchers("/user/**").hasRole("USER")
.antMatchers("/**").permitAll().and().formLogin()
.loginPage("/signin")
.defaultSuccessUrl("/home")
.and().csrf().disable();
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
I'm building a Rest application and I need to apply a filter(this filters will do the validation of the credentials like email is valid, the username is not already toked, the password is strong and match confirmationPassword).
I want to apply this chain just for Registration and no other filter (like check if you are authenticated)
I have something like this in spring securityConfiguration
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final BCryptPasswordEncoder passwordEncoder;
private final UserServiceImplementation serviceImplementation;
private final JwtConfiguration jwtConfiguration;
#Autowired
public SecurityConfiguration(UserServiceImplementation serviceImplementation, BCryptPasswordEncoder passwordEncoder, JwtConfiguration jwtConfiguration) {
this.serviceImplementation = serviceImplementation;
this.passwordEncoder = passwordEncoder;
this.jwtConfiguration = jwtConfiguration;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(), jwtConfiguration))
.addFilterAfter(new JwtTokenVerifier(jwtConfiguration), JwtUsernameAndPasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest()
.authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(daoAuthenticationProvider());
}
#Override
public void configure(WebSecurity web) {
web.debug(true);
web.ignoring().antMatchers(HttpMethod.GET, "/register/**");
web.ignoring().antMatchers(HttpMethod.POST, "/register/**");
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsService(serviceImplementation);
return provider;
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
}
};
}
}
I will appreciate any input and any suggestions from the community!
You can create a POST mapping controller method to handle validation of registration. Firstly you should permit url of registration as follows in your SpringSecurityConfig.
#Autowired
private UserRepository userRepository;
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return new CustomUserDetailsServiceImpl(userRepository);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/api/v1/user/register").permitAll();
}
In UserController:
#RestController
#RequestMapping("/api/v1/user")
public class UserController {
#Autowired
private CustomUserDetailsServiceImpl userDetailsService;
#Autowired
public UserController(CustomUserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
#PostMapping(value = "/register")
public User register(#RequestBody User user) {
return userDetailsService.save(user);
}
}
UserRepository:
public interface UserRepository extends JpaRepository<User, String> {
User findByUsername(String username);
}
In CustomUserDetailsServiceImpl: You can filter validation in this implementation (email is valid, the username is not already toked, the password is strong and match confirmationPassword)
public User save(User user){
if(StringUtils.isEmpty(user.getUsername())) {
throw ExceptionFactory.getApiError(ExceptionEnum.BAD_REQUEST, "username");
}
if(StringUtils.isEmpty(user.getPassword())) {
throw ExceptionFactory.getApiError(ExceptionEnum.BAD_REQUEST, "password");
}
User registeredUser = new User();
registeredUser.setUsername(user.getUsername());
registeredUser.setPassword(passwordEncoder.encode(user.getPassword()));
registeredUser.setEnabled(true);
registeredUser.setRoles(Arrays.asList(new Role(RoleEnum.USER.getRole())));
return userRepository.save(registeredUser);
}
I have created a method for persisting user details in the database and i also have a controller which is exposed at the endpoint /register. I wanted to make the /register endpoint available to all. I have used spring security and gave permit all for the /register end point.
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final UserDetailsServiceImpl userDetailsService;
#Autowired
public WebSecurityConfiguration(UserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(
request -> request.antMatchers(HttpMethod.POST,"/register").permitAll()
.anyRequest().authenticated()
);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Can someone please explain or help me out why permitAll is not working in my case. As per the code i have written the /register end point should return the user details but it returns 403. The /register endpoint is a rest endpoint which takes the user details as input and return the user details as output once the detal is persisted to the database.
#Slf4j
#RestController
public class RegistrationController {
private final UserDetailsServiceImpl userDetailsService;
#Autowired
public RegistrationController(UserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
#PostMapping(value = "/register")
public ResponseEntity<Users> registerNewUser(#Valid #RequestBody Users users) throws EmailAlreadyExistsException {
Users usersDetails = userDetailsService.processRegistration(users);
log.info("{}, Information: Successfully persisted new user",this.getClass().getSimpleName());
return new ResponseEntity<>(usersDetails,HttpStatus.OK);
}
}
I guess you are calling the url via curl or postman. You must then disable CSRF or use a GET mapping instead.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(
request -> request.antMatchers(HttpMethod.POST,"/register").permitAll()
.anyRequest().authenticated()
);
}
We're currently writing a middleware using Spring Boot (1.2.6) to expose a REST API to our mobile/web applications. Middleware has no DB and is backed by some remote services of our customer.
For Login, we send username/password and a few more parameters (ip, user agent etc.) to remote services and get back some information about the user (name, last login, boolean change password flag etc..) including a session id. We wrote some beans to do this that we use in the corresponding controller:
#RestController
#RequestMapping(value = "/user", produces = "application/json")
public final class UserController {
#Autowired
private UserService userService;
#RequestMapping(value = "/login", method = RequestMethod.POST)
public LoginResponse login(#RequestBody final LoginRequest request, final HttpServletRequest servletRequest) {
final LoginResponse response = new LoginResponse();
final LoginServiceRequest serviceRequest = new LoginServiceRequest();
serviceRequest.setAdditionalRequestData(AdditionalRequestData.getInstance(servletRequest));
serviceRequest.setUsername(request.getUsername());
serviceRequest.setPassword(request.getPassword());
final LoginData serviceResponse = userService.login(serviceRequest);
response.setChangePassword(serviceResponse.isChangePassword());
// setting other params here...
return response;
}
}
As far as I saw, Spring Security usually depends on servlet filters which work before the controller. For instance, if I enable formLogin in configuration, it enables UsernamePasswordAuthenticationFilter which handles the authentication based on the AuthenticationManager beans I define. However I need the authentication response in this case and we send our request parameters encoded in JSON. So it seems that filters don't work for us.
Instead, I created an AuthenticationProvider and AuthenticationToken and changed above code to something like this:
#RestController
#RequestMapping(value = "/user", produces = "application/json")
public final class UserController {
#Autowired
private AuthenticationManager auth;
#Autowired
private UserService userService;
#RequestMapping(value = "/login", method = RequestMethod.POST)
public LoginResponse login(#RequestBody final LoginRequest request,
final HttpServletRequest servletRequest) throws ServletException {
final LoginResponse response = new LoginResponse();
final Authentication authenticationToken = new CustomAuthenticationToken(
request.getUserId(),
request.getPassword(),
AdditionalRequestData.getInstance(servletRequest)
);
final LoginData loginData =
((CustomAuthenticationToken) auth.authenticate(authenticationToken)).getLoginData();
response.setChangePassword(loginData.isChangePassword());
// setting other params here...
return response;
}
}
The AuthenticationProvider is responsible for calling the userService.login method as well as setting the AuthenticationToken into SecurityContext.
This is our security configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
// #formatter:off
http
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers("/version/**").permitAll()
.anyRequest().hasAnyRole(Constants.ROLE_USER);
// #formatter:on
}
}
This manuel approach actually works. We also make use of authorities (ROLE_USER etc..) for granting access to different endpoints.
Is there a better solution to this? Do you think we lose some features of Spring Security when we do this?