spring boot oauth2 Can I get client info in the UserDetailsService - spring

public class UserService implements UserDetailsService {
#Autowired
private UserMapper userMapper;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SecurityContext context = SecurityContextHolder.getContext();
Authentication a = SecurityContextHolder.getContext().getAuthentication();
return userMapper.findUserByUsername(username);
}
public void create(User user) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
user.setPassword(encoder.encode(user.getPassword()));
userMapper.insert(user);
}
}
I want to get the Client type When I loadUserInfo, I find in the SecurityContextHolder, But ,I still found the Client type, How can I write this code?
Thanks for your review

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.

where do I get the "username" value from in spring security to pass to the loadUserByUsername(String username) method of UserDetailsService interface

I am trying to get a user from the database by authenticating the user based on username and password. I am using basic authentication to do this.
I am sending username and password in the authorization header of the rest api
In my controller the getUser() method calls the getuser() method of the UserService class
#GetMapping("/user/self")
public ResponseEntity<UserDto> getUser() {
UserDto UserDto = userService.getUser();
return new ResponseEntity<>(UserDto, HttpStatus.OK);
}
#PutMapping("/user/self")
public ResponseEntity<User> updateUser(#Valid #RequestBody Map<String, String> userMap, Principal principal) {
String username = principal.getName();
String firstname = userMap.get("firstName");
String lastName = userMap.get("lastName");
String password = BCrypt.hashpw(userMap.get("password"), BCrypt.gensalt(10));
User user = userService.getUserByUserName(username);
user.setFirstName(firstname);
user.setLastName(lastName);
user.setPassword(password);
userService.save(user);
return new ResponseEntity<>(user, HttpStatus.NO_CONTENT);
}
UserService class implements UserDetailsService and overrides the loadUserByUsername method that requires a username to be passed as an argument. my question is: how do I pass username to loadUserByUsername() method from my UserService class that I am calling from my controller. where does username value reside?
my understanding is - the Authentication Object contains user credentials that are passed to authentication object when a user types their credentials and send their request, how do I retrieve this username value
#Service
public class UserService implements UserDetailsService {
#Autowired
UserRepository userRepository;
public UserDto save(User user) {
String hashedPassword = BCrypt.hashpw(user.getPassword(), BCrypt.gensalt(10));
user.setPassword(hashedPassword);
userRepository.save(user);
UserDto userDto = new UserDto();
userDto.setId(user.getId());
userDto.setFirstName(user.getFirstName());
userDto.setLastName(user.getLastName());
userDto.setUserName(user.getUserName());
userDto.setAccountUpdatedAt(user.getAccountUpdatedAt());
userDto.setAccountCreatedAt(user.getAccountCreatedAt());
return userDto;
}
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user = userRepository.findByUserName(userName);
if (user == null) {
throw new UsernameNotFoundException(userName + "was not found");
}
return new UserPrincipal(user);
}
here is my repository code:
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
User findByUserName(String userName);
}
here is my authentication code:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
#Autowired
UserService userService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.authorizeRequests().antMatchers("/v1/user").permitAll()
.antMatchers("/v1/user/self").authenticated().and().httpBasic()
.authenticationEntryPoint(authenticationEntryPoint);
}
}
if you dealing with JPA then in your case you have to use userDetailsService instead of jdbcauthentication, therefor your security class would look like this :
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private UserService userService;
public SecurityConfig(UserService userService){
this.userService = userService;
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(10); // Number of rounds
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.userService).passwordEncoder(passwordEncoder());
}
}
then you can customize the authentication in the UserService class to satisfy the business need as the below sample :
#Service
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository){
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> user = userRepository.findByUsername(username);
if(user.isPresent()){
log.info("cretaed under User service : " + user.get());
return user.get();
}
throw new UsernameNotFoundException("empty or invalud user");
}
}
in addition, do not forget to create the findByUsername method in your repository also do not forget to implement org.springframework.security.core.userdetails.UserDetails in your module class:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String name);
}

Session Concurrency not working with custom UserDetails

Spring security maximum concurrent session setting is not working with custom UserDetailsService and custom UserDetails implementation. It allows login with same user from different machines.
But when I use custom UserDetailsService with Spring Security's UserDetails implementation User, it terminates first logged in session and logs in with the new session.
Security Configuration:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic().and()
.sessionManagement().maximumSessions(1).and().and().userDetailsService(customUserDetailsService);
}
}
Custom UserDetailsService with Spring Secutiy User implementation(Working):
#Service
public class CustomUserDetailsService
implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
final User user = new User("user", "password", Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
return user;
}
Custom UserDetailsService with Custom User implementation(Not Working):
#Service
public class CustomUserDetailsService
implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
final CustomUser user = new CustomUser();
user.setUsername("user");
user.setPassword("password");
user.setAuthorities(Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
user.setAccountNonLocked(true);
user.setAccountNonExpired(true);
user.setCredentialsNonExpired(true);
user.setEnabled(true);
return user;
}
Any help on this?

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

Spring Boot Basic Auth for each request, username validated for later requests

I have enabled the http basic auth in spring boot. I am seeing strange results when calling from Postman
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private ApiUserDetailsService userDetails;
#Bean
public ShaPasswordEncoder passwordEncoder() {
return new ShaPasswordEncoder();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
ReflectionSaltSource salt = new ReflectionSaltSource();
salt.setUserPropertyToUse("username");
DaoAuthenticationProvider dao = new DaoAuthenticationProvider();
dao.setUserDetailsService(userDetails);
dao.setPasswordEncoder(passwordEncoder());
dao.setSaltSource(salt);
auth.authenticationProvider(dao);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().csrf().disable().httpBasic();
}
Custome Userdetails
#Service
public class ApiUserDetailsService implements UserDetailsService {
#Autowired
private JdbcTemplate jdbcTemplate;
#Value("${spring.queries.users.query}")
private String usersQuery;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<UserDetails> usersBasedOnUserName = getUsersBasedOnUserName(username);
if (usersBasedOnUserName.size() == 0) {
throw new UsernameNotFoundException("Username " + username + " not found");
}
return usersBasedOnUserName.get(0);
}
private List<UserDetails> getUsersBasedOnUserName(String username) {
return jdbcTemplate.query(this.usersQuery, new String[] {username}, new RowMapper<UserDetails>() {
#Override
public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException {
String username = rs.getString(1);
String password = rs.getString(2);
return new User(username, password, AuthorityUtils.NO_AUTHORITIES);
}
});
}
}
For the fist time I execute the request, it expects the correct credentials. After I enter correct credentials, I get the result. But when I try for the the same request without credentials or diffrent password keepign username same, I wont get 401 error.
I will get 401 error only when I chnage username.
My API needs to be validated against each request.
Am I doing some thing wrong here.
Adding the stateless to config helped to solve issue.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().anyRequest().authenticated().and().csrf().disable().httpBasic();
}

Resources