I'm trying to do custom user with the properties I want and to use authentication , the CustomUser extends the spring User , The user is returned by the CustomProvider which implements UsersDetailsService
#Service
#Qualifier("UserDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
#Transactional
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user=userRepository.findByEmail(email);
return new CustomUser(user.getName(),user.getPassword(),buildUserAuthority(user.getRoles()));
}
private List<GrantedAuthority> buildUserAuthority(Set<Role> userRoles) {
Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();
// add user's authorities
for (Role userRole : userRoles) {
setAuths.add(new SimpleGrantedAuthority(userRole.getRole()));
}
List<GrantedAuthority> Result = new ArrayList<GrantedAuthority>(setAuths);
return Result;
}
#Getter
#Setter
public class CustomUser extends User {
public CustomUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public String firstName;
public String lastName;
}
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("UserDetailsService")
private UserDetailsService customUserDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
/* auth.
jdbcAuthentication()
.usersByUsernameQuery(usersQuery)
.authoritiesByUsernameQuery(rolesQuery)
.dataSource(dataSource)
.passwordEncoder(bCryptPasswordEncoder);
*/
auth.userDetailsService(customUserDetailsService);
}
}
I have 2 issues :
1- I have commented the auth.jdbcAuthentication as I couldn't have the authentication and customProvider to work together, how can I use the database authentication with customuser ?
2- if I comment the jdbcAuthentication the customuser works but when I get the principal the password is null : authentication.getPrincipal().getPassword()
Update :
I have solved 2 by eraseCredentials(false) but still unable to do both ( authentication with custom user )
Old answer:
I have solved it by this :
auth.eraseCredentials(false).userDetailsService(customUserDetailsService).and().jdbcAuthentication()
.usersByUsernameQuery(usersQuery)
.authoritiesByUsernameQuery(rolesQuery)
.dataSource(dataSource)
.passwordEncoder(bCryptPasswordEncoder);
update:
I found that no need to use 2 types of authentication as spring will verify the password with the returned user.
Related
I am creating an application using Role based restriction to certain REST endpoints.
I have a Roles table, user_roles table and a users table.
ROLE_USER is working fine, and users with that Role can successfully access their pages. Even if I swap it around so that only ROLE_USER can access /admin/**, it works.
However, when I try use ROLE_ADMIN on the admin page, it never works. It always redirects me to the 403 Forbidden page
Security Config:
#Bean
DaoAuthenticationProvider provider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsServiceImpl);
provider.setPasswordEncoder(passwordEncoder);
return provider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(provider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/banking/**").hasAnyRole("USER", "ADMIN")
.antMatchers(HttpMethod.GET, "/admin/**").hasRole("ADMIN")
.and()
.formLogin().loginPage("/login").permitAll()
.and()
.logout().permitAll()
.and()
.exceptionHandling().accessDeniedPage("/403");
}
}
MyUserDetails:
public class MyUserDetails implements UserDetails {
private static final long serialVersionUID = -2067381432706760538L;
private User user;
public MyUserDetails(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<Role> roles = user.getRoles();
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
}
return authorities;
}
UserDetailsServiceImpl:
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserService userService;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUsername(username);
if(user == null) {
throw new UsernameNotFoundException("User not found");
}
return new MyUserDetails(user);
}
}
Would appreciate any help as I've been stuck on this for a while. Thanks!
Right now my authentication is done with username and password. I'd like to add one more step to it so that it checks if user is activated or not. I have a user table that holds the value if the user has activated the account.
I have my SecurityConfig.java
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// This somehow works but only if the account is not validated
// auth.authenticationProvider(new UserActivatedAuthenticationProvider(userService));
auth.userDetailsService(userDetailsService).passwordEncoder(new ShaPasswordEncoder(encodingStrength));
}
And UserActivatedAuthenticationProvider.java
#Component
public class UserActivatedAuthenticationProvider implements AuthenticationProvider {
private final UserService userService;
#Autowired public UserActivatedAuthenticationProvider(UserService userService) {
this.userService = userService;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
User user = userService.findByEmail(name);
if (user != null) {
if (!user.isActivated()) {
throw new BadCredentialsException(name + " email is not activated.");
}
}
Object credentials = authentication.getCredentials();
if (!(credentials instanceof String)) {
return null;
}
String password = credentials.toString();
Authentication auth = new UsernamePasswordAuthenticationToken(name, password);
return auth;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
I want to proceed with authentication only if the account is activated. I can't use userService in AuthenticationManagerBuilder because I can't get the username. I am using this project as a seed. In short... I also want to check the value of is_activated column and proceed based on that value as it now does (username and password validation).
You don't need an AuthenticationProvider. You need to implement the UserDetailsService as following;
#Service
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private UserService userService;
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = userService.findByEmail(username);
if(user == null) {
throw new UsernameNotFoundException(username);
}
return org.springframework.security.core.userdetails.User(username, user.getPassword(), user.isActivated(), true, true, true, user.getRoles().stream().map(role -> role.getRoleName()).map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
}
}
The spring class org.springframework.security.core.userdetails.User as an property named enabled for which you can pass your user.isActivated() flag from database.
You can do that by providing your custom user details service.
#Autowired
private CustomUserDetailsService customUserDetailsService ;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService ).passwordEncoder(new ShaPasswordEncoder(encodingStrength));
}
and
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserService userService;
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = userService.findByEmail(username);
if(user == null) {
throw new UsernameNotFoundException(username);
}
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("USER");
authorities.add(grantedAuthority);
return org.springframework.security.core.userdetails.User(username, user.getPassword(), user.isActivated(), true, true, true, authorities );
}
}
Now based on Boolean value of third param, spring security will auto allow/deny user login and will also give the message "User disabled" if user is not activated.
I'm implementing a project using Spring security oauth2, everything works perfectly, now I want to start digging deeper beyond the basics. I want to check if the user making the request is the actual user owner of the resource, the end result would be for example:
/private/users/{uuid}/clients returns all clients for the specified user.
So my controller now looks like this:
#RestController
public class HomeController {
#Autowired
private UserService userService;
#GetMapping(value = "/")
public String index() {
return "Hello world";
}
#GetMapping(value = "/private")
public String privateTest(Principal principal) {
User user = userService.get(principal.getName());
return user.getUuid();
}
}
EDIT: The full security code (working) for a better explanation.
ResourceServerConfig
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable().and()
.authorizeRequests()
.antMatchers("/","/home","/register","/login").permitAll()
.antMatchers("/private/**").authenticated();
}
}
CustomUserDetails with getters and setters off course
public class CustomUserDetails implements UserDetails {
private Collection<? extends GrantedAuthority> authorities;
private String password;
private String username;
private String uuid;
public CustomUserDetails(User user) {
this.username = user.getUsername();
this.password = user.getPassword();
this.uuid = user.getUuid();
this.authorities = translate(user.getRoles());
}
}
AuthorizationServerConfig
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("my-trusted-client")
.authorizedGrantTypes("client_credentials", "password")
.authorities("ROLE_CLIENT","ROLE_TRUSTED_CLIENT").scopes("read","write","trust")
.resourceIds("oauth2-resource").accessTokenValiditySeconds(5000).secret("secret");
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
}
Main
#SpringBootApplication
public class DummyOauthApplication {
#Autowired
private PasswordEncoder passwordEncoder;
public static void main(String[] args) {
SpringApplication.run(DummyOauthApplication.class, args);
}
#Autowired
public void authenticationManager(AuthenticationManagerBuilder builder, UserRepository repository, UserService service) throws Exception {
//Setup a default user if db is empty
if (repository.count() == 0) {
service.save(new User("user", "password", UUID.randomUUID().toString(), Arrays.asList(new Role("USER"), new Role("ACTUATOR"))));
}
builder.userDetailsService(userDetailsService(repository)).passwordEncoder(passwordEncoder);
}
private UserDetailsService userDetailsService(final UserRepository repository) {
return username -> new CustomUserDetails(repository.findByUsername(username));
}
}
So, using the way I've implemented. I can get the actual user but it implies a database query every time an endpoint is called. Getting the user and match with the user uuid.
I want to find another way that I can get the user and then compare if the uuid = user.getUuid()
Thanks in advance.
After some time and a lot of mistakes, I've managed to find a solution that I leave here. The CustomUserDetails can be seen in the question and from there you can easily get the uuid and match with the requested one.
public static CustomUserDetails getCurrentUser() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null) {
if (authentication.getPrincipal() instanceof CustomUserDetails) {
return (CustomUserDetails) authentication.getPrincipal();
}
}
throw new IllegalStateException("User not found!");
}
EDIT: if you want to return the user you do something like this
public class CustomUserDetails implements UserDetails {
private Collection<? extends GrantedAuthority> authorities;
private String password;
private String username;
private User user;
public CustomUserDetails(User user) {
this.username = user.getUsername();
this.password = user.getPassword();
this.user = user;
this.authorities = translate(user.getRoles());
}
}
And then in a Utils or something,
public static User getCurrentUser() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null) {
if (authentication.getPrincipal() instanceof CustomUserDetails) {
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
return userDetails.getUser();
}
}
throw new IllegalStateException("User not found!");
}
Thanks for all the effort.
Perhaps you could implement a custom AuthenticationProvider and store user details as Principal
Spring Security Authentication Provider
I'm trying to add bcrypt to a spring app of mine. Authentication works just fine without. But when I try to encode using bcrypt I get "Reason: Bad credentials" when trying to login.
My user model looks as follows.
#Entity
#Table(name="users") // user is a reserved word in postgresql
public class User extends BaseEntity {
private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
...
#Column(nullable=false)
private String password;
...
public String getPassword() {
return password;
}
public void setPassword(String password) {
String hashedPassword = passwordEncoder.encode(password);
this.password = hashedPassword;
}
...
}
My SecurityConfig looks as follows.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
private BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
...
}
Do the above seem right? Do I need to do more than what I've already done?
My bad for not posting enough code. Naturally my user model didn't tell the entire story. I also have a class called SecurityUser which I've posted below. Due to the copy constructor the password gets hashed twice.
public class SecurityUser extends User implements UserDetails {
private static final long serialVersionUID = 867280338717707274L;
public SecurityUser(User user) {
if(user != null)
{
this.setId(user.getId());
this.setName(user.getName());
this.setEmail(user.getEmail());
this.setPassword(user.getPassword());
this.setRoles(user.getRoles());
}
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
Set<Role> userRoles = this.getRoles();
if(userRoles != null)
{
for (Role role : userRoles) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getName());
authorities.add(authority);
}
}
return authorities;
}
...
}
I've made my passwordEncoder method public and promoted it to a bean so I can autowire it into my UserService which is shown below. That way I only have to change encoder in one place if I ever decide to do so.
#Service
public class UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private PasswordEncoder passwordEncoder;
public User create(User user) {
String hashedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(hashedPassword);
return userRepository.save(user);
}
...
}
Here is how I would set it up.
User Table has 4 properties (amongst others)
id (auto increment)
username (or email_address) field
password field.
enabled (value will be either 1 or 0)
Role table (3 properties)
id (auto increment)
user_id (user table foreign key)
authority;
Create Java Entities for the two tables.
Spring Security Configuration Class looks like:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
String usrsByUnameQry = "SELECT u.email_address, u.password, u.enabled FROM user u WHERE u.email_address=?";
3String authByUnameQry = "SELECT u.email_address, r.authority FROM user u, role r WHERE u.id=r.user_id AND u.email_address=?";
auth
.eraseCredentials(false)
.jdbcAuthentication()
.dataSource(dataSource)
.passwordEncoder(passwordEncoder())
.usersByUsernameQuery(usrsByUnameQry)
.authoritiesByUsernameQuery(authByUnameQry);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.usernameParameter("username") //username property defined in html form
.passwordParameter("password") //password property defined in html form
// url that holds login form
.loginPage("/signin")
.loginProcessingUrl("/signin/authenticate")
.failureUrl("/loginfail")
// Grant access to users to the login page
.permitAll()
.and()
.logout()
.logoutUrl("/signout")
.deleteCookies("JSESSIONID")
.logoutSuccessUrl("/signin")
.and()
.authorizeRequests()
.antMatchers("/foo/**").permitAll()//Grant access to all (no auth reuired)
.antMatchers("/").hasAnyAuthority("ROLE_USER","ROLE_ADMIN") //Grant access to only users with role "ROLE_USER" or "ROLE_ADMIN"
}
#Bean(name = "authenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Bean
public TextEncryptor textEncryptor(){
return Encryptors.noOpText();
}
I am trying to save additional data in de user principal object.
What i did was:
implement the "UserDetails" interface to my existing user class where my additional data is saved ( like email address etc. ).
#Entity
public class User implements UserDetails {
Then i created a UserDetailsService implementation:
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
UserDAO userDAO;
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = userDAO.findOneByUsername(username);
if (user == null)
throw new UsernameNotFoundException("username " + username
+ " not found");
System.out.println("---------------------> FOUND ------------->"
+ user.getEmail());
return user;
}
}
Last step was to add the UserDetailsService in my Security configuration.
#Configuration
#EnableWebMvcSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(userDetailsService());
// ...
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.userDetailsService(userDetailsService());
// ...
}
#Override
protected UserDetailsService userDetailsService() {
return userDetailsService;
}
I see in my console that "loadUserByName" gets called twice ( because of the "Found" output ).
When i try to access the principal object in my controller ->
System.out.println(SecurityContextHolder.getContext()
.getAuthentication().getPrincipal());
I dont get my additional data.
When i try to cast it to my User object i get a could not cast exception.
Is there anything I am missing??
Thank you in advance.
Ok. My problem was hidden in the code i didnt post.
I thought this detailsService is only to get additional details but it is used for the login itself.
I had "jdbcAuthentication" configured additionally and spring seemed to use this always.
Now that i only got the detailsService configured everything works fine.
edit.:
so i only had to delete this code:
auth.jdbcAuthentication() .dataSource(dataSource)
* .passwordEncoder(passwordEncoder) .usersByUsernameQuery(
// ....
And now it also works with my code in the question above.
Create Extention class:
public class CustomUserDetails extends org.springframework.security.core.userdetails.User{
private User user;
public CustomUserDetails(User user, Collection<? extends GrantedAuthority> authorities) {
super(user.getName(), user.getPassword(), authorities);
this.user = user;
}
public CustomUserDetails(User user, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
super(user.getName(), user.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
this.user = user;
}
public User getUser() {
return user;
}
}
Than add it to UserDetailsService:
#Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException, DataAccessException {
UserDetails userDetails = null;
User user = userService.getByLogin(login);
userDetails = new CustomUserDetails(user,
true, true, true, true,
getAuthorities(user.getRole()));
return userDetails;
}
Get it!
(CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()