while experimenting around with spring boot, security, and data.
i just came across this scenario:
i use H2 in memory DB and poblate it with one user with liquibase on startup
with username and password.
now i want spring security to authenticate against H2. for that purpose i have this code:
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceImp);
}
and im implementing the userDetails as follows:
#Override
public UserDetails loadUserByUsername(String username) {
//this works, the user with pass is pulled
com.fix.demo.logic.user.User byUsername =
userRepository.findByUsername(username);
if (byUsername == null) {
System.out.println("No user found with username: ");
return null; //trow ex here
}
User user = new User(byUsername.getUsername(),
byUsername.getPassword(), true, true,
true, true, getAuthorities(Collections.singletonList("user")));
//System.out.println(user.toString());
//System.out.println(byUsername.toString()+ " "+byUsername.getPassword());
return user;
}
but my tests keep failing with
Authentication should not be null
and trying to log in will give me
bad credentials
what is necessary for my custom implementation of UserDetailsService to work?
this is the failing test:
#Test
public void loginWithValidUserThenAuthenticated() throws Exception {
FormLoginRequestBuilder login = formLogin()
.user("admin")
.password("root");
mockMvc.perform(login)
.andExpect(authenticated().withUsername("admin"));
}
One of the reasons is, the password might my encoded and you need to tell spring security to use an encoder. Add the following line to the configure override.
auth.setPasswordEncoder(passwordEncoder());
define the passwordEncoder bean.
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Related
Hi I have a Rest WS using WebSecurityConfigurerAdapter to implement HTTP Basic auth.
The password is allowed to be updated and I need to let the WS to pick up updated password without restarting server
Following are the codes:
SecurityConfig
// init a user with credentials admin/password
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
//disable csrf
.csrf().disable()
//authentic all requests
.authorizeRequests().anyRequest().authenticated().and().httpBasic()
//disable session
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(inMemoryUserDetailsManager());
}
#Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
Properties users = new Properties();
users.put("admin", "password,USER,enabled");
return new InMemoryUserDetailsManager(users);
}
}
The controller that will update password
#RestController
public class someController{
#Autowired
public InMemoryUserDetailsManager inMemoryUserDetailsManager;
// update password from password -> pass
#RequestMapping(...)
public updatePass(){
ArrayList<GrantedAuthority> grantedAuthoritiesList = new ArrayList<>();
grantedAuthoritiesList.add(new SimpleGrantedAuthority("USER"));
this.inMemoryUserDetailsManager.updateUser(new User("admin", "pass", grantedAuthoritiesList));
}
// another way that also doesn’t work
#RequestMapping(...)
public newUpdate(){
ArrayList<GrantedAuthority> grantedAuthoritiesList = new ArrayList<>();
grantedAuthoritiesList.add(new SimpleGrantedAuthority("USER"));
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("admin", "pass",
grantedAuthoritiesList);
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(auth);
SecurityContextHolder.setContext(context);
}
}
After calling updatePass() with credential admin/password for the first time, I can see that the password has been updated to "pass" in debugger
I assume that if I'm to call updatePass() again, I should use admin/pass. However it turned out to be still using the old admin/password.
Sources I referred to when writing this code source1 source2
*I'm using Advance Rest Client to make the calls
When you update the password, you have to set the UserDetails in springSecurityContext object if the user is authenticated.
instead of using SecurityContext, I overwrote function loadUserByUsername of interface UserDetailsService to let spring security always pick up the latest pwd from DB.
I have a list of users, and I want to use them in my basic auth.
MY code currently looks like this:
#Configuration
#EnableWebSecurity
public class BasicAuthConfig extends WebSecurityConfigurerAdapter {
#Bean
public PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
#Autowired
private ConfigService configService;
// Authentication : User --> Roles
// NoOpPasswordEncoder has been deprecated in Spring security so {noop} is being used to avoid errors
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().passwordEncoder(passwordEncoder())
.withUser("someuser")
.password("somepassword")
.roles("USER");
}
// Authorization : Role -> Access
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and().authorizeRequests()
.antMatchers("/actuator/**")
.permitAll()
.antMatchers("/tokenservice/**")
.hasRole("USER")
.antMatchers("/")
.permitAll()
.and().csrf()
.disable()
.headers()
.frameOptions()
.and().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}}
I want to replace "someuser" and "somepassword" with usernames and passwords from my list of users. Currently I can get the list with configService.getCOnfigurations().getUsers().
A user just has a username and a password, both strings. How do I go about getting all the usernames and all the passwords into .withUser()?
**EDIT
I made a simple for loop in the configure, that should do it, but whenever i try to post to my API, it says org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder:99 - Encoded password does not look like BCrypt
I used an online bcrypt generator to generate the passwords, and they look like this
<?xml version="1.0" encoding="UTF-8"?>
<Configurations>
<Port>8007</Port>
<EnableHttps>true</EnableHttps>
<KeyStorePath>classpath:ssl-server.jks</KeyStorePath>
<KeyPass>changeit</KeyPass>
<TokenTtlMillis>15000</TokenTtlMillis>
<Users Username="user1">
<Password>$2y$10$.8VQR6tJub5uVdVLByItQO8QYGZVuWPhLuBUTQSDJAvVpLAUmuqZ2</Password>
</Users>
<Users Username="user2">
<Password>$2y$10$r/CQz7PZp5banmSzr9OiDe2Kxrda4BhXIBXvvouRnm1w3M72wLQj.</Password>
</Users>
</Configurations>
the passwords are in plain just password and password2
Building on Claudio's answer with the DaoAuthenticationProvider:
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(userDetailsService())
.authenticationProvider(authenticationProvider());
}
#Override
protected UserDetailsService userDetailsService() {
return new MyUserDetailsService();
}
The UserDetailsService is where the real meat of your code would be. You would provide a custom implementation of the interface that reads from your XML. Assuming that you have a method getPassword(String username):
// Adding this import to demontrate where "User" is coming from
import org.springframework.security.core.userdetails.User;
public class MyUserDetailsService implements UserDetailsService {
#Override
public User loadUserByUsername(String username) {
return new User(username, getPassword(username), Arrays.asList(new SimpleGrantedAuthority("USER")));
}
private String getPassword(String username) {
// Get password from your XML
}
}
As for your BCrypt issue, the password hash gives me an invalid salt revision error. Try using your app directly to hash it, e.g.:
public static void main(String[] args) {
System.out.println(new BCryptPasswordEncoder().encode("password"));
}
Or to pass in a file with a password on each line (using Java 8):
public static void main(String[] args) throws IOException {
if (args.length != 1) {
System.out.println("Requires 1 parameter that points to a file.");
System.exit(1);
}
File f = new File(args[0]);
if (!f.isFile()) {
System.out.println("Not a file: " + f);
System.exit(1);
}
PasswordEncoder encoder = new BCryptPasswordEncoder();
try (Stream<String> lines = Files.lines(f.toPath())) {
lines.map(encoder::encode)
.forEach(System.out::println);
}
}
That will give you the Spring-generated hashes which you can then insert into your XML.
You can declare a DaoAuthentificationProvider in your WebSecurityConfigurerAdapter like this:
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
and give it your implementation of a passwordEncoder and a userDetailsService,
for which you have to implement the respective interfaces and their methods.
And then you can assign your authenticationProvider in your WebSecurityConfigurerAdapter class like this:
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
auth.authenticationProvider(authenticationProvider());
}
This way, your UserDetailService will provide all the available users and their credentials and you don't have to worry about that in the security configuration.
This way you can store the credentials in whatever way you want (simple file, nosql DB like MongoDB, etc.) and even change that implementation without impact on the way you authenticate with spring security.
Your UserDetailService should look somewhat like this:
public class SecUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository().findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(username);
} else {
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
grantedAuthorities.add(new SimpleGrantedAuthority(user.getRole().getName()));
return new org.springframework.security.core.userdetails.User(user.getName(), user.getPassword(),
grantedAuthorities);
}
}
}
Here I've used a UserRepository that takes care of loading all users from the storage of your choice. E.g. if you decide to store it in a file, it will load all users and their passwords from a file, and provide the method findByUsername that gives back the User object if one with a matching name is found. Your Repository can also take care of deleting users or modifying their names if needed.
I implemented this on spring boot 2.x, with getting users credentials from the console on server start; you can easily change it to load users from file or any other source:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger log = LogManager.getLogger();
#Override
protected void configure(HttpSecurity http) throws Exception {
// Note:
// Use this to enable the tomcat basic authentication (tomcat popup rather than spring login page)
// Note that the CSRf token is disabled for all requests
log.info("Disabling CSRF, enabling basic authentication...");
http
.authorizeRequests()
.antMatchers("/**").authenticated() // These urls are allowed by any authenticated user
.and()
.httpBasic();
http.csrf().disable();
}
#Bean
public UserDetailsService userDetailsService() {
log.info("Setting in-memory security using the user input...");
String username = null;
String password = null;
System.out.println("\nPlease set the admin credentials for this web application (will be required when browsing to the web application)");
Console console = System.console();
// Read the credentials from the user console:
// Note:
// Console supports password masking, but is not supported in IDEs such as eclipse;
// thus if in IDE (where console == null) use scanner instead:
if (console == null) {
// Use scanner:
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("Username: ");
username = scanner.nextLine();
System.out.print("Password: ");
password = scanner.nextLine();
System.out.print("Confirm Password: ");
String inputPasswordConfirm = scanner.nextLine();
if (username.isEmpty()) {
System.out.println("Error: user must be set - please try again");
} else if (password.isEmpty()) {
System.out.println("Error: password must be set - please try again");
} else if (!password.equals(inputPasswordConfirm)) {
System.out.println("Error: password and password confirm do not match - please try again");
} else {
log.info("Setting the in-memory security using the provided credentials...");
break;
}
System.out.println("");
}
scanner.close();
} else {
// Use Console
while (true) {
username = console.readLine("Username: ");
char[] passwordChars = console.readPassword("Password: ");
password = String.valueOf(passwordChars);
char[] passwordConfirmChars = console.readPassword("Confirm Password: ");
String passwordConfirm = String.valueOf(passwordConfirmChars);
if (username.isEmpty()) {
System.out.println("Error: Username must be set - please try again");
} else if (password.isEmpty()) {
System.out.println("Error: Password must be set - please try again");
} else if (!password.equals(passwordConfirm)) {
System.out.println("Error: Password and Password Confirm do not match - please try again");
} else {
log.info("Setting the in-memory security using the provided credentials...");
break;
}
System.out.println("");
}
}
// Set the inMemoryAuthentication object with the given credentials:
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
if (username != null && password != null) {
String encodedPassword = passwordEncoder().encode(password);
manager.createUser(User.withUsername(username).password(encodedPassword).roles("USER").build());
}
return manager;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
I have configured a JDBC data source and autowired the JDBCTemplate to execute custom SQL queries. I also have a simple HTTP Basic authentication:
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
However, I would like to use the user and password used for HTTP Basic to authenticate the user to the data base itself, i.e pass through the credentials of HTTP Basic to the data source and execute queries as the user who logged in with HTTP Basic authentication. I'm facing two issues here, one is that the username and password are in the application.properties file that I want to override every time a user authenticates and also (reload?) execute queries as that user instead of the ones specified in the properties file.
Update 1:
I could programmatically use username and password like below:
#Bean
#Primary
public DataSource dataSource() {
return DataSourceBuilder
.create()
.username("")
.password("")
.url("")
.driverClassName("")
.build();
}
But how to call this every time a user logs with the HTTP Basic auth with those credentials?
Use UserCredentialsDataSourceAdapter as #"M. Deinum" have suggested with some kind of filter or handling AuthenticationSuccessEvent.
Basically you should just call setCredentialsForCurrentThread method with current principal username and password.
You'll have to disable credential erasure for authentication manager in order to be able to retrieve user password after authentication.
#EnableWebSecurity
public static class Security extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.eraseCredentials(false) // for password retrieving
.inMemoryAuthentication()
.withUser("postgres").password("postgres1").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests().mvcMatchers("/").fullyAuthenticated();
}
}
Datasource adapter:
#Bean
public UserCredentialsDataSourceAdapter dataSource(DataSourceProperties properties) {
final UserCredentialsDataSourceAdapter dataSourceAdapter = new UserCredentialsDataSourceAdapter();
dataSourceAdapter.setTargetDataSource(DataSourceBuilder.create()
.driverClassName(properties.getDriverClassName())
.url(properties.getUrl())
.username(properties.getUsername())
.password(properties.getPassword())
.type(SimpleDriverDataSource.class) // disable pooling
.build());
((SimpleDriverDataSource) dataSourceAdapter.getTargetDataSource()).setDriverClass(org.postgresql.Driver.class); //binder won't set it automatically
return dataSourceAdapter;
}
AuthenticationSuccessHandler:
#Component
public static class AuthenticationHandler /*implements ApplicationListener<AuthenticationSuccessEvent> use that if your spring version is less than 4.2*/ {
private final UserCredentialsDataSourceAdapter dataSourceAdapter;
#Autowired
public AuthenticationHandler(UserCredentialsDataSourceAdapter dataSourceAdapter) {
this.dataSourceAdapter = dataSourceAdapter;
}
#EventListener(classes = AuthenticationSuccessEvent.class)
public void authenticationSuccess(AuthenticationSuccessEvent event) {
final Authentication authentication = event.getAuthentication();
final User user = (User) authentication.getPrincipal();
dataSourceAdapter.setCredentialsForCurrentThread(user.getUsername(), user.getPassword()); // <- the most important part
}
}
Or you can use Filter instead of event listener:
#Component
public static class DataSourceCredentialsFilter extends GenericFilterBean {
private final UserCredentialsDataSourceAdapter dataSourceAdapter;
#Autowired
public DataSourceCredentialsFilter(UserCredentialsDataSourceAdapter dataSourceAdapter) {
this.dataSourceAdapter = dataSourceAdapter;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
final User user = (User) authentication.getPrincipal();
dataSourceAdapter.setCredentialsForCurrentThread(user.getUsername(), user.getPassword());
chain.doFilter(request, response);
dataSourceAdapter.removeCredentialsFromCurrentThread();
}
}
See full example here.
I have two tables 'user' and 'role'.I want to create a login api (e.g '/login') which will take username and password as a json data. I want to check if given credential is a valid credential and if it is,then I want to set the user as authenticated user so that he/she may have the protected resources. I am new to spring boot framework and I don't know how to do so.I have read the offical documentation but cannot find any resources.Could someone help me on this?
You have number of choices to implement such authentication in Spring.
Case 1:- If you are building REST services then you can implement security in following ways:
i) - you can use Basic-Authentication to authenticate your user.
ii) - you can use OAuth2 to authenticate and authorize your user.
Case 2: If you are building web application
i) - you can use auth token (in case of Single page application SPA)
ii) - you can use session based authentication (traditional login form and all)
I Guess you are in beginner mode so i will recommend you to firstly understand the control flow user authentication in web app via login form. So Let's go through some code.
I'm assuming that you have set a basic spring project and now you are implementing security.
USER - Hibernate entity for your user table;
ROLE - Hibernate entity for your role table
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthProvider customAuthProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
// everyone is allowed tp view login page
http.authorizeRequests().antMatchers("/login").permitAll().and();
http.authorizeRequests().antMatchers("custom_base_path" + "**").authenticated().and().
formLogin().loginPage("/loginForm).loginProcessingUrl("/loginUser")
.usernameParameter("username").passwordParameter("password")
.defaultSuccessUrl("custom_base_path+ "home", true);
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthProvider);
}
//CustomAuthProvider
#Component
public class CustomAuthentiationProvider implements AuthenticationProvider{
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userid = authentication.getName();
String password = authentication.getCredentials().toString();
Authentication auth = null;
try {
//write your custom logic to match username, password
boolean userExists = your_method_that_checks_username_and_password
if(userExists ){
List<Role> roleList= roleDao.getRoleList(userid);
if (roleList == null || roleList.isEmpty()) {
throw new NoRoleAssignedException("No roles is assigned to "+userid);
}
auth = new UsernamePasswordAuthenticationToken(userid, password,getGrantedAuthorities(roleList));
}
} catch (Exception e) {
log.error("error", e);
}
return auth;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
public List<GrantedAuthority> getGrantedAuthorities(List<Role> roleList) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (Role role : roleList) {
authorities.add(new SimpleGrantedAuthority(role.getRoleName());
}
return authorities;
}
}
NOTE: Please consider these codes to understand the logic of authentication. don't consider as perfect code(Not for production env.). You can ping me anytime i'll suggest you more about that.
I am making a basic Tic tac toe Project with Spring , Spring Security and Hibernate. Application can save game for each logged user in database and allow to load it whenever we want. That is not a problem , but it appears when it comes to multi-threading. When i run single application in single browser window everything is working good. But when i open another window , two players are playing the same game.
I know it may be caused by bean singletons created by Spring but I am sure that it is not. To check currently logged user, i made an method to get him from SecurityContextHolder
private User getUserFromSpringContext() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String name = authentication.getName();
System.out.println("Currently logged users = " + name);
return userService.findUserByUsername(name);
}
When multiple users are logged in , that metod prints name of only one of them. I have no idea why. Here are some important code lines my Security configuration and userDetails classes:
Security Configuration:
#Autowired
UserDetailsService userDetailsService;
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService);
builder.authenticationProvider(authenticationProvider());
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(userDetailsService);
return auth;
}
Custom Users Details Service
#Autowired
private UserService userService;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findUserByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("Username not found");
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), true, true, true, true, getAuthoriries(user));
}
public void setUserService(UserService userService) {
this.userService = userService;
}
private List<GrantedAuthority> getAuthoriries(User user) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority(user.getRole().getRole()));
return authorities;
}
Does anybody knows cause of this problem?
During testing that I came up with another problem. When i click Logout , all users are logged out. I am posting here rest of my Spring Security Configuration.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/start", "/", "/login", "/registry","/success","/new").permitAll()
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.antMatchers("/**").access("hasAnyRole('ROLE_USER','ROLE_ADMIN')")
.and().formLogin().loginPage("/login").defaultSuccessUrl("/user")
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.and().csrf()
.and().exceptionHandling().accessDeniedPage("/access_denied");
}
What can be a problem?
SecurityContextHolder gives you access to the security context associated with the current thread, thus only the current user - the one whose request triggered the call to getAuthentication(), so it's behaving exactly the way it should.
If you, on the other hand, want all active sessions (i.e. all logged in users) you should inject the SessionRegistry and call sessionRegistry.getAllPrincipals() on it.
The details are already given here.