Create a custom annotation to validate userToken from header - java

I am trying to create an independent packageable jar with a custom annotation, which on inclusion in a controller mapping function (and taking userToken as input in header), returns a boolean whether the user is authenticated or now.
// Expected way of inclusion
public #ResponseBody boolean isAuthenticated(#Authenticator(#RequestHeader("userToken")) Boolean isUserAuthenticated) {
return isUserAuthenticated;
}
I know that this won't be the right syntax, since using this code gives the error that RequestMapping cannot be converted to String (and annotations only accept primitive values).
I am also open to other approaches, but it should have the flexibility to return authentication boolean only when needed and not through global interception.
Important: Please note #Authenticator comes from an independent package, imported in the current package through Maven. Would HTTPServletRequest pass in ConstraintValidator.

Use the spring security BasicAuthenticationFilter :
public class MyBasicAuthenticationFilter extends BasicAuthenticationFilter {
private AuthenticationManager authenticationManager;
public MyBasicAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
this.authenticationManager=authenticationManager;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// do you checks here
super.doFilterInternal(request, response, chain);
}
}
Then add this to your security config with something like:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
http.addFilterBefore(new MyBasicAuthenticationFilter(authenticationManager());
}
#Bean
public AuthenticationManager authenticationManager() {
return new MyAuthenticationManager();
}

Related

Spring Boot fails to boot up after add #EnableWebSecurity

