I would like to ask for your help regarding my problem in Spring Security.
I have a requirement wherein I have to validate a login credential based on the option selected by the user. Option 1 would be validating the logged in user via third party service. Option 2 would be the normal validation using database authentication level. How can I implement this?
General Strategy
Provide a custom implementation of org.springframework.security.authentication.AuthenticationProvider that delegates authentication to the appropriate backend (third-party service, another AuthenticationProvider, etc.).
Pass the user-selected option to the custom AuthenticationProvider, enabling it to choose the correct authentication backend.
Configure the custom AuthenticationProvider as the default authentication provider.
Step 1: Implement AuthenticationProvider
AuthenticationProvider is an interface with a single method. Therefore, a custom implementation may look like:
class DelegatingAuthenticationProvider implements AuthenticationProvider {
#Autowired
private ThirdPartyAuthenticationService service;
#Autowired
#Qualifier("anotherAuthenticationProvider")
private AuthenticationProvider provider;
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
// Get the user selection.
String selection = (String) authentication.getDetails();
// Take action depending on the selection.
Authentication result;
if("ThirdParty".equals(selection)) {
// Authenticate using "service" and generate a new
// Authentication "result" appropriately.
}
else {
// Authenticate using "provider" and generate a new
// Authentication "result" appropriately.
}
return result;
}
}
Step 2: Pass the user selection to the AuthenticationProvider
The AuthenticationProvider implementation above picks up the user selection from the details property of the Authentication object. Presumably, the user selection would have to be picked up from the HttpServletRequest and added to the Authentication object before the AuthenticationProvider is invoked. This means, another component that has access to both the Authentication and HttpServletRequest objects needs to be invoked before the AuthenticationProvider is called.
The Authentication object is created by an implementation of AbstractAuthenticationProcessingFilter. This class has a method named attemptAuthentication that accepts an HttpServletRequest object and returns an Authentication object. So it seems this would be a good candidate for implementing what is needed. For username-password based authentication, the implementation class is UsernamePasswordAuthenticationFilter. This class returns a new instance of UsernamePasswordAuthenticationToken, which is an implementation of Authentication. So, a class extending UsernamePasswordAuthenticationFilter should be sufficient.
class ExtendedUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
...
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, password);
authentication.setDetails(obtainUserSelection(request));
...
return authentication;
}
}
obtainUserSelection is a private method that gets the user selection out of the request.
Step 3: Configuration
Configure the AuthenticationProvider and filter implementations in the Spring Security configuration. The exact steps will vary depending on whether XML or Java configuration is used.
Related
I'm trying to create a custom Spring Security Authentication Filter in order to implement a custom authentication scheme. I've spent a couple hours reading up on Spring Security, but all of the guides I've found explain how to configure basic setups; I'm trying to write a custom setup, and I'm having trouble finding documentation on how to do so.
For the sake of example, lets say that my custom authentication scheme is as follows:
If the client provides a "foo_username" header and a "foo_password" header in the http request (both unencrypted for the sake of example), then my custom filter needs to construct a UsernamePasswordAuthenticationToken. Of course if the password is wrong, that's an authentication error. If either header is missing that's an authentication error. If both headers are missing, I want to delegate down the filter chain without changing anything.
That seems simple enough in theory, but I don't know how to implement that in Spring. Am I intended to check the password against the DB myself? or is that the reponsibility of the UserDetailsPasswordService? Am I intended to modify the SecurityContextHolder.getContext().authentication field myself? What responsibilities do I delegate to the AuthenticationManager? Which exceptions do I throw when authentication fails in various ways? Do I implement Filter, OncePerRequestFilter, or AbstractAuthenticationFilter? Is there any documentation on how to do all this???
Admittedly, this is a duplciate of How to create your own security filter using Spring security?, but I'm not him, and he got no answers.
Thanks for the help!
Edit: This is the not best way to do things. It doesn't follow best practices. I haven't had time to udpate this answer with an example that does follow best practices.
As others have pointed out, it's better to use Basic auth or OAuth2, both of which are built into Spring. But if you really want to implement a custom filter, you can do something like this. (Please correct me if I'm doing this wrong.) But don't do this exactly. This is not a very secure example; it's a simple example.
class CustomAuthenticationFilter(val authManager: AuthenticationManager) : OncePerRequestFilter() {
override fun doFilterInternal(request: HttpServletRequest,
response: HttpServletResponse,
chain: FilterChain) {
val username = request.getHeader("foo_username")
val password = request.getHeader("foo_password")
if(username==null && password==null){
// not our responsibility. delegate down the chain. maybe a different filter will understand this request.
chain.doFilter(request, response)
return
}else if (username==null || password==null) {
// user is clearly trying to authenticate against the CustomAuthenticationFilter, but has done something wrong.
response.status = 401
return
}
// construct one of Spring's auth tokens
val authentication = UsernamePasswordAuthenticationToken(username, password, ArrayList())
// delegate checking the validity of that token to our authManager
val userPassAuth = this.authManager.authenticate(authRequest)
// store completed authentication in security context
SecurityContextHolder.getContext().authentication = userPassAuth
// continue down the chain.
chain.doFilter(request, response)
}
}
Once you've created your auth filter, don't forget to add it to your HttpSecurity config, like this:
override fun configure(http: HttpSecurity?) {
http!!.addFilterBefore(CustomAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter::class.java)
}
I think what you want to do is implement AuthenticationProvider. It allows your code to explicitly manage the authentication portion. It has a fairly simple method signature to implement as well.
public class YourAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
...
return new UsernamePasswordAuthenticationToken(principal, password, principal.getAuthorities())
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
You can register it by adding it to the AuthenticationManagerBuilder in a config that extends WebSecurityConfigurerAdapter
#Configuration
class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) {
AuthenticationProvider provider= new YourAuthenticationProvider();
auth.authenticationProvider(provider);
}
}
Is it possible to use OAuth2 for certain endpoints in my rest application and use basic authentication too for some other endpoints.
It should all work on spring security version 2.0.1.RELEASE. I hope someone can help me further.
Yes, it's possible to use a basic authentication as well as an OAuth2 authentication intertwined, but I doubt you'll be able to set it up easily as HttpSecurity's authenticated() method doesn't allow you to pick which of your authentication method (oauth2Login/formLogin) will work.
However, there's a way to easily bypass that:
You could add a custom authority, let's call it ROLE_BASICAUTH, when an user connects using basic auth, and ROLE_OAUTH2 when an user connects using OAuth2. That way, you can use
.antMatchers("/endpoint-that-requires-basic-auth").hasRole("BASICAUTH")
.antMatchers("/endpoint-that-requires-oauth2").hasRole("OAUTH2")
.anyRequest().authenticated()
When they reach an endpoint that you want basic authentication (and not OAuth2), you check their current authorities, and if it's not BASICAUTH, then you invalidate their session you display a login form without OAuth2 (to force them to use the basic authentication).
The downside to doing that is that you'd need to implement both a custom UserDetailsService as well as a custom OAuth2UserService...
But that's actually not that hard:
#Service
public class UserService extends DefaultOAuth2UserService implements UserDetailsService {
// ...
#Override
public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
OAuth2User user = super.loadUser(oAuth2UserRequest);
Map<String, Object> attributes = user.getAttributes();
Set<GrantedAuthority> authoritySet = new HashSet<>(user.getAuthorities());
String userNameAttributeName = oAuth2UserRequest.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getUserNameAttributeName();
authoritySet.add(new SimpleGrantedAuthority("ROLE_OAUTH2"));
return new DefaultOAuth2User(authoritySet, attributes, userNameAttributeName);
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails user = getUserFromDatabase(username); // you'll need to provide that method (where are the username/password stored?)
if (user == null) { // UserDetailsService doesn't allow loadUserByUsername to return null, so throw exception
throw new UsernameNotFoundException("Couldn't find user with username '"+username+"'");
}
// add ROLE_BASICAUTH (you might need a custom UserDetails implementation here, because by defaut, UserDetails.getAuthorities() is immutable (I think, I might be a liar)
return user;
}
}
Note that this is a rough implementation, so you'll have to work it out a bit on your end as well.
You can also use this repository I made https://github.com/TwinProduction/spring-security-oauth2-client-example/tree/master/custom-userservice-sample as a guideline for the custom OAuth2UserService
Good luck.
I have a web service that I have built on top of Spring. I am currently authenticating using Spring Security as follows:
#Configuration
#EnableGlobalMethodSecurity(securedEnabled=true)
#EnableWebSecurity
public class ServerSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private Properties properties;
private static final String ALL_URI = "/v1/**";
private static final String HEALTH_URI = "/v1/healthCheck";
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(getFilter(), BasicAuthenticationFilter.class);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests()
.antMatchers(HEALTH_URI).permitAll()
.anyRequest().authenticated();
http.csrf().disable();
}
private AuthenticationFilter getFilter() {
return new AuthenticationFilter( properties.getKey());
}
}
My AuthenticationFilter class extends AbstractAuthenticationProcessingFilter and performs the actual authentication. If I want to add Authorization to my security, would I just make those checks in the attemptAuthentication method apart of the AuthenticationFilter? Or is there a better way to do it? The way I understand it is that Authorization and Authentication should be done independently. You first authenticate, and then you verify the permissions. So, I would assume there would be a better approach to do authorization within Spring Security rather than just adding it to the attemptAuthentication method.
You need a AuthenticationProvider to do authenticate, implement the AuthenticationProvider and override the authentication and supports methods, and then inject to the AuthenticationManager.
attemptAuthentication method in filter is usually to get authentication(e.g. UsernamePasswordFilter gets username and password from request, and then builds a UsernamePasswordAuthenticationToken to AuthenticationManager ),
supports method tests the AuthenticationProvider whether can be used to do authenticate.(e.g DaoAuthenticationProvider supports UsernamePasswordAuthenticationToken)
authenticate method is used to do authenticate(e.g DaoAuthenticationProvider gets the real password by username and then compare to the user input), this method should return an Authentication that is already authenticated(e.g UsernamePasswordAuthenticationToken), and this authentication should contains the user authorities(this can be used to hasRole('xxx')), or use detail and so on.
After attemptAuthentication successful, the Authentication will set into SecurityContextHolder. and then you can use the hasRole('xx'), or something else.
How can I pass request specific information to an AuthenticationProvider from a Filter without creating a custom Authentication ?
The AuthenticationProvider signature is as below:
interface AuthenticationProvider {
Authentication authenticate(Authentication authentication);
}
I'm thinking something similar to the static SecurityContextHolder.getContext().getAuthentication() but with request specific information.
Background
The reason I want it is because I want to implement a switching authentication provider that delegates in runtime to another AuthenticationProvider
public class SwitchingAuthenticationProvider implements AuthenticationProvider {
private AuthenticationProvider[] authProviders = // ...
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return authProvider[GetCurrentContext.getIndex()].authenticate(authentication);
}
}
You can provide your own implementation of AuthenticationEntryPoint which have access to HttpServletRequest. In AuthenticationEntryPoint implementation put all attribute that you need in your switching AuthenticationProvider, in RequestContextHolder as given below
RequestContextHolder.currentRequestAttributes().setAttribute(attributeName, attibuteValue, scope);
At the end you can retrieve these attribute in your authentication provider as given below
RequestContextHolder.currentRequestAttributes().getAttribute(attributeName,scope);
I am using spring & jersey2 to serve some rest-requests like:
#GET
#Path("/someservice")
#Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
public String getSomeStuff(...) {
login(...);
// ...
}
During a rest-request, I get an authorized user of the rest-request.
Now I need this user while updating or creating entities like:
#MappedSuperclass
public abstract class PersistentObject {
#PrePersist
#PreUpdate
public void onSaveOrUpdate() {
setCreationUser(...); // How to get the user of this session?
}
// ...
}
How can I get the current user of the rest-request there?
You can try to perform your login operation (for appropriate resource methods) in a ContainerRequestFilter and set SecurityContext:
#Provider
public class SecurityFilter implements ContainerRequestFilter {
#Override
public void filter(final ContainerRequestContext context) throws IOException {
final Principal user = login(...);
context.setSecurityContext(new SecurityContext() {
public Principal getUserPrincipal() {
return user;
}
// Other methods omitted.
});
}
}
Make sure you have jersey-spring3 module on your class-path and the Jersey-Spring integration allows you to inject SecurityContext into a Spring service:
#Service
public MySpringService implements MyService {
#Context
private SecurityContext context;
public String doStuff() {
final Principal user = context.getUserPrincipal();
// ...
}
}
You can't do this if the service, you want to use user principal in, is neither managed by Jersey nor Spring.
Spring Security might be useful to you in two ways:
It can manage authentication, (you would not need to do that login(...) call yourself, it would be done automatically by Spring Security filter chain. But you can still do it manually if you want.
Once a request has been authenticated, as long as the request is alive you can access the authenticated user from anywhere just by doing:
Authentication auth = SecurityContextHolder.getSecurityContext().getAuthentication();
// auth is an object that holds the authenticated user's data
I think you need some sort of authentication by the fact that you make a login(...) and you want to audit the user afterwards. You might not nedd an authentication form, but you do need authentication. Spring Security is not only for interactive applications, you can set up an authentication filter that does authentication based on cookies, request parameters, client certificates or whatever, all of that without user interaction.
Furthermore, Spring Security is very extensible, if you have your authentication method already implemented, integrating with Spring Security is easy. And it is also flexible: you don't need to use the security filter chain if it is too heavyweight for your use case. You can do most things manually and use just a little bit of Spring Security if you want.
I really suggest you take a deeper look at Spring docs about:
Spring Security core components overview and Spring Security authentication overview
I think with just that you will be able to get something working.