Spring Security Basic Auth not using CustomUserDetailsService - spring

my project is in newest Spring Boot + Jersey and I have a problem with login validation.
My security config is:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
#Autowired
private DataSource datasource;
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable().authorizeRequests()
.antMatchers("/api/user/**").permitAll()
.antMatchers("/api/**").authenticated().and().httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
auth.jdbcAuthentication().dataSource(datasource);
}
#Bean
public UserDetailsService userDetailsService()
{
return new CustomUserDetailsService(datasource);
}
}
but when I do this:
#POST
#Path("authenticate")
#Produces(MediaType.APPLICATION_JSON)
public Response authenticate(#HeaderParam("username") String username, #HeaderParam("password") String password)
{
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, password);
Authentication authentication = this.authManager.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = this.userService.loadUserByUsername(username);
return createOkResponse(userDetails.getUsername());
}
authenticate method is using inside InMemoryUserDetailsManager not CustomUserDetailsService which I need for login validation.
How do I change that?
if needed:
public class CustomUserDetailsService extends JdbcUserDetailsManager implements UserDetailsService
{
#Autowired
private UserRepository userRepository;
public CustomUserDetailsService(DataSource datasource)
{
setDataSource(datasource);
}
#Override
public CurrentUserInfo loadUserByUsername(String email)
throws UsernameNotFoundException
{
User user = userRepository.findByPrimaryEmailAndEnabledTrue(email);
handleUserNotFound(email, user);
return new CurrentUserInfo(user);
}
private void handleUserNotFound(String email, User user)
{
if (user == null)
{
throw new UsernameNotFoundException("No user found with email: " + email);
}
}
}
Starter dependencies:
compile("org.springframework.boot:spring-boot-starter-web") {
exclude module: 'spring-boot-starter-tomcat'
}
compile "org.springframework.boot:spring-boot-starter-jetty"
compile "org.springframework.boot:spring-boot-starter-security"
compile "org.springframework.boot:spring-boot-starter-aop"
compile "org.springframework.boot:spring-boot-starter-data-jpa"
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
compile "org.springframework.boot:spring-boot-starter-jersey"
compile("org.springframework.boot:spring-boot-starter-actuator") { exclude module: 'hsqldb' }

The problem is that you are "registering" a AuthenticationManager only usable for the /api URIs and it isn't exposed as a bean to the ApplicationContext. When using Spring Boot that will add a global one and inject a default user with a generated password in it.
Spring Boot autoconfig will detect the presence of an already availabe AuthenticationManager. To register a global one just create a method which takes a AuthenticationManagerBuilder as an argument and annotate if with #Autowired. Just make sure it isn't named configure as that will not work.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
auth.jdbcAuthentication().dataSource(datasource);
}

Related

Configuring both http basic and Form based authentication in latest Spring security 6.0

I'm trying to configure REST and Form based authentication in new Spring boot and spring secuirty latest. I went through following post Combining basic authentication and form login for the same REST Api and new spring configuration using SecurityFilterChain and created the following. As I learnt that WebSecurityConfigurerAdapter method in the above post is deprecated.
#Configuration
#Order(2)
#EnableWebSecurity
public class RESTBasedConfigurationAdapter {
#Autowired
private AuthenticationProvider authenticationProvider;
#Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.authorizeRequests().anyRequest().hasAnyRole(...)
.and().httpBasic()
.authenticationEntryPoint(authenticationEntryPoint());
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
}
and
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE)
#EnableWebSecurity
public class FormBasedConfigurationAdapter {
#Autowired
private AuthenticationProvider authenticationProvider;
#Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers(...)
.permitAll().anyRequest().authenticated()
.and().formLogin().loginPage("/login").successHandler(authenticationSuccessHandler)
.permitAll().and().logout().permitAll();
return http.build();
}
}
But the configure method FormBasedConfigurationAdapter's is never called. Please explain how to configure so that both http Basic and Form based Authentication can be done.
As far I under I want two flows.
One flow REST which uses the following AuthenticationProvider for logging STATELESS
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
#Lazy
private BCryptPasswordEncoder passwordEncoder;
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String userName = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails userDetails = userDetailsService.loadUserByUsername(userName);
if (passwordEncoder.matches(password, userDetails.getPassword())) {
return new UsernamePasswordAuthenticationToken(userName, password, userDetails.getAuthorities());
} else {
throw new BadCredentialsException(" Bad Credentials ");
}
}
#Override
public boolean supports(Class<?> authenticationType) {
return authenticationType.equals(UsernamePasswordAuthenticationToken.class);
}
}
and for other FormBased authentication, I would like to go through controller as below.
#PostMapping("/login")
public String login(#ModelAttribute("loginForm") LoginForm loginForm,
BindingResult bindingResult,
Model model) {
loginValidator.validate(loginForm, bindingResult);
securityService.login(loginForm.getUserName(), loginForm.getPasswd());
if (bindingResult.hasErrors()) {
return "login";
}
return "...";
}
to facilitate Validation for Authenticate using Service as below.
#Override
public void login(String username, String password) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
if (passwordEncoder.matches(password, userDetails.getPassword())) {
if (usernamePasswordAuthenticationToken.isAuthenticated()) {
SecurityContextHolder.getContext()
.setAuthentication(usernamePasswordAuthenticationToken);
logger.debug(String.format("Auto login %s successfully!", username));
}
} else {
throw new BadCredentialsException(" Bad Credentials ");
}
}
Please explain how to achieve this. I also tried doing both HttpSecurity mapping in the same class but it is not working due to various reasons.