I'm trying to add security headers to my Spring Boot application.
It already had a Java class with multiple filters extending from WebSecurityConfigurerAdapter. But whenever I try to add the annotation #EnableWebSecurity to this class or even with a new custom one I always receive NullPointerException for the bean springSecurityFilterChain.
Changing the order to add some filters seems to solve this problem but whenever I try to enter the app I can't because it seems the HTTP Authorization header field is null (which I recover inside one of my custom filters).
Do any have a clue of what is happening?
EDIT: After some days of cheking this I noted that the Authorization header was not the problem as the code is built to let that call enter without it and before any change it was already sent without header.
Still with the same call and the changes I'm receiving a 403 FORBIDDEN (before any change this call was receiving 302 FOUND).
This happens before even reaching the controller and I can only get debugging until the filter.
As there were no other changes in the code except the #EnableWebSecurity and the way to add one filter I suspect the problem is around here but i can't find what is causing it exactly.
EDIT: I'm adding the code in case anyone need to see it.
This is the class that has the multiple filters:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity //ADDED THIS ONE
public class MultipleEntryPointsSecurityConfig {
#Configuration
#Order(1)
public class OauthSecurityAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2RestTemplate restTemplate;
#Bean
public CustomFilterOneFilter customFilterOneFilter() {
final CustomFilterOneFilter filter = new CustomFilterOneFilter ("/testLogin");
filter.setRestTemplate(restTemplate);
return filter;
}
#Bean
public FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(
OAuth2ClientContextFilter filter) {
FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<OAuth2ClientContextFilter>();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.antMatcher("/login")
.cors()
.and()
.csrf().disable()
//CHANGED THIS
// .addFilterAfter(openIdConnectFilter(), OAuth2ClientContextFilter.class)
//FOR THESE TWO
.addFilterAfter(new OAuth2ClientContextFilter(), AbstractPreAuthenticatedProcessingFilter.class)
.addFilterAfter(openIdConnectFilter(), OAuth2ClientContextFilter.class)
.httpBasic()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/testLogin"))
.and()
.logout()
.logoutSuccessUrl("/logout")
.permitAll()
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated();
// #formatter:on
}
}
#Configuration
#Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public JwtSecurityFilter authenticationJwtTokenFilter() {
return new JwtSecurityFilter();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.cors()
.and()
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/**").authenticated();
http
.addFilterAfter(new UsernamePasswordAuthenticationFilter(), AbstractPreAuthenticatedProcessingFilter.class)
.addFilterAfter(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
//CHANGED THE BELOW ONE FOR THE TWO ABOVE
//http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
#Configuration
#Order(3)
public static class PublicConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/**").permitAll()
.antMatchers("/api/v1/login/**").permitAll();
}
}
}
And this is the custom filter where I try to recover the Authorization header:
#Component
public class JwtSecurityFilter extends OncePerRequestFilter{
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
//FAILS HERE!
if(authHeader == null || !authHeader.startsWith("Bearer ")) {
SecurityContextHolder.getContext().setAuthentication(null);
chain.doFilter(request, response);
return;
}
...
}
}

UsernamePasswordAuthenticationFilter skips success handler

I am having a hard time configuring my spring security. The problem is, my authentication filter always skips my success and failure handlers whenever I authenticate via a custom UsernamePasswordAuthenticationFilter. I don't seem to know why this happens.
First off, I pass the authentication parameter as JSON, and filter out the username and password, then I pass those two parameters into a new UsernamePasswordAuthenticationToken(username, password) then I get the authentication manager and authenticate the returned token. At the point of success full authentication I expect that the success handler should take over but no it doesn't get called at all.
This is my security configuration.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.and()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers("/signup")
.permitAll()
.antMatchers("/", "/security/login", "/request", "/request.html")
.authenticated()
.and()
.formLogin()
.loginProcessingUrl("/security/login")
.successHandler(authenticationSuccessHandler())
.failureHandler(authenticationFailureHandler())
.and()
.logout()
.logoutUrl("/logout")
.permitAll()
.and()
.addFilterAfter
(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
//.and()
.userDetailsService(userDetailsServiceBean());
}
The relevant beans are
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceBean());
}
#Bean
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return new JdbcUserDetails();
}
#Bean
public RestAuthenticationSuccessHandler authenticationSuccessHandler(){
return new RestAuthenticationSuccessHandler();
}
#Bean
public RestAuthenticationFailureHandler authenticationFailureHandler(){
return new RestAuthenticationFailureHandler();
}
#Bean
JsonAuthenticationFilter authenticationFilter() throws Exception {
logger.debug("Authenication filter processing loggin request ");
JsonAuthenticationFilter filter = new JsonAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
The filter is
public class JsonAuthenticationFilter extends UsernamePasswordAuthenticationFilter{
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
UsernamePasswordAuthenticationToken authRequest = this.getUserNamePasswordAuthenticationToken(request);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
and finally my success handler
class RestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication)
throws ServletException, IOException {
logger.debug("Successful login");
System.out.println("\n\n\n\n\n\n\n\nresponse here\n\n\n\n\n\n\n");
response.getWriter().write("{This is a login success response}");
response.getWriter().flush();
response.getWriter().close();
}
I have been battling for too long
Spring Security will back off on a given bean configuration when you supply that bean.
So, because you supplied your filter (JsonAuthenticationFilter), Spring Security expects that you'll know best how to compose it.
So, then, you'd instead do:
#Bean
JsonAuthenticationFilter authenticationFilter() {
JsonAuthenticationFilter filter = new JsonAuthenticationFilter();
// .. other configs
filter.setAuthenticationSuccessHandler(new RestAuthenticationSuccessHandler());
filter.setAuthenticationFailureHandler(new RestAuthenticationFailureHandler());
}
It looks like there is a lot going on, so if that doesn't solve your issue, feel free to put together a sample, say on GitHub, and I'd be happy to look it over.

Spring Boot: 2 different login pages for 2 URLs

I have a Spring Boot application. I have 2 login pages for 2 different sets of users in the system.
Any URL starting with /expert, the user should have the role of either EXPERT or ADMIN. The user uses form login to login into the system, and the login page path is /login.
For some URLs, such as css, js, etc, no authentication is required.
For all other URLs, the user needs no special role, an authentication is enough. The login page for the user should be /loginTwo.
I looked at this stackoverflow question and this documentation to implement this. However, when I access a URL that contains /expert, it takes me to the login page /loginTwo, instead of /login.
Here is my code below:
#Autowired
#Qualifier("userService")
UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new Md5PasswordEncoder();
return encoder;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Configuration
#Order(1)
public static class ExpertWebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login/**").permitAll();
http
.antMatcher("/expert/**")
.authorizeRequests()
.anyRequest().access("hasRole('ROLE_ADMIN') or hasRole('ROLE_EXPERT')")
.and()
.formLogin()
.loginPage("/login").permitAll();
}
}
#Configuration
public static class StudentWebSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("Here in stu");
http
.authorizeRequests()
.antMatchers("/css/**,/js/**").permitAll()
.antMatchers("/error/**").permitAll()
.antMatchers("/student/**").permitAll()
.antMatchers("/filter/**").permitAll()
.antMatchers("/loginTwo").permitAll()
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_EXPERT')")
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.csrf();
http
.formLogin()
.loginPage("/loginTwo").permitAll()
.and()
.logout().permitAll();
http
.sessionManagement()
.maximumSessions(20)
.expiredUrl("/loginTwo")
.maxSessionsPreventsLogin(false);
http
.headers().addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN));
}
}
Request you to help me with this.
You need to add different authentication filters for each different endpoint.
For HttpSecurity you can define something like this, giving the constructor your UserDetailsService and passing the authentication manager as well:
.addFilterBefore(new FirstLoginFilter("/api/login/first", userDetailsService, authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new SecondLoginFilter("/api/login/second", userDetailsService, authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new AdminLoginFilter("/api/login/admin", userDetailsService, authenticationManager()), UsernamePasswordAuthenticationFilter.class)
The implementation for the filters would look something like this.
Let's start with abstract authentication filter which is parent for all the above ones:
public abstract class LoginFilter extends AbstractAuthenticationProcessingFilter {
protected final SimpleUserDetailsService userService;
public LoginFilter(String pattern, SimpleUserDetailsService userService, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(pattern));
this.userService = userService;
this.setAuthenticationManager(authManager);
this.setAuthenticationSuccessHandler(new FormAuthenticationSuccessHandler());
this.setAuthenticationFailureHandler(new FormAuthenticationFailureHandler());
}
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws ServletException {
User authenticatedUser = this.userService.loadUserByUsername(authentication.getName());
UserAuthentication userAuthentication = new UserAuthentication(authenticatedUser);
SecurityContextHolder.getContext().setAuthentication(userAuthentication);
}
}
And for example one of the implementations for the actual filter:
public class FirstLoginFilter extends LoginFilter {
public FirstLoginFilter(String pattern, SimpleUserDetailsService userDetailsService, AuthenticationManager authManager) {
super(pattern, userDetailsService, authManager);
}
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
User user = (User)(new ObjectMapper()).readValue(request.getInputStream(), User.class);
UsernamePasswordAuthenticationToken loginToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
Authentication authentication = this.getAuthenticationManager().authenticate(loginToken);
if(!Role.isRolePresent(authentication.getAuthorities(), Role.YOUR_ROLE)) {
throw new BadCredentialsException("Bad credentials");
} else {
return authentication;
}
}
}
My example uses stateless authentication mechanism, so you need to modify the filter accordingly. As I see from your initial example, you are using sessions instead, so in fact it should be much easier for you, since it is already built in to Spring Security
Hope it helps.

spring security AuthenticationFailureHandler with java config

i created a custom authentication failure handler like this:
public class TrackerAuthFailureHandler extends SimpleUrlAuthenticationFailureHandler{
#Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
super.onAuthenticationFailure(request, response, exception);
if(exception.getClass().isAssignableFrom(DisabledException.class)){
setDefaultFailureUrl("/accountRecovery");
}
}
}
and i created a bean like this:
#Bean
public AuthenticationFailureHandler trackerAuthFailureHandler(){
SimpleUrlAuthenticationFailureHandler handler=new SimpleUrlAuthenticationFailureHandler();
return handler;
}
and spring config like this:
#Autowired
private TrackerAuthFailureHandler trackerAuthFailureHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.failureHandler(trackerAuthFailureHandler);
}
But bean not found exception occurred . any ideas?
When you use annotation injection way then you must use #Component in top of class TrackerAuthFailureHandler. like this:.
#Component
public class TrackerAuthFailureHandler extends SimpleUrlAuthenticationFailureHandler{

Spring Security anonymous 401 instead of 403

I have a problem with default behaviour in spring security with authorize requests provided with Java Config.
http
....
.authorizeRequests()
.antMatchers("/api/test/secured/*").authenticated()
When I do a call to for example /api/test/secured/user without login (with anonymous user), it returns 403 Forbidden. Is there an easy way to change status to 401 Unauthorized when anonymous user wants to get secured by authenticated() or #PreAuthorize resource?
As of Spring Boot 2 class Http401AuthenticationEntryPoint has been removed (see Spring Boot Issue 10725).
Instead of Http401AuthenticationEntryPoint use HttpStatusEntryPoint with HttpStatus.UNAUTHORIZED:
http.exceptionHandling()
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
With spring security 4.x there is already a class for that
org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint
Spring boot also includes one
org.springframework.boot.autoconfigure.security.Http401AuthenticationEntryPoint
and both benefits that they require the developer to use spec compliant as 401 responses requires that header WWW-Authenticate must be set, example 401 response could be:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example",
error="invalid_token",
error_description="The access token expired"
So in your security configuration you define and autowire a bean of class
So for instance with spring boot app:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Bean
public Http401AuthenticationEntryPoint securityException401EntryPoint(){
return new Http401AuthenticationEntryPoint("Bearer realm=\"webrealm\"");
}
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login").anonymous()
.antMatchers("/").anonymous()
.antMatchers("/api/**").authenticated()
.and()
.csrf()
.disable()
.headers()
.frameOptions().disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.logout()
.permitAll()
.exceptionHandling().authenticationEntryPoint(securityException401EntryPoint());
}
the relevant line is:
.exceptionHandling().authenticationEntryPoint(securityException401EntryPoint());
I've got solution here:
http
.authenticationEntryPoint(authenticationEntryPoint)
AuthenticationEntryPoint source code:
#Component
public class Http401UnauthorizedEntryPoint implements AuthenticationEntryPoint {
private final Logger log = LoggerFactory.getLogger(Http401UnauthorizedEntryPoint.class);
/**
* Always returns a 401 error code to the client.
*/
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg2) throws IOException,
ServletException {
log.debug("Pre-authenticated entry point called. Rejecting access");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied");
}
}
A simple approach in Spring Boot 2 using lambda expressions:
#Override
public void configure(HttpSecurity http) throws Exception {
http.
...
.exceptionHandling()
.authenticationEntryPoint((request, response, e) -> {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json");
response.getWriter().write("{ \"error\": \"You are not authenticated.\" }");
})
...
}
You need to extend AuthenticationEntryPoint to do customization based upon the exceptions.
#ControllerAdvice
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
// 401
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed");
}
#ExceptionHandler (value = {AccessDeniedException.class})
public void commence(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
// 401
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization Failed : " + accessDeniedException.getMessage());
}
}
Specify the above custom AuthenticationEntryPoint in your SecurityConfig like below:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity (prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new MyAuthenticationEntryPoint());
}
}
Who interested in mechanism of work. If you don't set http.exceptionHandling().authenticationEntryPoint() spring will use defaultAuthenticationEntryPoint() and method ExceptionHandlingConfigurer.createDefaultEntryPoint() will return new Http403ForbiddenEntryPoint()
So, just create Http401UnauthorizedEntryPoint(). Above answers how to do it, didn't duplicate it.
P.S. It's actual for Spring Security 5.2.5.RELEASE

Categories