I'm working on with spring boot security layer to authenticate and authorize the user.Now, i would like to do some sample app using multi http security configuration.I have the scenario like there will be two login pages with different URL mappings("/managementLogin","/othersLogin").
I can understood how to configure multi httpsecurity configs but i need to validate the users from two tables.If the management users loggedIn i need to validate the user from management table through DAO layer using UserDetailsService else if any other users loggedIn i need to validate from other_users table.
Could anybody help me to know how to configure the multi http config and dao layer using UserDetailsService with spring boot security ?
Here is my basic code snippet,
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("userDetailsService")
UserDetailsService userDetailsService;
#Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private RESTAuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private RESTAuthenticationSuccessHandler authenticationSuccessHandler;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// for testing authentication purpose using inMemory db
/*
* auth.inMemoryAuthentication().withUser("user").password("user").roles
* ("USER").and().withUser("admin") .password("admin").roles("ADMIN");
*/
// Dao based authentication
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/home").permitAll();
http.authorizeRequests().antMatchers("/rest/**").authenticated();
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
http.formLogin().successHandler(authenticationSuccessHandler);
http.formLogin().failureHandler(authenticationFailureHandler);
http.logout().logoutSuccessUrl("/");
// CSRF tokens handling
http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/registerUser","/register.html");
}
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
}
TIA..,
Implement a custom UserDetailsService like this:
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserDaoTableOne userDaoTableOne;
#Autowired
private UserDaoTableTwo userDaoTableTwo;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails user = userDaoTableOne.find(username);
if(user == null){
user = userDaoTableTwo.find(username);
}
if (user == null) {
throw new UsernameNotFoundException(String.format("Username '%s' not found", username));
}
return user;
}
}
Implement two DaoAuthenticationProvider with his own UserDetailsService and inject both providers to the authenticationManager.
I don't know what is the requisite for two distinct login endpoints but at first I think is a bad idea.
You can create different Authentication objects an let the AuthenticationManager choose the correct AuthenticationProvider based in the supports method.
Indeed you will need to use, two user detail services. But, that wont be enough. I suggest you to create another ApplicationSecurity2 class with different order.
Spring security is built on an ordered list of filter chains.
see the answer given here by Dave Sayer. Then you can handle different urls, as you want.
in my case I checked into two repositories, Below an exemple that I use:
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AbstractUser user;
try {
user = clientRepository.findByUsername(username);
}
catch (Exception userException) {
try {
user = adminRepository.findByUsername(username);
}
catch (Exception adminException) {
throw new UsernameNotFoundException("No user present with username : " + username);
}
}
return user;
}
I have to handle around same issue , i have autowired httprequest class in userdetail service and get request params type and drive my logic based on that.
you can directly solve the issue as the recommended solutions, but you can create a simple trick to define two different UserDetailsService as here I have two user one as a normal user and another as an editor :
editor
#Log4j2
#RequiredArgsConstructor
#Service
public class EditorService implements UserDetailsService {
private final EditorRepository editorRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if(username == null || "".equals(username)){
throw new UsernameNotFoundException("null value");
}
Optional<Editor> editor = editorRepository.findByUsername(username);
if(editor.isPresent()){
log.info("created under editor service: " + editor.get());
return editor.get();
}
throw new UsernameNotFoundException("does not exists");
}
}
user
#Log4j2
#RequiredArgsConstructor
#Service
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if(username == null || "".equals(username)){
throw new UsernameNotFoundException("null");
}
Optional<User> user = userRepository.findByUsername(username);
if(user.isPresent()){
log.info("cretaed under User service : " + user.get());
return user.get();
}
throw new UsernameNotFoundException("does not exists");
}
}
then on the configurations side, we can use of spring order mechanism :
user config :
#EnableWebSecurity
#Configuration
#RequiredArgsConstructor
#Order(1)
public class UserWebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserService userService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/user/**")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(10);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.userService).passwordEncoder(passwordEncoder());
}
}
Editor config :
#EnableWebSecurity
#Configuration
#RequiredArgsConstructor
public class EditorWebSecurityConfig extends WebSecurityConfigurerAdapter {
private final EditorService editorService;
#Lazy
private final PasswordEncoder passwordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http // all other requests handled here
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.editorService).passwordEncoder(passwordEncoder);
}
}
Related
After finishing startup spring boot, Spring Security's configuration can not update . how to add new user or update user password or user role without restart spring boot? Because the page was redirected to the /login?error page, when I use new password to login.
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
MemberMapper memberMapper;
Logger logger = Logger.getLogger(this.getClass());
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.addFilterBefore(new loginFilter(), AnonymousAuthenticationFilter.class).
authorizeRequests().antMatchers("/","/register/**","/log/**").permitAll();
http.formLogin().loginPage("/log/toLogin")
.loginProcessingUrl("/log/login")
.usernameParameter("memacc")
.passwordParameter("mempwd")
.failureHandler(new AppsAuthenticationFailureHandler());;
http.logout().logoutSuccessUrl("/");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
List<Members> allMembers = memberMapper.getAllMembers();
for (Members members : allMembers){
String[] roleList = members.getRoleList().split(",");
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser(members.getMemacc()).password(new BCryptPasswordEncoder().encode(members.getMempwd())).roles(roleList);
}
}
}
When you do the login process, spring boots saves the user profile in memory.
If you add a new ROLE for a logged user for example and save in database, the user profile in memory didn't change and thats why you have to update this data.
You can make it with:
User user = userService.findById(idUser);
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getRoles()));
With this command, you update the user profile in spring-security memory.
thanks all,
I found the solution.
...
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
MyUserDetailsService myUserDetailsService;
Logger logger = Logger.getLogger(this.getClass());
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.addFilterBefore(new loginFilter(), AnonymousAuthenticationFilter.class).
authorizeRequests().antMatchers("/","/register/**","/log/**").permitAll();
http.formLogin().loginPage("/log/toLogin")
.loginProcessingUrl("/log/login")
.usernameParameter("memacc")
.passwordParameter("mempwd");
http.logout().logoutSuccessUrl("/");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService);
}
}
...
use the class implement UserDetailsService inteface. when user log in then authentate that user.
...
#Service
public class MyUserDetailsService implements UserDetailsService {
#Autowired
MemberMapper memberMapper;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Members member = memberMapper.getOneMemberByMemacc(username);
if(member == null)
throw new UsernameNotFoundException(username + " not found");
UserDetails userDetails = User.builder()
.username(member.getMemacc())
.password("{noop}" + member.getMempwd())
.roles(member.getRoleList()).build();
return userDetails;
}
}
...
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()
);
}
I am trying to implement a UserCache in my application to avoid to make multiple calls to the User table in the case I am using the basic authentication. I created my CacheConfig following the accepted answer of this topic, in which the CachingUserDetailsService is used to manage the user cache. Bellow is the code of the UserService, CacheConfig and SecurityConfig:
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
public UserService(UserRepository repository) {
this.userRepository = repository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AddInUser user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("O usuário " + username + " não pode ser encontrado!"));
UserDetails userDetails = User
.builder()
.username(user.getUsername())
.password(user.getPassword())
.roles("USER")
.build();
return userDetails;
}
#Transactional
public AddInUser save(AddInUser user) {
return userRepository.save(user);
}
}
#EnableCaching
#Configuration
public class CacheConfig {
public static final String USER_CACHE = "userCache";
/**
* Define cache strategy
*
* #return CacheManager
*/
#Bean
public CacheManager cacheManager() {
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
List<Cache> caches = new ArrayList<>();
//Failure after 5 minutes of caching
caches.add(new GuavaCache(CacheConfig.USER_CACHE,
CacheBuilder.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build()));
simpleCacheManager.setCaches(caches);
return simpleCacheManager;
}
#Bean
public UserCache userCache() throws Exception {
Cache cache = cacheManager().getCache("userCache");
return new SpringCacheBasedUserCache(cache);
}
}
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
};
#Autowired
private UserRepository userRepository;
#Autowired
private UserCache userCache;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues())
.and()
.authorizeRequests()
.antMatchers("/api/**")
.authenticated()
.and()
.httpBasic();
}
#Override
#Bean
public UserDetailsService userDetailsService() {
UserService userService = new UserService(userRepository);
CachingUserDetailsService cachingUserService = new CachingUserDetailsService(userService);
cachingUserService.setUserCache(this.userCache);
return cachingUserService;
}
}
The first call works well because it makes the call to the UserRepository. But on the second, it does not make the call to the repository (as expected) but I am getting the following WARN from BCryptPasswordEncoder:
2020-09-24 08:43:51.327 WARN 24624 --- [nio-8081-exec-4] o.s.s.c.bcrypt.BCryptPasswordEncoder : Empty encoded password
The warning is clear in its meaning and it fails to authenticate de user because of the null password. But I cannot understand why the user retried from cache has a null password if it was correctly stored. I am not sure how to solve it using the cache. Any thoughts?
Thank you very much for your help!
#M.Deinum comment is absolutely correct. You can refer to the doc here.
Note that this implementation is not immutable. It implements the
CredentialsContainer interface, in order to allow the password to be
erased after authentication. This may cause side-effects if you are
storing instances in-memory and reusing them. If so, make sure you
return a copy from your UserDetailsService each time it is invoked.
You can check the Spring-security source code if you are curious more:
private boolean eraseCredentialsAfterAuthentication = true;
...
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
// If the parent AuthenticationManager was attempted and successful then it
// will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent
// AuthenticationManager already published it
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
And User.java source code:
#Override
public void eraseCredentials() {
this.password = null;
}
By the way, it looks weird to cache login user that way. During login, it is better to get fresh record from DB instead of from cache. You can use cached user at other place but seldom see it is used during login.
If you really need to do that, you can change the default flag to false as mentioned in doc, just inject AuthenticationManager and call:
setEraseCredentialsAfterAuthentication(false)
I have this SpringSecurity config
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImpl detailsService;
#Autowired
public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(detailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/index").access("hasRole('USER')");
http.userDetailsService(detailsService);
}
}
when I open index.html page I get 403 error. It is because user is anonymous. But I want check user and set role and detail before opened this page. For this i write this service
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private HttpServletRequest request;
#Autowired
AuthService authService;
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
String ipAddress = request.getRemoteAddr();
AuthLkUser authLkUserByIp = authService.getAuthLkUserByIp(ipAddress);
if (authLkUserByIp == null) return null;
boolean b = authService.checkAuthLkUser(authLkUserByIp);
if (b) return null;
Set<GrantedAuthority> roles = new HashSet();
roles.add(new SimpleGrantedAuthority("USER"));
UserDetails userDetails = new org.springframework.security.core.userdetails.User(authLkUserByIp.getMsisdn(),
authLkUserByIp.getSubsId(), roles);
return userDetails;
}
}
But this service is never called.
In Spring Boot if you have a custom Spring Security Configuration then it will not use Auto Configuration for Spring Security.
In your Spring security configuration you haven't given anyway to authenticate a user. There are form based, basic header based, etc authentication available in Spring Security. You should use one of that to authenticate user so that during authentication your UserDetailsServiceImpl will be used to load the user.
Follow this post for form based login
I'm trying to build a Java EE app prototype using different frameworks. Everything works fine except the security layer. I chose to use Spring Security configured with Spring configuration.
The code is like this:
Spring Security Config
#Configuration
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
private MyUserDetailsService userDetailsService;
#Override
protected UserDetailsService userDetailsService () {
return this.userDetailsService;
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login/authenticate")
.failureUrl("/login?error=bad_credentials")
.and()
.logout()
.logoutUrl("/signout")
.deleteCookies("JSESSIONID")
.and()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/**").permitAll()
.and()
.csrf();
}
}
User Detail Service
#Service("myUserDetailsService")
public class MyUserDetailsService implements UserDetailsService
{
public static final Logger log = Logger.getLogger(MyUserDetailsService.class);
public MyUserDetailsService() {
}
#Autowired
private UserDao userDao;
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
final User user = getSystemUser(userName);
final List<GrantedAuthority> authorities = getUserAuthorities(user);
return buildUserForAuthentication(user, authorities);
}
private User buildUserForAuthentication(User user, List<GrantedAuthority> authorities) {
//...
}
private User getSystemUser(String alias) {
//...
}
private List<GrantedAuthority> getUserAuthorities(User user) {
//...
return null;
}
}
What I'm expecting this code to do is that when /login/authenticate is reached with the user & pass params, the underlying spring code invokes my user service, but this never happens.
What am I missing?
I'm using spring-security 3.2.3.RELEASE.
You should register your custom authentication in SecurityConfig class which have extended WebSecurityConfigureAdapter:
#Autowired
private MyUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(this.userDetailsService);
}
for 3.2.3 the config should be
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.userDetailsService);
}