Can't inject dependencies in Spring Security configuration

I have a spring project using Spring Security. I want to inject dependencies into my WebSecurityConfigurerAdapter extending class but the dependencies do not seem to be getting injected. My controllers also use dependency injection and it does work there.
My SecSecurity class:
#Configuration
#Component
#EnableWebSecurity
public class SecSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserPrincipalDetailsService userPrincipalDetailsService;
LogoutSuccessHandler handler = new LogoutSuccess();
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http. logout().and().
httpBasic()
.and()
.authorizeRequests()
.antMatchers( "/", "/home", "/user", "/vestigingen", "/available", "/logout").permitAll()
.anyRequest().authenticated()
.and().logout().logoutSuccessHandler(handler).deleteCookies("JSESSIONID").invalidateHttpSession(false).permitAll()
.and().csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
#Bean
public UserDetailsService userDetailsService() {
return new UserPrincipalDetailsService();
}
DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
daoAuthenticationProvider.setUserDetailsService(this.userPrincipalDetailsService);
return daoAuthenticationProvider;
}
}
It cannot find beans for the userPrincipalDetailsService class.
UserPrincipalDetailService class:
#Component
public class UserPrincipalDetailsService implements UserDetailsService {
private UserRepositorySQL userRepository = new UserRepositorySQL();
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
UserEntity user = userRepository.findUserByUsername(s);
UserPrincipal userPrincipal = new UserPrincipal(user);
return userPrincipal;
}
}
to clarify: I can't inject any dependencies within my SecSecurityConfig class eventhough i have tried annotating it in many different ways.
Define the bean name #Component("userPrincipalDetailsService") in the UserPrincipalDetailsService class.
#Component("userPrincipalDetailsService")
public class UserPrincipalDetailsService implements UserDetailsService {
private UserRepositorySQL userRepository = new UserRepositorySQL();
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
UserEntity user = userRepository.findUserByUsername(s);
UserPrincipal userPrincipal = new UserPrincipal(user);
return userPrincipal;
}
}

Spring boot 2+ Could not Autowire. There is more than one bean of 'UserDetailsService'

