Please give a hint in Spring Security, how can I check additional parameters during user login.
For example, to check not only "username" and "password", but also if he confirmed his registration with email link;
All data is stored in DB and i can get it easily with my implementation of UserDetailsService;
But how to make the security service to pay attention to the additional parameter "isValidated"?
I'm using Spring 3.2.0 now;
One way to achieve this is to create a custom AuthenticationProvider or to extend an existing one. In your case it might be sufficient to extend for instance the DaoAuthenticationProvider and put the logic for checking whether the account is confirmed in additionalAuthenticationChecks() method.
Here is an example:
public class CustomAuthenticationProvider extends DaoAuthenticationProvider {
#Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
// Perform the checks from the super class
super.additionalAuthenticationChecks(userDetails, authentication);
// Cast the UserDetails to the implementation you use
User user = (User) userDetails;
// Check the confirmed status
if (!user.isAccountConfirmed()) {
throw new AccountNotConfirmedException("Account is not confirmed yet.");
}
}
public static class AccountNotConfirmedException extends AuthenticationException {
public AccountNotConfirmedException(String message) {
super(message);
}
}
}
Your implementation of UserDetails should contain the information about account confirmation status. You can map this information in your implementation of UserDetailsService.
Option 2
Edit: Now that I look at it, the first solution is a bit overcomplicated. You can easily solve this problem without using custom AuthenticationProvider. Just make sure that isEnabled() of your UserDetails implementation returns false if the account is not confirmed. If the enabled property is false authentication will not be allowed (this is automatically taken care of by Spring Security).
The first solution might still be useful if you want explicitly handle the AccountNotConfirmedException in AuthenticationFailureHandler for instance.
Related
I am pretty new in Spring Security and I am working on a Spring Boot project that uses Basic Authentication in order to protect some APIs. I am starting from an existing tutorial code (a Udemy course) trying to adapt it to my own use cases.
In this project I have this SecurityConfiguration used to configure the basic authentication.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
private static String REALM = "REAME";
private static final String[] USER_MATCHER = { "/api/utenti/cerca/**"};
private static final String[] ADMIN_MATCHER = { "/api/utenti/inserisci/**", "/api/utenti/elimina/**" };
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable()
.authorizeRequests()
.antMatchers(USER_MATCHER).hasAnyRole("USER")
.antMatchers(ADMIN_MATCHER).hasAnyRole("ADMIN")
.anyRequest().authenticated()
.and()
.httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint()).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public AuthEntryPoint getBasicAuthEntryPoint()
{
return new AuthEntryPoint();
}
/* To allow Pre-flight [OPTIONS] request from browser */
#Override
public void configure(WebSecurity web)
{
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
#Bean
public BCryptPasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
};
#Bean
#Override
public UserDetailsService userDetailsService()
{
UserBuilder users = User.builder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users
.username("ReadUser")
.password(new BCryptPasswordEncoder().encode("BimBumBam_2018"))
.roles("USER").build());
manager.createUser(users
.username("Admin")
.password(new BCryptPasswordEncoder().encode("MagicaBula_2018"))
.roles("USER", "ADMIN").build());
return manager;
}
}
So from what I have understand:
Here it id defined the list of API that can be accessed by a nornmal user and the list of API that can be accessed by and admin user:
private static final String[] USER_MATCHER = { "/api/utenti/cerca/**"};
private static final String[] ADMIN_MATCHER = { "/api/utenti/inserisci/**", "/api/utenti/elimina/**" };
Into the previous configure() method basically it is stating that the API URL matching with the USER_MATCHER are accessible by logged user having role USER while API having URL matching ADMIN_MATCHER are accessible by logged user having role ADMIN. Is this interpretation correct?
Finnally the UserDetailsService bean simply define two users: one belonging to the USER "group" and the other one belonging to both the USER and ADMIN "group".
So, if I well understood, the first one will be aple only to access to the API having enpoint URL /api/utenti/cerca/** while the second one will be able to access also to the APIs having endpoint URLs /api/utenti/inserisci/** and /api/utenti/elimina/**
Is it my reasoning correct?
And now my doubt: into a controller class of this project I defined this method:
#RestController
#RequestMapping("api/users")
#Log
public class UserController {
#Autowired
UserService userService;
//#Autowired
//private BCryptPasswordEncoder passwordEncoder;
//#Autowired
//private ResourceBundleMessageSource errMessage;
#GetMapping(value = "/test", produces = "application/json")
public ResponseEntity<String> getTest() throws NotFoundException {
log.info(String.format("****** getTest() START *******"));
return new ResponseEntity<String>("TEST", HttpStatus.OK);
}
..............................................................................................................
..............................................................................................................
..............................................................................................................
}
As you can see this method handling a GET request toward the localhost:8019/api/users/test endpoint.
This endpoint URL is not in any of the previous two list related the protected endpoint (it is not into the USER_MATCHER list neither into the ADMIN_MATCHER list. So I expected that simply this endpoint was not protected and accessible to everyone. But performing the previous request using PostMan, I obtain this error message:
HTTP Status 401 : Full authentication is required to access this resource
So basically it seems to me that also if this endpoint not belong to any protected endpoint list it is in some way protected anyway (it seems to me that at least the user must be authenticated (infact trying both the previous user I can obtain the expected output, so it should mean that the endpoint is not protected by the user rule but it is protected againts not authenticated access).
Why? Maybe it depende by the previous configure() method settings, in particular this line?
.anyRequest().authenticated()
In case is it possible to disable in some way to implement something like this:
If a called endpoint belong to one of the previous two lists (USER_MATCHER and ADMIN_MATCHER) --> the user must be authenticated and need to have the correct role.
If a called endpoint not belong to one of the previous lists --> everybody can access, also not authenticated user.
This approach make sense or am I loosing something?
I take this occasion to ask you also another information: do you think that it is possible to configure Spring security of this specific project in order to protect some specific endpoints using the basic authentication and some other specific endpoints using the JWT authentication.
Sone further notes to explain why this last question. This project is a microservice that at the moment is used by another microservice (used to generate JWT token) in order to obtain user information. (the other microservice call an API of this project in order to receive user information so it can generate a JWT token that will be used in my application. The comunication between these 2 microservice must use basic authentication).
Since this project contains all the entity classes used to map the tables related to the users on my DB, my idea was to use this project also for generic user management, so it could include functionality like: add a brand new user, changes information of an existing user, obtain the list of all the users, search a specific user, and so on.
These new APIs will be protected by JWT token because each API can be called from a specific user type having different privileges on the system.
So I am asking if in a situation like this I can add without problem 2 different types of authentication (basic authentication for the API that retrieve a user so the other microservice can obtain this info) and JWT authentication for all the other APIs. It make sense or is it better to create a brand new project for a new user management microservice?
So, if I well understood, the first one will be aple only to access to the API having enpoint URL /api/utenti/cerca/** while the second one will be able to access also to the APIs having endpoint URLs /api/utenti/inserisci/** and /api/utenti/elimina/**
Yes.
Why? Maybe it depende by the previous configure() method settings, in particular this line?
Yes, when using .anyRequest().authenticated(), any requests that have not been matched will have to be authenticated.
If a called endpoint not belong to one of the previous lists --> everybody can access, also not authenticated user.
You can achieve this by doing anyRequest().permitAll(). But this is not so secure because you are allowing access to every other endpoints, instead you should stay with anyRequest().authenticated() and allow access to specific endpoints manually, like so:
http
.authorizeRequests()
.antMatchers(USER_MATCHER).hasAnyRole("USER")
.antMatchers(ADMIN_MATCHER).hasAnyRole("ADMIN")
.antMatchers("/api/users/test").permitAll()
.anyRequest().authenticated()
...
In a spring mvc application i need to capture an additional 'location' parameter on login screen and use it for authentication in addition to username. I came across few approaches suggested to achieve this but none of it is straight forward and involves extending and/or implementing number of spring classes and interfaces. I somehow achieved it by extending UsernamePasswordAuthenticationFilter and by retrieving and putting location parameter in session.
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
final Long locationId = Long.parseLong(request.getParameter("locations"));
request.getSession().setAttribute("LOCATION_ID", locationId);
return super.attemptAuthentication(request, response);
}
}
This approach seems like a hack and doesn't seem too elegant. On top of that, now since i am using a custom AuthenticationFilter i have to manually configure this filter by injecting number of dependencies.
#Bean
public CustomAuthenticationFilter customAuthenticationFilter () {
CustomAuthenticationFilter filter= new CustomAuthenticationFilter();
filter.setRequiresAuthenticationRequestMatcher(
new AntPathRequestMatcher("/login","POST"));
filter.setAuthenticationManager(authenticationManagerBean());
filter.setUsernameParameter("username");
filter.setPasswordParameter("password");
filter.setAuthenticationSuccessHandler(simpleUrlAuthenticationSuccessHandler());
filter.setAuthenticationFailureHandler(simpleUrlAuthenticationFailureHandler());
filter.setRememberMeServices(persistentTokenBasedRememberMeServices());
return filter;
}
Eventually, capturing location and using it in authentication works but then it creates new issues if i want to use remember-me feature. I explained the problem here Spring remember-me with extra login parameter.
Using extra login parameters must be a common requirement. Spring framework known for being pluggable and extensible i wish there was a more user friendly way to use additional parameters.
Can anyone please suggest me a better approach here to use extra parameter and get remember-me working as well. Thanks
The classes of spring security are really complicated.
Use AbstractPreAuthenticatedProcessingFilter as your superclass.
Than you can return an Object instead of a String in
getPreAuthenticatedPrincipal
getPreAuthenticatedCredentials
there you read whatever you need from the HttpRequest and return an object that contains all the data you need (as you need it as principal or credential wherever location belongs.). Maybe you create a simple class for the return type.
Than you create a subclass of org.springframework.security.authentication.AuthenticationProvider
.In the method public Authentication authenticate(Authentication authentication)
you can access the objects you returned in AbstractPreAuthenticatedProcessingFilter using authentication.getCredentials() and authentication.getPrincipal()
with this you create one or more new SimpleGrantedAuthority(role), one for each role the user has.Collect them in a List grantedAuthorities;
And finally create a
Authentication auth=new new UsernamePasswordAuthenticationToken(authentication.getPrincipal(),authentication.getCredentials(),grantedAuthorities)
which you return.
I've been trying to implement OAuth2 password expiration filter and I'm unsure about what the proper way would be to do so. The idea is as follows:
User tries to login.
User gets response with a header containing token if the password is expired.
User get's redirected to password change page using that token (i.e. /password-change/{token}).
He submits his old and new passwords, it gets changed.
Some rest controller retrieves user id by that token and does the rest password changing logic.
User should be redirected back to the initial login page where he logins with his new password (if he would be logged in instantly after the password change, he could navigate through secured pages even if the password would not be changed in background due to some exception, etc.).
So... I set a custom flag in user details for password expiration because I can't use credentialsNonExpired as it gets validated in DaoAuthenticationProvider and thrown as an exception which gets processed as InvalidGrantException which doesn't give me much control. I've figured out that in order to access user details right after it's authentication my filter should be in the inner Spring Security filter chain placed after OAuth2AuthenticationProcessingFilter:
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
...
http.addFilterAfter(new PasswordExpirationFilter(), BasicAuthenticationFilter.class
}
}
Why does my filter get placed after OAuth2AuthenticationProcessingFilter while there's no BasicAuthenticationFilter in the chain? I've digged through Spring Security and OAuth2 documentation and sources and couldn't find the right answer.
If that user's password is expired my filter generates some random string and it saves it to retrieve user details later during the password change request (at least it should be):
public class PasswordExpirationFilter extends OncePerRequestFilter implements Filter, InitializingBean {
private static final String TOKEN_HEADER = ...;
private ExpiredPasswordRepository repo; // gets set in a constructor and is basically holding a concurrent map of tokens
...
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
UserDetails details = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (details.isPasswordExpired) {
String uuid = UUID.randomUUID().toString();
repo.push(uuid, details.getId());
SecurityContextHolder.clearContext();
SecurityContextHolder.getContext().setAuthentication(null);
request.getSession(false).invalidate(); // don't create a new session
response.addHeader(TOKEN_HEADER, uuid);
response.sendError(HttpStatus.SC_PRECONDITION_FAILED, "Credentials have expired");
} else {
filterChain.doFilter(request, response);
}
}
}
Do I have to revoke the OAuth token as well? It gets reused in later requests and I keep getting the last userDetails object and therefore I keep getting the same response from my filter.
Is it even the right place to do all this validation? How should one validate the password for the concrete user and not the OAuth client?
Ok, I think I resolved this issue by revoking the access token via injected TokenStore in my filter (I used BearerTokenExtractor to get the token value) which seems pretty logical in this situtation. I still had no time to figure out, why my filter gets placed after OAuth2AuthenticationProcessingFilter, though.
My requirement is as below:
In our application the user's credentials are validated against the database(not using spring security since it is a legacy application) for the first time. If the user is a valid user, he will be logged into the application. Once the user logs into the application he can make few rest calls. Now, I want to once again validate the user's credentials by using spring security before making any rest call. Here, the challenge is we should not redesign the database schemas. We need to use a stored procedure which validates the user. This particular stored procedure returns an error message if authentication fails, otherwise nothing is returned. There are no roles defined in this case. Just simple authentication using a stored procedure. Now, I want to accomplish this whole thing by spring security. May be writing a java class/ custom spring framework's class and in which the stored procedure is called and using that class in spring security configuration files. Can anybody suggest ideas on how to start up with please?
I have implemented AuthenticationProvider. The following is the *security.xml.
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/rest/*" access="permitAll"></intercept-url>
</http>
<authentication-manager >
<authentication-provider ref="csAuthenticationProvider" />
</authentication-manager>
But, the security framework is looking for roles. In my case there are no roles defined. As I said earlier, the user is authenticated for the first time without using spring framework. If the user wants to make any rest call, the spring security needs to re authenticate the user. It doesn't mean that the user needs to re enter credentials. The user's credentials are available in the rest call/request since he is already authenticated. The only thing needs to be done is I need to use the credentials by using request and re validate using the stored procedure. of course, using AuthenticationProvider may be a good idea, but the parameter "Authentication authentication" of the authenticate(Authentication authentication) method is not useful for me since I need to call my own stored procedure call again. for time being, I did not used the Authentication object, but instead used the stored procedure calling code in the authenticate() method. But, strangely, authenticate() method is not getting called. I am surprised and in confusion. Does any body has any ideas on where I am doing wrong?
Sounds like you need to implement an Authentication Provider. Here's a pretty simple example that I think you could adapt to call your stored procedure.
http://danielkaes.wordpress.com/2013/02/20/custom-authentication-provider-in-spring/
You can implement your own UserDetailsService and configure spring to use it.
<security:authentication-manager>
<security:authentication-provider user-service-ref="userDetailsServiceImpl"/>
</security:authentication-manager>
You need to create a custom UserDetailsService implementation, that will check against the DB.
Here is an example UserDetailsService implementation that does just that:
#Service("userService")
public class UserDetailsServiceImpl implements UserDetailsService, InitializingBean {
#Autowired
private AccountService accountService;
public void afterPropertiesSet() throws Exception {
}
#Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
username = username.toLowerCase();
try {
Account account = accountService.loadUserAccountByEmail(username);
if (account == null) {
throw new UsernameNotFoundException("Could not find email: " + username + "in the DB.");
}
List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
for (Role r : account.getRoles()) {
auths.add(new SimpleGrantedAuthority(r.getRole()));
}
ApplicationUser user = null;
try {
user = new ApplicationUser(new Long(account.getId()), username, account.getPassword(), true, true, true, true, auths);
} catch (Exception ex) {
ex.printStackTrace();
}
return user;
} catch (Exception e) {
e.printStackTrace();
throw new UsernameNotFoundException(username + "not found", e);
}
}
}
Which I config in code like so:
#Override
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsServiceImpl)
.passwordEncoder(bCryptPasswordEncoder());
}
(you can also see a blog post I wrote about switching from xml to #annotation config for spring security referncing that project here: http://automateddeveloper.blogspot.co.uk/2014/02/spring-4-xml-to-annotation-configuration.html)
I followed the instructions to create a custom security realm for my glassfish. It all works fine, users are authenticated correctly. The problem however is the following:
The user credentials are encrypted in a string
The realm decrypts this string and performs the authentication against a database (works)
Instead of using the decrypted values as principal in the securityContext the encrypted
String is passed.
I already tried to override the commit() method to replace the _userPrincipal or attach my own implementation using getSubject().getPrincipals().add(new PrincipalImpl("user")). Neither was working as expected. Basically the question is a simple as this: How can I set my own principal in a custom security realm in glassfish in a way which makes it possible to use it together with an injected securityContext?
My environment:
Glassfish 3.1.2.2 (Build 5) Full Profile
The application running behind the authentication is a JAX-RS 1.1 based application
The SecurityContext is obtained using injection
I already tried to override the commit() method to replace the
_userPrincipal or attach my own implementation using getSubject().getPrincipals().add(new PrincipalImpl("user")). Neither
was working as expected.
What kind of error(s) do you get?
Regardless, I think your issue lies on the third step of this process. SecurityContext only defines BASIC_AUTH, FORM_AUTH, CLIENT_CERT_AUTH, DIGEST_AUTH as AuthenticationScheme so perhaps SecurityContext cannot see your implementation of your security scheme or type. But you can try these steps and I hope they would work for you.
A- Implement a Java Authentication and Authorization Service (JAAS) LoginModule or extend com.sun.appserv.security.AppservPasswordLoginModule
public class MyLoginModule extends AppservPasswordLoginModule {
#Override
protected void authenticateUser() throws LoginException {
if (!authenticate(_username, _password)) {
//Login fails
throw new LoginException("LoginFailed");
}
String[] myGroups = getGroupNames(_username);
commitUserAuthentication(myGroups);
}
private boolean authenticate(String username, String password) {
/*
Check the credentials against the authentication source, return true if authenticated, return false otherwise
*/
return true;
}
private String[] getGroupNames(String username) {
// Return the list of groups this user belongs to.
}
B- Implementing your realm class.
public class MyRealm extends AppservRealm {
#Override
public void init(Properties props)
throws BadRealmException, NoSuchRealmException {
//here you initialize the realm
}
#Override
public String getAuthType() {
return "Custom Realm";
}
}
C- Installing and configuring the realm and LoginModule into the server.
for this you need to look at JSR 196 and write you own SAM by implmenting javax.security.auth.message.module.ServerAuthModule. Take a look at thelink below.
https://blogs.oracle.com/enterprisetechtips/entry/adding_authentication_mechanisms_to_the