Spring Security - Custom login error messages - spring

I have 2 different types of login errors:
Incorrect user/pw
IP blocked for too many attempts
Right now Spring is forwarding the user to www.site.com/login?error in both cases. How can I customize login behavior so that it forwards the user to error1 or error2 depending on the case? This forward behavior doesn't seem to be explicitly declared anywhere and I couldn't find any documentation on this.

You have to implement your custom AuthenticationFailureHandler, see :
Spring security authenticate exceptions handling
If you just want to redirect the user to error1 or error2, your implementation can be just a simple redirect method :
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
super.onAuthenticationFailure(request, response, exception);
if(exception.getClass().isAssignableFrom(UsernameNotFoundException.class)) {
response.sendRedirect("error1")
} else if (exception.getClass().isAssignableFrom(LockedException.class)) {
response.sendRedirect("error2")
}
}
}

Related

How to track all login logs about the jwt in spring security

Recently, I started to make a platform and chose spring security as the back end and angular as the front end. And I want to track all login logs, such as failed login, successful login, username does not exist, incorrect password, etc.
I try to use spring aop to track all login logs, but I only get the logs when the login is successful.
These are the jwt filter and the spring aop code.
public class JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
public JwtUsernameAndPasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
/* get username and password in user request by jwt */
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
UsernameAndPasswordAuthenticationRequest authenticationRequest = new ObjectMapper()
.readValue(request.getInputStream(), UsernameAndPasswordAuthenticationRequest.class);
Authentication authentication = new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword()
);
Authentication authenticate = authenticationManager.authenticate(authentication);
return authenticate;
} catch (IOException e) {
throw new RuntimeException();
}
}
/* create jwt token when user pass the attemptAuthentication */
#Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain, Authentication authResult) throws IOException, ServletException {
String key = "securesecuresecuresecuresecuresecuresecuresecuresecuresecuresecure";
String token = Jwts.builder()
.setSubject(authResult.getName())
.claim("authorities", authResult.getAuthorities())
.setIssuedAt(new Date())
.setExpiration(java.sql.Date.valueOf(LocalDate.now().plusWeeks(2)))
.signWith(Keys.hmacShaKeyFor(key.getBytes()))
.compact();
response.addHeader("Authorization", "Bearer " + token);
}
}
#Aspect
#Component
public class LoginLogAOP {
private static final Logger logger = LoggerFactory.getLogger(LoginLogAOP.class);
#AfterReturning(pointcut="execution(* org.springframework.security.authentication.AuthenticationManager.authenticate(..))"
,returning="result")
public void afteReturn(JoinPoint joinPoint,Object result) throws Throwable {
logger.info("proceed: {}", joinPoint.getArgs()[0]);
logger.info("result: {}", ((Authentication) result));
logger.info("user: " + ((Authentication) result).getName());
}
}
Has anyone tracked login logs through Spring Security jwt? Thank you very much for your help!
Your advice type #AfterReturning does exactly what the name implies: It kicks in after the method returned normally, i.e. without exception. There is another advice type #AfterThrowing, if you want to intercept a method which exits by throwing an exception. Then there is the general #After advice type which kicks in for both. It is like the supertype for the first two subtypes.
And then of course if you want to do more than just react to method results and log something, but need to actually modify method parameters or method results, maybe handle exceptions (which you cannot do in an "after" advice), you can use the more versatile, but also more complex #Around advice.
Actually, all of what I said is nicely documented in the Spring manual.
Update: I forgot to mention why #AfterReturning does not capture the cases you are missing in your log: Because according to the documentation for AuthenticationManager.authenticate(..), in case of disabled or locked accounts or wrong credentials the method must throw certain exceptions and not exit normally.

Is possible ask for an acces token oauth2 just with refresh token in spring security? without basic authentication?