Hello Everyone i'm new in spring security and jwt. I'm implementing Jwt in my spring boot project to secure user login and i'm using spring boot 2.1.5
and i don't know much about new bean restriction in spring boot 2+ .
I need some help .. here i'm trying to #Autowired UserDetailsService and code run fine ..and result is also fine.. but intellij shows error at
#Autowired UserDetailsService jwtUserDetailsService
saying ... Could not autowire. There is more than one bean of UserDetailsService type.
Can anyone explain me what what happens wrong here why i can't autowired and why and what are the Autowired restriction in spring boot 2+ ?
And thanks in advance
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private JwtFilter jwtFilter;
#Autowired
private UserDetailsService jwtUserDetailsService; // here i got error only
#Autowired
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder);
}
#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.csrf().disable();
http.authorizeRequests().antMatchers("/api/user/add", "/generate").permitAll().anyRequest().authenticated().and() .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}
my customUserDetailService is
#Service
public class JwtUserDetailService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user != null) {
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), new ArrayList<>());
} else {
throw new UsernameNotFoundException("Username does't exists");
}
}
}
My JwtController class which expose restend point to generate jwt token
#CrossOrigin
#RestController
public class JwtController {
#Autowired
private JwtUtils jwtUtils;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private JwtUserDetailService jwtUserDetailService;
#PostMapping(value = "/generate")
public ResponseEntity<?> generateToken(#RequestBody JwtRequest jwtRequest) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(jwtRequest.getUsername(),
jwtRequest.getPassword()));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVAILD_CREDENTIALS", e);
}
final UserDetails userDetails = jwtUserDetailService.loadUserByUsername(jwtRequest.getUsername());
final String token = jwtUtils.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
}
My JwtFilter Class
#Component
public class JwtFilter extends OncePerRequestFilter {
#Autowired
private JwtUserDetailService jwtUserDetailService;
#Autowired
private JwtUtils jwtUtils;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtUtils.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
logger.warn("JWT Token does not begin with Bearer String");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.jwtUserDetailService.loadUserByUsername(username);
if (jwtUtils.validate(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
Other thing as just normal like entity, repository, and some secured restend points
UserDetailsService was provided by spring.
To Autowire you need to configure it with.
#Bean
public UserDetailsService getUserDetails(){
return new JwtUserDetailService(); // Implementation class
}
If you are not interested in Bean Configuration.
you can autowire JwtUserDetailService directly.
#Autowired
private JwtUserDetailService jwtUserDetailsService;
I got the same error in another context. The reason was the Idea donĀ“t know which bean of type 'UserDetailsService' to use.
My solution is through annotation Qualifier:
#Qualifier("beanNameWhichYouWantUse")
#Autowired
private UserDetailsService jwtUserDetailsService;
If use Idea: give mouse point on the error, select from context menu:
"More actions" -> "Add qualifier"
and finally select the bean
You can put this code in application.properties:
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration

Spring boot basic authentication

I'm using spring boot security to help me to make authentication...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
#Configuration
#EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable().authorizeRequests()
.anyRequest().authenticated().and().httpBasic();
}
}
I have a rest service to make login (on my controller) thats a post request that i send email and password and i like to use this service to make the authentication...
But i'm new on spring-boot / java... Can some one help me to make that right way?
Thanks.
You need to permit access to the login endpoint (at least). E.g.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/login", "/error").permitAll()
.antMatchers("/**").authenticated().and().exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"));
}
If I were you I would remove the #EnableWebSecurity (and let Spring Boot do it's job) as well. And then in the login endpoint you need to set the security context, e.g.
#PostMapping
public void authenticate(#RequestParam Map<String, String> map,
HttpServletRequest request, HttpServletResponse response) throws Exception {
Authentication result = authService.authenticate(map.get("username"), map.get("password"));
SecurityContextHolder.getContext().setAuthentication(result);
handler.onAuthenticationSuccess(request, response, result);
}
The authService should throw BadCredentialsException if the user cannot be authenticated. Here's a sample app that I used in a blog once: https://github.com/dsyer/mustache-sample/blob/7be8459173d0b65b6d44d05f86e581d358ea9b2e/src/main/java/com/example/DemoApplication.java#L177
Change add method in SpringSecurityConfig.java like Below
#Configuration
#EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserAuthenticationService userAuthenticationService;
#Autowired
private CustomAuthenticationProvider authenticationProvider;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(this.authenticationProvider).userDetailsService(this.userAuthenticationService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable().authorizeRequests()
.anyRequest().authenticated().and().httpBasic();
}}
Create CustomAuthenticationProvider.
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserAuthenticationService userAuthenticationService;
#Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String emailId = authentication.getName();
String password = (String) authentication.getCredentials();
UserDetails user = this.userAuthenticationService.loadUserByUsername(emailId);
if (user == null) {
throw new UsernameNotFoundException("Username not found.");
}
//Your password encoder here
if (!password.equals(user.getPassword())) {
throw new UsernameNotFoundException("Wrong password.");
}
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
return new UsernamePasswordAuthenticationToken(user, password, authorities);
}}
Create Custom UserService
#Service
public class UserAuthenticationService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmailAddressWithRole(email);
if (user == null) {
throw new UsernameNotFoundException("Username not found for " + email);
}
List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
for (Role roles : user.getRoles()) {
grantedAuthorities.add(new SimpleGrantedAuthority(roles.getRoleName()));
}
return new UserAuthenticationWrapperDto(user.getId(), user.getEmailAddress(), user.getPassword(),
user.getUserType(), user.getCompany().getId(), grantedAuthorities,user.getName());
}}

How to configure Spring for LDAP and JDBC?

In my web application, I have to use Spring Security and get the user's authentication details using LDAP and the user's authorization details using JDBC. The user submits a form and I get the username and password from it.
How Do I get the username and password in my WebSecurityConfig file?
How do I configure the authorization and authentication?
My WebSecurityConfig:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin()
.loginPage("/").permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.ldapAuthentication().userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups").contextSource(contextSource());
}
#Bean
public DefaultSpringSecurityContextSource contextSource() {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(
"ldap://mjkoldc-03.red.com");
contextSource.setUserDn("mj\\" + email);
contextSource.setPassword(password);
contextSource.setReferral("follow");
contextSource.afterPropertiesSet();
return contextSource;
}
}
Previously I was getting details using LDAPTemplate:
LdapQuery query = query().base("dc=metaljunction,dc=com")
.attributes("GivenName", "sn", "mail", "MobilePhone")
.where("ObjectClass").is("user").and("SamAccountName")
.is(email);
If you what to authentificate agains LDAP and Autorize (get user roles from JDBC) you should implement LdapAuthoritiesPopulator.
public class CustomAuthoritiesPopulator implements LdapAuthoritiesPopulator {
#Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
Collection<GrantedAuthority> gas = new HashSet<GrantedAuthority>();
gas.add(new SimpleGrantedAuthority("ADMIN"));
return gas;
}
}
and add it to your SecurityConfig
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.ldapAuthoritiesPopulator(new CustomAuthoritiesPopulator())
.userSearchFilter("yourfilter")
.contextSource(contextSource());
}
}
Like this all users authentificated via LDAP will automatically get "ROLE_ADMIN".

Resources