I have built a Java application with the REST API convention. I working on endpoint which returns objects only if object is connected with user by common id in database(ManyToOne annotation). In order to achieve that i need current logged user id for comapring it with object's user id. If Ids are the same, endpoint returns data. I know solutions as "Principal" or "Authentication" classes but they provide everything except of "id". I used spring security http basic for authentication.
My authentication classes:
#Component
public class CustomAuthenticator implements AuthenticationProvider {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
#Autowired
public CustomAuthenticator(UserRepository userRepository, #Lazy PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String login = authentication.getName();
String password = authentication.getCredentials().toString();
User user = userRepository.findByLogin(login).orElseThrow(() -> new EntityNotFoundException("User not found"));
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("Bad credentials");
}
return new UsernamePasswordAuthenticationToken(login, password, convertAuthorities(user.getRoles()));
}
private Set<GrantedAuthority> convertAuthorities(Set<UserRole> userRoles) {
Set<GrantedAuthority> authorities = new HashSet<>();
for (UserRole ur : userRoles) {
authorities.add(new SimpleGrantedAuthority(ur.getRole().toString()));
}
return authorities;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
SECURITY CONFIG CLASS:
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CustomAuthenticator customAuthenticator;
public SecurityConfig(CustomAuthenticator customAuthenticator) {
this.customAuthenticator = customAuthenticator;
}
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
return passwordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/api").permitAll()
.antMatchers("/api/register").permitAll()
//TODO everybody now has access to database, change it later
.antMatchers("/h2-console/**").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
http
.csrf().disable()
.headers().frameOptions().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticator);
}
}
Does someone know how to resolve that problem ?
You can use UserDetails class and set id for the username field, this class provides by spring security.
If you don't want that solution, you can create a Subclass extend UserDetails class and decide an id field. When receiving the request, parse principal to UserDetails or subclass extends UserDetails to get the id
Ex:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetails userPrincipal = (UserDetails)authentication.getPrincipal();
Related
I created my Custom UserDetailService and Security Config. When I allow to enter to secure page only authorized users - OK, but if users with roles -
HTTP Status 403 – Forbidden.
I think I do not work with roles correctly. Please, help
My UserService
public interface UserService extends UserDetailsService {
}
#Service
public class UserServiceImpl implements UserService{
#Autowired
private PasswordEncoder passwordEncoder;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = new User();
user.setUsername(username);
user.setPassword(passwordEncoder.encode("1"));
//There is the problem I think
List<SimpleGrantedAuthority> roleList = new ArrayList<>();
roleList.add(new SimpleGrantedAuthority("ADMIN"));
user.setRoleList(roleList);
//
user.setAccountNonExpired(true);
user.setAccountNonLocked(true);
user.setCredentialsNonExpired(true);
user.setEnabled(true);
return user;
}
}
Security Config
#Configuration
#EnableWebSecurity
#ComponentScan("something")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin*").authenticated() - it works
//.antMatchers("/admin*").hasRole("ADMIN") - it doesn't work
.anyRequest().permitAll()
.and()
.formLogin().permitAll()
.and()
.logout().permitAll()
.and().csrf().disable();
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
My User.class just in case
public class User implements Serializable, UserDetails {
//fields
}
In order to set your user's role to "ADMIN", you need to set the authority to "ROLE_ADMIN".
roleList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
A role is the same as an authority prefixed with "ROLE_".
The order should be like this:
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/admins/**").hasRole("ADMIN")
.antMatchers("/users/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
.and()
.....
.....
I'm using spring boot to develop an application, Here I want to show the "Name and Image" of the logged in user, So I use session to pass name and image after authentication. Its working if any user enter the user credentials (in login page) or if any logged in user directly type the URL for few minutes (www.abc.com/this/url). But after few minutes, session name and Image are not visible(session expired) but other function are working with that session. My code is
#Component
public class SecurityHandler implements AuthenticationSuccessHandler{
#Autowired
private UserService userService;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
HttpSession session = request.getSession();
String userName = null;
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
userName = ((UserDetails) principal).getUsername();
} else {
userName = principal.toString();
}
User user = userService.findBySSO(userName);
session.setAttribute("userName", user.getFirstName());
session.setAttribute("imgPathh", user.getImagePath());
response.sendRedirect(request.getContextPath()+"/dashboard/index");
}
}
Common jsp page
<h2><c:out value="${userName }"></c:out></h2>
I want to know why this session variable doesn't work even after few minutes after the authentication ( anyhow if we type the URL directly, it should pass this authentication, am I correct?)
Update 1.
Security config
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;
#Autowired
PersistentTokenRepository tokenRepository;
#Autowired
SecurityHandler securityHandler;
#Autowired
HttpSession session;
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers() // antmachers
.and().formLogin().loginPage("/login").successHandler(securityHandler).loginProcessingUrl("/login").usernameParameter("ssoId").passwordParameter("password")
.and().rememberMe().rememberMeParameter("remember-me").tokenRepository(tokenRepository)
.tokenValiditySeconds(86400).and().csrf().and().exceptionHandling().accessDeniedPage("/Access_Denied")
.and()
.sessionManagement().sessionFixation().migrateSession()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); //always, IF_REQUIRED,never ,stateless
http.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.invalidateHttpSession(true)
.permitAll();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
authenticationProvider.setHideUserNotFoundExceptions(false);
System.out.println("Error in DaoAuthenticationProvider");
return authenticationProvider;
}
#Bean
public PersistentTokenBasedRememberMeServices getPersistentTokenBasedRememberMeServices() {
PersistentTokenBasedRememberMeServices tokenBasedservice = new PersistentTokenBasedRememberMeServices(
"remember-me", userDetailsService, tokenRepository);
System.out.println("Error in PersistentTokenBasedRememberMeServices");
return tokenBasedservice;
}
#Bean
public AuthenticationTrustResolver getAuthenticationTrustResolver() {
System.out.println("Error in AuthenticationTrustResolver");
return new AuthenticationTrustResolverImpl();
}
}
It's called session timeout.
Once the session is timed out, or expired, that's it.
The user doesn't have any session in the server anymore.
The user will have to log-in again.
Try changing the session timeout if you want it to be retained longer.
Here is the error details when i run.
APPLICATION FAILED TO START
Description:
Field userDetailsService in com.word.security.WebSecurityConfig
required a bean of type
'org.springframework.security.core.userdetails.UserDetailsService'
that could not be found.
Action:
Consider defining a bean of type
'org.springframework.security.core.userdetails.UserDetailsService' in
your configuration.
Here is the WebSecurityConfig.java class
#Configuration
#EnableWebSecurity
#Service
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
// disable caching
http.headers().cacheControl();
http.csrf().disable() // disable csrf for our requests.
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.GET, "/login").permitAll()
.antMatchers(HttpMethod.POST, "/createuser").permitAll()
.antMatchers(HttpMethod.GET, "/user/1").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Create a default account
auth.userDetailsService(userDetailsService());
}
#Bean
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
}
Intellj IDEA shows the Could not Autowire error for userDetailsService below;
#Autowired
private UserDetailsService userDetailsService;
However, on my another class named SecurityService ;
#Service
public class SecurityService {
#Autowired
IUserService userService;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
public User activeUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
Optional<User> user = userService.getUserByName(username);
if (user.isPresent()) {
return user.get();
}
return null;
}
public void autologin(String username, String password) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, password, AuthorityUtils.createAuthorityList("USER"));
authenticationManager.authenticate(usernamePasswordAuthenticationToken);
if (usernamePasswordAuthenticationToken.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
}
UPDATE
UserController.Class
#RestController
public class UserController {
#Autowired
private IUserService userService;
#Autowired
private SecurityService securityService;
#RequestMapping(value = "/createuser", method = RequestMethod.POST)
public String createUser(#RequestBody User user, Model md) {
if (userService.checkExistUserName(user.getUserName())) {
md.addAttribute("LoginError", true);
return "bu kullanici adi ile bir kullanici bulunmaktadir. Lutfen baska bir kullanici adi ile deneyiniz";
}
User newUser = new User();
newUser.setUserName(user.getUserName());
newUser.setFirstname(user.getFirstname());
newUser.setUserMail(user.getUserMail());
newUser.setSurname(user.getSurname());
newUser.setUserPassword(user.getUserPassword());
userService.saveUser(user);
/* Automatic login after register */
securityService.autologin(user.getUserName(), user.getUserPassword());
return user.getId().toString();
}
I'm not getting the same error as i have on WebSecurityConfig.java.
But now i m getting the StackoverFlow Error shown below after attempting to create an user;
java.lang.StackOverflowError: null at
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:441)
~[spring-security-config-4.2.1.RELEASE.jar:4.2.1.RELEASE] at
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:442)
~
its like going into the recursive loop. Dont know how to update.
WebSecurityConfigurerAdapter contains this method:
public UserDetailsService userDetailsServiceBean() throws Exception
From Java Docs:
Override this method to expose a UserDetailsService created from configure(AuthenticationManagerBuilder) as a bean.
So try to override this method in WebSecurityConfig like this:
#Bean
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
One of the reason for this error is as you do not have implementation of this class. Create a class called CustomUserDetailsService implementing UserDetailsService and annotate it with #Component.
Refer to spring documentation for more.
All users can login successfully but all of them can open only url which are using permitAll() method. FOr url meta i have set the role "RADMIN" and after the user with that role is logged in he can not open meta or any other url because of 403 ERROR. The url which can be open are only "login", "logout", "home".
#Configuration
#ComponentScan("bg.package")
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationService authenticationService;
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/assets/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login", "/home", "/logout").permitAll()
.antMatchers("meta/**").hasAuthority("RADMIN")
.anyRequest().authenticated()
.and().addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
Md5PasswordEncoder encoder = new Md5PasswordEncoder();
auth.userDetailsService(authenticationService).passwordEncoder(encoder);
}
}
AuthService
#Service
public class AuthenticationService implements UserDetailsService {
#Autowired
private AuthDao authDao;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
AuthModel authModel = authDao.getUserInfo(email);
GrantedAuthority authority = new SimpleGrantedAuthority(authModel.getRank());
UserDetails userDetails = (UserDetails) new User(authModel.getName(), authModel.getPass(), Arrays.asList(authority));
return userDetails;
}
}
If you are checking the role of a user with hasAuthority() method you should also include prefix ROLE_ before your role name. So the part of your security configuration which checks for role should look like this:
.antMatchers("meta/**").hasAuthority("ROLE_RADMIN")
Alternatively instead of hasAuthority("ROLE_RADMIN") you can use hasRole("RADMIN").
I am using DaoAuthenticationProvider for authenciation but when I submit form loadUserByUsername is called twice by super.authenticate(authentication) intially it throws BadCredentialsException and then next time it login successfully
This process is working fine if I do not use passwordencoder but when I use it loadUserByUsername method is called twice.
Below is my code:
SecurityConfig
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("authenticationProvider")
AuthenticationProvider authenticationProvider;
#Autowired
#Qualifier("userDetailsService")
UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(authenticationProvider)
.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/admin/**")
.access("hasRole('ROLE_ADMIN')").and().formLogin()
.loginPage("/login").failureUrl("/login?error")
.usernameParameter("username").passwordParameter("password")
.and().logout().logoutSuccessUrl("/login?logout").and().csrf()
.and().exceptionHandling().accessDeniedPage("/403");
}
}
Authentication class
#Component("authenticationProvider")
public class LimitLoginAuthenticationProvider extends DaoAuthenticationProvider {
#Autowired
#Qualifier("userDetailsService")
#Override
public void setUserDetailsService(UserDetailsService userDetailsService) {
super.setUserDetailsService(userDetailsService);
}
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
try {
System.out.println("inside authenticate");
Authentication auth = super.authenticate(authentication);
return auth;
} catch (BadCredentialsException be) {
System.out.println("First call comes here ");
throw be;
} catch (LockedException e) {
throw e;
}
}
}
MyUserdetailsService class implments UserDetailsService
#Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private UserDao userDao;
/* below method is called twice if I am using passwordencoder,
initially authentication fails and then again immediately
on second call authentication succeed */
#Transactional(readOnly=true)
#Override
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
com.mkyong.users.model.User user = userDao.findByUserName(username);
List<GrantedAuthority> authorities = buildUserAuthority(user.getUserRole());
return buildUserForAuthentication(user, authorities);
}
private User buildUserForAuthentication(com.mkyong.users.model.User user, List<GrantedAuthority> authorities) {
MyUserDetails myUserDetails = new MyUserDetails (user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(), user.isAccountNonLocked(), user.isCredentialsNonExpired(), user.getEmailId(),authorities);
return myUserDetails;
}
private List<GrantedAuthority> buildUserAuthority(Set<UserRole> userRoles) {
Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();
// Build user's authorities
for (UserRole userRole : userRoles) {
setAuths.add(new SimpleGrantedAuthority(userRole.getRole()));
}
List<GrantedAuthority> Result = new ArrayList<GrantedAuthority>(setAuths);
return Result;
}
}
Can some please help me. I believe there is some change needed in SecurityConfig class but exactly where I am not able to figure out.
Finally after help from java_dude and SergeBallesta I got the resolution for my query.
After a lot of debug I saw that when isPasswordValid method was getting called inside DaoAuthenticationProvider class instead of calling method 1 it was calling method 2 from org.springframework.security.authentication.encoding.PlaintextPasswordEncoder which one is depreciated and on second call it was calling proper isPasswordValid
method 1.
Method 1
public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
checkSalt(salt);
return delegate.matches(rawPass, encPass);
}
Method 2
public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
String pass1 = encPass + "";
// Strict delimiters is false because pass2 never persisted anywhere
// and we want to avoid unnecessary exceptions as a result (the
// authentication will fail as the encodePassword never allows them)
String pass2 = mergePasswordAndSalt(rawPass, salt, false);
if (ignorePasswordCase) {
// Note: per String javadoc to get correct results for Locale insensitive, use English
pass1 = pass1.toLowerCase(Locale.ENGLISH);
pass2 = pass2.toLowerCase(Locale.ENGLISH);
}
return PasswordEncoderUtils.equals(pass1,pass2);
}
To work authentication properly just add below code in your SecurityConfig class in addittion to my current code in question.
#Bean
public DaoAuthenticationProvider authProvider() {
// LimitLoginAuthenticationProvider is my own class which extends DaoAuthenticationProvider
final DaoAuthenticationProvider authProvider = new LimitLoginAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
** and change this method code**
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider())
.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}