I would like to know if in spring oauth2 is possible get a new pair tokens (access token and refresh token) just using another refresh token, without the basic authentication (without clientId and clientSecret, is there any way?
For exemple:
WITH BASIC AUTH
curl -u clientId:clientSecret -X POST 'http://myapplication.oauth2/accounts/oauth/token?grant_type=refresh_token&client_id=<CLIENT_ID>&refresh_token=' -v
WITHOUT BASIC AUTH
curl -u -X POST 'http://myapplication.oauth2/accounts/oauth/token?grant_type=refresh_token&client_id=<CLIENT_ID>&refresh_token=' -v
I note that sprint BasicAuthenticationFilter in spring uses validation bellow, maybe override this filter and make the authentication just with refresh token.
String header = request.getHeader("Authorization");
if (header == null || !header.toLowerCase().startsWith("basic ")) {
chain.doFilter(request, response);
return;
}
The short answer is no. The class used to manage the Spring Oauth 2 endpoints is the following one:
#FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint
Both requests, I mean, get access token and refresh one use the same endpoint with different parameters. And the method to manage those ones is:
#RequestMapping(
value = {"/oauth/token"},
method = {RequestMethod.POST}
)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, #RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");
} else {
String clientId = this.getClientId(principal);
...
As you can see, a Principal object is required (in this case provided by the Basic Authentication).
Even, if you configure the security of your project to permit that url without checking authentication, you will achieve to "enter" in above method but you will receive an InsufficientAuthenticationException because no Authentication instance has been provided.
Why custom authentication will not work
1. Create a custom AuthenticationProvider will not work because the method postAccessToken is invoked before. So you will receive an InsufficientAuthenticationException.
2. Create a OncePerRequestFilter and configure it to execute before process the current request:
#Override
protected void configure(HttpSecurity http) throws Exception {
http...
.anyRequest().authenticated()
.and()
.addFilterBefore(myCustomFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(POST, "/accounts/oauth/**");
}
with a code "similar to":
#Component
public class CustomAuthenticationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
...
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("existingUser",
"passwordOfExistingUser",
Collections.emptyList()));
...
filterChain.doFilter(request, response);
}
The problem with this approach is the principal in TokenEndpoint comes from the HttpServletRequest not from Spring context, as you can see debugging BasicAuthenticationFilter class.
In your custom filter you can try, using reflection, set a value in userPrincipal property but, as you can verify, request has several "internal request properties" and that could be a "too tricky option".
In summary, Oauth standard needs user/pass to access to the resources, if you want to workaround in almost of provided endpoints maybe that project is not what you are looking for.
Workaround to include your own object in Spring Principal
I do not recommend that but if you still want to go ahead with this approach, there is a way to include your own value inside the principal parameter received by TokenEndpoint class.
It is important to take into account BasicAuthorizationFilter will be still executed, however you will be able to override the Spring principal object by your own one.
For this, we can reuse the previous CustomAuthenticationFilter but now your have to include the filters you need, I mean, allowed urls, parameters, etc You are going to "open the doors", so be careful about what you allow and not.
The difference in this case is, instead of add the configuration in our class that extends WebSecurityConfigurerAdapter we are going to do it in:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private CustomAuthenticationFilter customAuthenticationFilter;
...
#Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.checkTokenAccess("isAuthenticated()");
security.addTokenEndpointAuthenticationFilter(customAuthenticationFilter);
}
...

Spring security shares context between multiple threads, how can I avoid this?

I have been working on a spring-based service, using JWT for authentication.
The service handling the user requests calls an authorization service in a filter which sets up the security context and looks pretty much like this :
#Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#Autowired
private AuthorizationServiceClient authorizationServiceClient;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
User user = authorizationServiceClient.requestUserFromToken(request.getHeader("X-Auth-Token"));
SecurityContextHolder.getContext().setAuthentication(new UserAuthentication(user));
filterChain.doFilter(request, response);
SecurityContextHolder.getContext().setAuthentication(null);
} catch (HttpClientErrorException e) {
response.sendError(e.getStatusCode().value());
}
}
}
The AuthorizationServiceClient calls a remote service which handles the validation of the user's role and credentials.
I have been facing a very strange behavior :
When a page on my UI was making multiple request simultaneously, I end up getting a 500, caused by a NullPointerException.
The root cause is the Principal (containing the identity of the user) being null, when it shouldn't have.
After a painful investigation, I ended figuring that the SecurityContextHolder, even though it was using a ThreadLocal, was using sessions, and then would be shared between the threads.
The SecurityContextHolder.getContext().setAuthentication(null); was erasing the value used in concurrent threads when some requests were made in the same session, and was leading to the NPE.
So, if like me, you'd like to prevent the use of the sessions, you need to set up the security using :
http.
[...]
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).
[...]

Add Maintenance Mode to Spring (Security) app

