I'm building a simple console application which uses nice stack of frameworks(Hibernate, Spring), but found myself struggling with auth/reg issues. Usually for web projects I would use Spring Security framework, restrict some URL add configure login page which is completely processed by the framework automatically. But my console application contains 2 issues:
Storing encoded passwords in a database
Using User Service to configure Spring to work with my database
1st problem can be solved using some password encoder and specifying it in a spring-security configuration file like this
<beans:bean class="myconsoleapp.util.PasswordUtil" id="passwordEncoder" factory-method="getPasswordEncoder"/>
and later specify encoder in authentication manager
<authentication-manager>
<authentication-provider user-service-ref="userService">
<password-encoder ref="passwordEncoder"/>
</authentication-provider>
</authentication-manager>
2nd problem seems to be more complex. In my service class I've implemented UserDetailsService spring security interface and have overridden loadUserByUsername method like this
#Service("userService")
public class UserServiceImpl implements UserService, UserDetailsService {
public AuthorizedUser loadUserByUsername(String email) throws UsernameNotFoundException {
User user = repository.getByEmail(email.toLowerCase());
return new AuthorizedUser(user);
}
}
And my AuthorizedUser class has a constructor like this
public class AuthorizedUser extends org.springframework.security.core.userdetails.User {
private User user;
public AuthorizedUser(User user) {
super(user.getEmail(), user.getPassword(), user.isEnabled(), true, true, true, user.getRoles());
this.user= user;
}
}
Finally we're close to the question.. How can I use all that stuff providing email and password input from the console?? I see it like that:
System.out.println("Enter email:");
String userEmail = inputStream.readLine();
System.out.println("Enter your password");
String userPasspord = inputStream.readLine();
userService.//<-What method should I use here?
Given that you can access the authentication manager bean, you can check the credentials something like this:
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userEmail, userPassword);
try {
authenticationManager.authenticate(token);
System.out.println("Login successful!");
} catch (BadCredentialsException e) {
System.out.println("Bad credentials!");
}
Related
I want to use two types of users: common user and admin. Now I already have an infrastructure where admins and users are two completely different types: users have many things related only to them(controllers, tables, services etc.), same for admins. Therefore, they are different entities and different tables in the DB, and I don't want to combine them, because they are different. But now only users can log in using Spring Security OAuth2, but admins not principals and they can't log in. Note that I use my own authorization and resource servers.
So, I want to allow Spring Security to authenticate both users and admins. I also want to use two different login endpoints and two different entities and tables for users and admins.
How can this be done or what should I do instead?
UPD:
I think I should create 2 OAuth clients with 2 different grant_types in oauth_client_details and 2 AbstractTokenGranters for users and for admins.
I already have a custom AbstractTokenGranter for users which authenticate users like this:
//getOAuth2Authentication()
User user = userService.getUserByPhone(username);
if(user == null)
throw new BadCredentialsException("Bad credentials");
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(Long.toString(user.getId()), password)
);
//I use Long.toString(user.getId()) because some users use FB instead of the phone,
//so I have one more `AbstractTokenGranter` for social networks,
//I don't mention about it in this post, so don't be confused
As I understand, AuthenticationManager calls UserDetailsService, which looks like this now:
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails user = userRepository.findById(Long.parseLong(username)).orElseThrow(
() -> new UsernameNotFoundException("User not found with id : " + id)
);
return user;
}
But if I create one more AbstractTokenGranter for admins, then the current UserDetailsService will not know whose id it received - admin id or user id.
As a solution, I think I need to create one more UserDetailsService for admins. But how can I use multiple UserDetailsService? Also, maybe I should use a completely different scheme?
<security:http pattern="/oauth/token" use-expressions="true" create-session="stateless"
authentication-manager-ref="clientAuthenticationManager"
xmlns="http://www.springframework.org/schema/security">
<security:intercept-url pattern="/**" method="GET" access="ROLE_DENY"/>
<security:intercept-url pattern="/**" method="PUT" access="ROLE_DENY"/>
<security:intercept-url pattern="/**" method="DELETE" access="ROLE_DENY"/>
<security:intercept-url pattern="/oauth/token" access="permitAll"/>
<security:anonymous enabled="false"/>
<security:http-basic entry-point-ref="clientAuthenticationEntryPoint"/>
<!-- include this only if you need to authenticate clients via request
parameters -->
<security:custom-filter ref="contentTypeFilter" before="BASIC_AUTH_FILTER"/>
<security:custom-filter ref="clientCredentialsTokenEndpointFilter"
after="BASIC_AUTH_FILTER"/>
<security:access-denied-handler ref="oauthAccessDeniedHandler"/>
<security:csrf disabled="true"/>
</security:http>
You can define custom clientDetailService and override loadUserByUserName method.
It is up to you whether you can query different tables and authorizations also you can change the structure. That is what i can say without more description
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
ClientDetails clientDetails;
try {
clientDetails = clientDetailsService.loadClientByClientId(username);
} catch (NoSuchClientException e) {
throw new UsernameNotFoundException(e.getMessage(), e);
}
String clientSecret = clientDetails.getClientSecret();
if (clientSecret== null || clientSecret.trim().length()==0) {
clientSecret = emptyPassword;
}
return new User(username, clientSecret, clientDetails.getAuthorities());
}
This part can be modified to change structure :> authentication-manager-ref="clientAuthenticationManager"
If you are not using xml based, you can check annotation base link :
https://www.baeldung.com/spring-security-authentication-with-a-database
1.Create new OAuth2 client in the oauth_client_details table with custom_grant in the authorized_grant_types.
2.Create:
public class CustomTokenGranter extends AbstractTokenGranter {
//...
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> params = tokenRequest.getRequestParameters();
String username = params.getOrDefault("username", null);
String password = params.getOrDefault("password", null);
if(username == null || password == null)
throw new BadCredentialsException("Bad credentials");
CustomAuthenticationToken token = new CustomAuthenticationToken(username, password);
Authentication authentication = authenticationManager.authenticate(token);
}
}
3.Add this granter in AuthorizationServerConfigurerAdapter:
private TokenGranter tokenGranter(final AuthorizationServerEndpointsConfigurer endpoints) {
List<TokenGranter> granters = new ArrayList<TokenGranter>(Arrays.asList(endpoints.getTokenGranter()));
granters.add(new CustomGrantTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), "custom_grant"));
return new CompositeTokenGranter(granters);
}
Now CustomGrantTokenGranter will receive all authorization request with custom_grant grant type.
4.Create CustomAuthenticationToken extends UsernamePasswordAuthenticationToken
5.Create:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private PasswordEncoder adminPasswordEncoder;
#Autowired
private UserDetailsService adminDetailsService;
#Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
String username = auth.getName();
String password = auth.getCredentials().toString();
UserDetails adminDetails = adminDetailsService.loadUserByUsername(username);
//adminDetailsService.loadUserByUsername(username) returns Admin inside UserDetails
if (adminPasswordEncoder.matches(password, adminDetails.getPassword()))
return new UsernamePasswordAuthenticationToken(adminDetails, password, adminDetails.getAuthorities());
else
throw new BadCredentialsException("Bad credentials");
}
#Override
public boolean supports(Class<?> auth) {
return auth.equals(CustomAuthenticationToken.class);
}
}
Here you can use UserDetailsService different from other providers
6.Add CustomAuthenticationProvider in WebSecurityConfigurerAdapter
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(customAuthenticationProvider);
}
//...
}
Summary: with this scheme you can use as many user types as you want. If Admin implements UserDetails, then you can easily use it on the server as a Principal.
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();
}
I use LDAP authentication in my app.
I use this code:
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
String domain = customProperties.getAdDomain();
String url = customProperties.getAdUrl();
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(domain,url);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(userDetailsContextMapper());
auth.authenticationProvider(provider);
auth.userDetailsService(new MyUserDetailsService());
}
Authentication takes place with an empty password. I know that I need to insert a check for an empty password, because Not all LDAP servers return an error in this case. How and where is it better to insert a check for a blank password?
Instead of using the ActiveDirectoryLdapAuthenticationProvider, you can make use of Spring's LdapTemplate to have a custom implementation of how you authenticate users against the LdapServer. You can refer to the recommendation here and here to configure the LDAP template.
Then, you can create a CustomAuthenticationProvider class to handle the authentication.
CustomAuthenticationProvider.class
public class CustomAuthenticationProvider implement AuthenticationProvider{
#Autowired
private LdapTemplate ldapTemplate;
#Override
public Authentication authenticate(Authentication auth) throws AuthenticationException{
String username = auth.getName;
String password = auth.getCredentials().toString();
.. Your code to check whether password is blank ..
AndFilter andFilter = new AndFilter();
andFilter.and(new EqualFilter("<LDAP USER ATTRIBUTE>",username))
.and(new EqualFilter("<LDAP GROUP ATTRIBUTE>","<USER GROUP>"));
boolean isValidUser = ldapTemplate.authenticate("",andFilter.encode(),password);
... Your code to complete the authentication ...
{
I prefer this approach as it gives me finer control on how to authenticate the user. Here is the link to the sample I implemented previously.
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'm creating authentication service in Spring.
I'm using UserDetailsService to get form variables, but i found that loadUserByUsername has only one variable - userName.
How to get password ?
public class userAuthentication implements UserDetailsService{
private #Autowired
ASPWebServicesUtils aspWebServicesUtils;
#Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
//how to get password ?
User user = new User("test", "test", true, true, true, true, getAuthorities(true));
return user;
}
private List<GrantedAuthority> getAuthorities(boolean isAdmin){
List<GrantedAuthority> authorityList = new ArrayList<GrantedAuthority>(2);
authorityList.add(new SimpleGrantedAuthority("USER_ROLE"));
if(isAdmin){
authorityList.add(new SimpleGrantedAuthority("ADMIN_ROLE"));
}
return authorityList;
}
//...
}
Thanks
If you look at the User object, the second parameter in the constructor is the password.
The UserDetailsService is used to load the user from a back-end structure like database. The loadUserByUsername method is called when a user tries to login with a username and password, then it is the responsibility of the service to load the user definition and return it to the security framework. The required details includes data like username, password, accountNonExpired, credentialsNonExpired, accountNonLocked and authorities.
Once the spring security receives the user object, it will validate the user against the password entered by the user and other data like user account status (accountNonExpired, credentialsNonExpired etc)
Some of the standard (out-of-the-box) mechanisms to retrieve the user information and provide authentication information are:
inMemoryAuthentication
jdbcAuthentication
ldapAuthentication
userDetailsService
If the above does not suit your purpose and you need to have a custom solution, you can create and configure a new authentication provider like so:
Security Configuration:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new CustomAuthenticationProvider());
}
....
}
Authentication Provider:
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String name = authentication.getName();
// You can get the password here
String password = authentication.getCredentials().toString();
// Your custom authentication logic here
if (name.equals("admin") && password.equals("pwd")) {
Authentication auth = new UsernamePasswordAuthenticationToken(name,
password);
return auth;
}
return null;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
I believe a UserDetailsService is supposed to be used to acquire a UserDetails object from some back end storage, database, flat file, etc. Once you have that UserDetails, spring security (or you) have to compare it to the username (or other principals) and password (the credentials) provided by the user in order to authenticate that user.
I don't think you are using it the way it is intended.
Get password in UserDetailsService implementation by request.getParameter("password"):
public class MyUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String password = request.getParameter("password"); // get from request parameter
......
}
}
RequestContextHolder is base on ThreadLocal.
If your project is base on Spring Framework (not Spring Boot), add RequestContextListener to web.xml
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
XML Implementation:
<authentication-manager alias="loginAuthenticationManager">
<authentication-provider ref="loginAuthenticationProvider" />
</authentication-manager>
<!-- Bean implementing AuthenticationProvider of Spring Security -->
<beans:bean id="loginAuthenticationProvider" class="com.config.LoginAuthenticationProvider">
</beans:bean>
AuthenticationProvider:
public class LoginAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String name = authentication.getName();
// You can get the password here
String password = authentication.getCredentials().toString();
// Your custom authentication logic here
if (name.equals("admin") && password.equals("pwd")) {
List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
return new UsernamePasswordAuthenticationToken(name, password, grantedAuths);
}
return null;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
the loadUserByUsername(String name) is a method defined on an interface (userServicedetails I think), which your service implements. You have to write the implementation.
Just as you have to write the implementation for getPassword() or similar ... spring does not provide that. I imagine the password is stored in your user object, but you wrote that ... did you create a getPassword() method ?