In my application I'm trying to implement authentication using X509 certificates
I found default solutions provided by the Spring Security framework as follows:
X509AuthenticationFilter
X509PrincipalExtractor
X509AuthenticationProvider
X509AuthoritiesPopulator
Given how much I know about Spring Security, the purpose of these classes and interfaces is as follows:
X509AuthenticationFilter is responsible for intercepting the HTTP request, creating an authRequest token (of type X509AuthenticationToken) in doFilter() method and initializing the authentication process by delegating the token to the authManager. The authManager later delegates the authentication job to the X509AuthenticationProvider
The main goal of the X509AuthenticationProvider is to perform the authentication by extracting the UserDetails using X509AuthoritiesPopulator and setting it to the new X509AuthenticationToken that is later set in SecurityContext. After the new token has been set, the process is finished.
X509PrincipalExtractor is an interface, implementation of which should return a DN (Distinguished Name) from a certificate. However, it's not clear to me where it should be used. In my application I use it to get the DN and based on the DN I return a UserDetails instance inside X509AuthoritiesPopulator.
My questions are the following:
Where should I use the X509PrincipalExtractor? I put this interface inside my X509AuthoritiesPopulator to get the UserDetails instance based the returned string value of DN. Is that correct?
Why is the X509PrincipalExtractor used in the default X509AuthenticationFilter provided by spring security in the method getPreAuthenticatedPrincipal()?
What's the point of the method getPreAuthenticatedPrincipal() anyway? What it should look like in my class? I found it in the doAuthenticate() method in the AbstractPreAuthenticatedProcessingFilter, but from what I can gather, just an X509Certificate instance is enough to create an X509AuthenticationToken, there's no need for principle. Am I missing something?
My doAuthenticate() method is below:
private void doAuthenticate(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
// why would I need principle? I found this in the doAuthenticate() of AbstractPreAuthenticatedProcessingFilter
Object principal = getPreAuthenticatedPrincipal(request);
X509Certificate credentials = getPreAuthenticatedCredentials(request);
try
{
X509AuthenticationToken authRequest = new X509AuthenticationToken(credentials);
authRequest.setDetails(super.getAuthenticationDetailsSource().buildDetails(request));
Authentication authResult = authenticationManager.authenticate(authRequest);
super.successfulAuthentication(request, response, authResult);
}
catch (AuthenticationException e)
{
super.unsuccessfulAuthentication(request, response, e);
}
}
In AbstractPreAuthenticatedProcessingFilter there's a method called principalChanged(). How come a principle can change? The certificate is always static and simply can't be modified.
Related
I have a use case in which I have to perform authorization part only. JWT token is getting generated by another service. My service will only consume that token which will have data in it's custom claim. In my spring boot application, I just want to verify that token if it's valid or not before allowing users to access any API. In all answers I can see one Authentication object is created from current Security context. Is there any way in spring in which I don't need to write so much unnecessary code in Custom Filter and just write the logic to parse JWT token and if it is valid, then allow user to API or else send unauthorized response?
Auth0 Java JWT provides few methods to verify JWT token. I want to validate and parse using code like below
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).withIssuer(issuer).build();
DecodedJWT decoded = verifier.verify(token);
If you want to work with spring security you have to work with those things (filters, authentication object, context, etc.). You have to set an Authentication to be able to access protected resources. It's not complicated at all, you don't have to write "useless" code but just tell spring security what you want to do.
In your case, you just have to provide a custom filter that matches your criterias.
We can imagine something like :
#Component
public class TokenAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) req;
// Here, I get the token from authorization header, change if you get it from anywhere else
String token = httpRequest.getHeader("Authorization");
// here i'm just doing a dummy check. if my token equals "mysupersecrettoken" the authentication is validated
// change this test by yours (using Auth0 etc.)
if ("mysupersecrettoken".equals(token)) {
// dummy authentication object. You can set a real username / credentials / role based on the claims of your token if you want to.
UsernamePasswordAuthenticationToken user = new UsernamePasswordAuthenticationToken("username", "credentials",
Arrays.asList(new SimpleGrantedAuthority("ROLE")));
SecurityContextHolder.getContext().setAuthentication(user);
}
// call next filters, if authentication is not valid, no authentication will be set so user will not be authenticated
chain.doFilter(req, res);
}
}
It's not the best way to work with spring security but it's a kind of "minimal" way to achieve your usecase. You'll probably need to set some security context too but it's not a big thing.
Spring Security ships with out of the box support for JWT authentication, so you would not need to write any code aside from configuration. See OAuth 2.0 Resource Server JWT in the reference docs. You can enable it with the Spring Security DSL using:
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
Note that JWTs can be used outside of OAuth 2.0 with this support, though it may not be obvious at first that this is the case. You can check out the JWT Login sample to see how that is accomplished, but the main takeaway is that you need to provide an #Bean of type JwtDecoder that is created with a public key, such as:
#Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(this.key).build();
}
Also see this Tanzu Tuesday talk for a deep dive on this subject.
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);
}
}
I'm new to spring-boot and quite new to web services.
I would like to implement an authorization process in an (abstract) generic RestController which should get the content of the basic-auth. header from the extending controllers and then perform the authorization.
Something like that:
public class GenericController
{
// Constructor
protected GenericController(String basicAuth) throws MalformedURLException, ProtocolException, IOException
{
// check user can execute action
}
}
#RestController
#RequestMapping( "/users" )
public class UserController extends GenericController
{
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
// Constructor
protected UserController() throws MalformedURLException, ProtocolException, IOException
{
// somehow get the basic auth information from header and pass it to the parent's constructor
basicAuth = ???
super(basicAuth);
}
#GetMapping( "/user/{cn}" )
public String getUser( #PathVariable String cn )
{
logger.error("Start getUser(...): cn=" + cn);
}
How can I do that? (is that even possible?)
ADDED INFORMATION:
My web services are themselves consumers of other web services.
Once the calling user is authorized to "use" my web services I have to forward/set the Basic Authentication into the request for the web services I call.
So, how/where can I "intercept" and get the headers when my services are called?
I would recommend to take a look at Spring Security framework. There are many tutorials showing how to configure it to perform basic authentication, e.g.: here.
The main idea is decoupling authentication / authorization concerns from controllers and business logic code. To implement this filters are used which intercept every request and validate it against different conditions. When implementing stateless REST-service (each request independent from others) it's not so difficult. When handling sessions, logic will become much more complicated. Spring Security implements filter chains, consisting of multiple filters, each performing its own checks.
Spring Security is used very often in spring-based projects and is definitely worth to look at, although it may seem a bit difficult when meeting it for the first time.
You can use HttpServletRequest to get the header from the request.
protected UserController(HttpServletRequest httpServletRequest) throws MalformedURLException, ProtocolException, IOException
{
System.out.println( httpServletRequest.getHeaders("key name"));
// somehow get the basic auth information from header and pass it to the parent's constructor
basicAuth = ???
super(basicAuth);
}
Short Version
Is javax.servlet.ServletRequest's method setAttribute(<key>, <Object>) only used as a means of passing objects between methods in Java code?
Long version
Let's say I have a javax.servlet.Filter implementation to handle all logged in users' authentication using cookies:
in Spring Boot
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class AuthFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
Cookie[] cookies = null;
if (request instanceof HttpServletRequest) {
cookies = ((HttpServletRequest) request).getCookies();
}
Optional<User> user = mySessionAuthMethod(cookies);
if (user.isPresent()) {
request.setAttribute("user", user.get());
}
chain.doFilter(request, response);
}
}
Then later, I can avoid manual authentication in all of the Web API methods, and just check the user attribute. Example of a #RestController's method:
#RequestMapping(value = "/profile")
#CrossOrigin(origins = {MyProperties.ORIGIN}, allowCredentials = "true")
public ResponseEntity getProfile(HttpServletRequest request, HttpServletResponse response) {
String user = request.getAttribute("user");
if (user != null) {
return myGetProfileResponse(user);
}
return myNotLoggedInResponse();
}
My questions are:
Is this form of authentication secure? What I mean is, are the attributes in the ServletRequest only added and used in Java for communication between methods, or could they be added to the request already before reaching the server?
Is this way of authentication using Filters a good practice to avoid duplicate code?
Additional Explanation
The real reason of doing this is not only authentication. I have also Filters which need to process each and every request and pass objects to the Controllers. What I definitely want is that none of these objects and information can be forged even by a person who knows the implementation of the system.
I think I have found the answer from the documentation of getAttribute
Attributes can be set two ways. The servlet container may set attributes to make available custom information about a request. For example, for requests made using HTTPS, the attribute javax.servlet.request.X509Certificate can be used to retrieve information on the certificate of the client. Attributes can also be set programatically using ServletRequest#setAttribute. This allows information to be embedded into a request before a RequestDispatcher call.
So according to this (if there is no missing information), it should be completely safe to pass custom objects and know that they were always created by the server.
In my spring project, I would like to create an endpoint which returns the security context user details for a clients active session cookie.
One way would be a specific method implementation within a controller
#RequestMapping(value = "/session", method = GET)
public AuthenticatedUserDto getCurrentSession(HttpServletResponse response) {
if (SecurityContextHolder.getContext().getAuthentication().getPrincipal() != null && SecurityContextHolder.getContext().getAuthentication().getPrincipal() instanceof User) {
return AuthenticatedUserBuilder.build(SecurityContextHolder.getContext().getAuthentication());
}
throw new BadCredentialsException("unkown session");
}
There is few things which bother me on this approach:
I need a ugly if to determine if it's not an anonymous authentication
I am handling this issue far to deep within the context, as I have all information already as soon the session cookie gets resolved.
So my other approach is using a security chain filter for matching the specific url ("/session), and handle the task there.
public class SessionObjectResponder extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
...//do create my response
..
}
}
Adding the filter just before the AnonymousAuthenticationFilter, should guarantee that I would have the security context available matching the session cookie. So I configured my web security like this:
httpSecurity.antMatcher("/session").addFilterBefore(new SessionObjectResponder(),AnonymousAuthenticationFilter.class);
Oddly, the SecurityContextHolder contains a null authentication even when a valid session cookie is passed.
I also can see that within the SecurityContextPersistenceFilter no security context gets set.
When I remove the httpSecurity configuration for this filter and add the #Component to the filter class, the SecurityContextis loaded correctly again. (Without path matching of course)
Why is that?
You can ask for the Authentication or Principal like this:
#RequestMapping(value = "/session", method = GET)
public AuthenticatedUserDto getCurrentSession(Authentication auth) {
if (auth == null || !auth.isAuthenticated())
throw new BadCredentialsException("unkown session");
return AuthenticatedUserBuilder.build(auth);
}
When you use #Component or #Service or #Controller ... then it becomes spring managed been. Their life cycle is managed by spring container. Therefore context is available in container created instances.
But in your case of
httpSecurity.antMatcher("/session").addFilterBefore(new SessionObjectResponder(),AnonymousAuthenticationFilter.class);
You are creating the instance with new operator. It is not created by the container. So this instance is outside the container scope. Therefore the context is not available in that instance at runtime.