I'm looking for a way to implement a Maintenance Mode in my Spring app.
While the app is in Maintenance Mode only users role = MAINTENANCE should be allowed to log in. Everyone else gets redirected to login page.
Right now I just built a Filter:
#Component
public class MaintenanceFilter extends GenericFilterBean {
#Autowired SettingStore settings;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if(settingStore.get(MaintenanceMode.KEY).isEnabled()) {
HttpServletResponse res = (HttpServletResponse) response;
res.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
} else {
chain.doFilter(request, response);
}
}
}
And added it using:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// omitted other stuff
.addFilterAfter(maintenanceFilter, SwitchUserFilter.class);
}
Because as far as I figured out SwitchUserFilter should be the last filter in Spring Security's filter chain.
Now every request gets canceled with a 503 response. Though there's no way to access the login page.
If I add a redirect to the Filter, this will result in an infinite loop, because access to login page is also denied.
Additionally I can't figure out a nice way to get the current users roles. Or should I just go with SecurityContextHolder ?
I'm looking for a way to redirect every user to the login page (maybe with a query param ?maintenance=true) and every user with role = MAINTENANCE can use the application.
So the Filter / Interceptor should behave like:
if(maintenance.isEnabled()) {
if(currentUser.hasRole(MAINTENANCE)) {
// this filter does nothing
} else {
redirectTo(loginPage?maintenance=true);
}
}
I now found two similar solutions which work, but the place where I inject the code doesn't look that nice.
For both I add a custom RequestMatcher, which get's #Autowired and checks if Maintenance Mode is enabled or not.
Solution 1:
#Component
public class MaintenanceRequestMatcher implements RequestMatcher {
#Autowired SettingStore settingStore;
#Override
public boolean matches(HttpServletRequest request) {
return settingStore.get(MaintenanceMode.KEY).isEnabled()
}
}
And in my Security Config:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired MaintenanceRequestMatcher maintenanceRequestMatcher;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.requestMatchers(maintenanceRequestMatcher).hasAuthority("MY_ROLE")
.anyRequest().authenticated()
// ...
}
Solution 2:
Very similar, but uses HttpServletRequest.isUserInRole(...):
#Component
public class MaintenanceRequestMatcher implements RequestMatcher {
#Autowired SettingStore settingStore;
#Override
public boolean matches(HttpServletRequest request) {
return settingStore.get(MaintenanceMode.KEY).isEnabled() && !request.isUserInRole("MY_ROLE");
}
}
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired MaintenanceRequestMatcher maintenanceRequestMatcher;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.requestMatchers(maintenanceRequestMatcher).denyAll()
.anyRequest().authenticated()
// ...
}
This will perform a denyAll() if Maintenance Mode is enabled and the current user does not have MY_ROLE.
The only disadvantage is, that I cannot set a custom response. I'd prefer to return a 503 Service Unavailable. Maybe someone can figure out how to do this.
It's kinda of a chicken or egg dilemma, you want to show unauthorized users a "we're in maintenance mode ..." message, while allow authorized users to login, but you don't know if they are authorized until they log in. Ideally it would be nice to have this in some sort of filter, but I found in practice it was easier for me to solve a similar issue by putting the logic after login, like in the UserDetailsService.
Here's how I solved it on a project. When I'm in maintenance mode, I set a flag for the view to show the "we're in maintenance mode .." message, in a global header or on the login page. So users, regardless of who they are know it's maintenance mode. Login should work as normal.
After user is authenticated, and in my custom UserDetailsService, where their user details are loaded with their roles, I do the following:
// if we're in maintenance mode and does not have correct role
if(maintenance.isEnabled() && !loadedUser.hasRole(MAINTENANCE)) {
throw new UnauthorizedException(..)
}
// else continue as normal
It's not pretty, but it was simple to understand (which I think is good for security configuration stuff) and it works.
Update:
With you solution I'd have to destroy everyone's session, else a user
which was logged in before maintenance mode was enabled, is still able
to work with the system
On our project we don't allow any users to be logged in while in maintenance mode. An admin, kicks off a task which enables "maintenance..." msg, with a count down, then at the end, we expire everyone's session using SessionRegistry.
I was a similar situation and found this answer is helpful. I followed the second approach and also managed to return custom response.
Here is what I have done to return the custom response.
1- Define a controller method that returns the needed custom response.
#RestController
public class CustomAccessDeniedController {
#GetMapping("/access-denied")
public String getAccessDeniedResponse() {
return "my-access-denied-page";
}
}
2- In your security context, you should permit this URL to be accessible.
http.authorizeRequests().antMatchers("/access-denied").permitAll()
3- Create a custom access denied exception handler
#Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
#Autowired
private SettingStore settingStore;
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
if(settingStore.get(MaintenanceMode.KEY).isEnabled()) {
response.sendRedirect(request.getContextPath() + "/access-denied");
}
}
}
4- Register the custom access denier exception handler in the security config
#Autowired
private CustomAccessDeniedHandler accessDeniedHandler;
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);

Set a redirect into a custom Authentication Failure Handler with Spring

Which is the properly way to set a redirect into a custom AuthenticationFailureHandler in Spring?
Is it possible to call a controller?
The code is as follows:
#Component
public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
super.onAuthenticationFailure(request, response, exception);
if (exception.getClass().isAssignableFrom(
CustomUsernameNotFoundException.class)) {
// TODO Set the redirect
}
}
}
Try soemthing like this
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
saveException(request, exception);
//do your things
getRedirectStrategy().sendRedirect(request, response, "/page/login?error=Retry");
}
You are calling super.onAuthenticationFailure which will peform a redirect to the configured URL. The response is thus already committed and you cannot decide to redirect somewhere else.
You can configure SimpleUrlAuthenticationFailureHandler to redirect to one URL and only call the super method if you aren't going to do a redirect yourself.
Alternatively, implement AuthenticationFailureHandler directly and implement all the logic you want in the failure method - once things get beyond a certain level of complexity I prefer to avoid inheritance altogether:
if (oneCondition) {
// redirect to IdP
} else {
// redirect to registration page
}
You can call a controller., a code snippet from you would help, but am getting this from the example that is discussed here.,
Spring Security Tutorial
#RequestMapping(value = "/login/failure")
public String loginFailure() {
String message = "Login Failure!";
return "redirect:/login?message="+message;
}
make sure you understand how the redirect works by looking at the mapping for login in the xml
Spring Mapping.xml
You can redirect to a specific URL.
response.sendRedirect("/redirect");

Resources