Need help with regard to Authentication using Spring Security. It is to authenticate REST API , there is no UI (formlogin etc..).
Configured with Authentication provider and Authentication entry point to handle exceptions.
Below are Security Config and Authentication Provider Classes. Please check. Issue is
1. When we invoke API with No Auth (with out credentials) - it provided valid exception with response body- "Full Authentication is required to access this resource".
2. When we invoke API with incorrect credentials (bad username/password) - it provided only http status code as 401 with no response body.
Security Config :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated();
// http.authorizeRequests().anyRequest().permitAll();
http.addFilterAfter(securityFilter, BasicAuthenticationFilter.class);
http.exceptionHandling()
.authenticationEntryPoint(new AuthenticationEntryPoint());
http.httpBasic();
http.csrf().disable();
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
}
Authentication Provider :
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Authentication auth = null;
try {
auth = authenticationProvider().authenticate(authentication);
} catch (BadCredentialsException e) {
LOGGER.info("User Authenticated Status Active Directory 1:" + e.getMessage());
throw new BadCredentialsException("Authentication Failed :" + e.getMessage());
} catch (Exception e) {
LOGGER.info("User Authenticated Status Active Directory:" + e.getMessage());
throw new BadCredentialsException("Authentication Failed :" + e.getMessage());
}
return auth;
}
```
http.authorizeRequests().anyRequest().fullyAuthenticated() - // working for No Auth but not for incorrect credentials
http.authorizeRequests().anyRequest().permitAll(); - // working for incorrect credentials but not if user provided no credentials - it is allowing all request with no basic auth details which is invalid
I tried with different ways by using authentication failure handler , security filter bean - but no luck.
Please advise on the above issue on how to handle.
Related
Whenever we try to input wrong credentials in Spring Boot login page, we got Bad Credentials Error with link /login?error I'm trying to limit a login for which I've created a custom login failure handler and whenever I try to provide wrong credentials I'm not able to get any kind of error by Spring Security at this /login?error page in place of this, I'm getting Status 404 Error.
AppConfig
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.failureHandler(loginFailureHandler)
.permitAll()
.and()
.logout();
}
LoginFailureHandler
#Component
public class LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Autowired
private UserService service;
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
String username = request.getParameter("username");
Employee emp = service.getByUsername(username);
if(emp!=null) {
if (emp.isAccountNonLocked()) {
if (emp.getFailedAttempt() < UserService.MAX_FAILED_ATTEMPTS - 1) {
service.increaseFailedAttempt(emp);
} else {
service.lock(emp);
exception = new LockedException("Your account has been locked due to three failed attempts"
+"Try again after 24 Hours....");
}
} else {
if(service.unlockWhenTimeExpired(emp)){
exception = new LockedException("Your Account is unLocked now...." +
"try to login again");
}
}
}
super.setDefaultFailureUrl("/login?error"); // I'm not getting this page while a fail login
super.onAuthenticationFailure(request, response, exception);
}
}
Since I'm unable to get this page /login?error I'm not able to display any message regarding failure login.
I assume you are using spring-boot-mvc. You can overwrite the default login page by creating a login.html in the src/main/resources/templates directory.
And in it you can display your error message by utilizing th:if="${param.error}" like so:
<div th:if="${param.error}">Wrong credentials!</div>
The below Code I used in webConfigSecurity class to bypass some requests from the client
#Override
public void configure(WebSecurity webSecurity) throws Exception
{
webSecurity.ignoring().antMatchers("/adminSettings/get/**")
.antMatchers("/cases/sayHello/**").antMatchers("/cases/**/downloadPdfFolderPBC/**");
}
In the controller api method requires the user details for further execution, while getting the user details the authentication object is null, so it throws an exception that "user is not authenticated"
public static User get() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
if (principal == null) {
throw new InsufficientAuthenticationException("User not authenticated");
}
return principal.getUser();
}
throw new AuthenticationCredentialsNotFoundException("User not authenticated");
}
I'm new to spring security, In this case, what I should do to get logged user details
Instead of ignoring(), which causes Spring Security to skip those requests, I believe you want to use permitAll() instead like so:
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests((requests) -> requests
.antMatcher("/adminSettings/get/**").permitAll()
.antMatcher("/cases/sayHello/**").permitAll()
// ... etc
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
}
In this way, Spring Security will still populate the SecurityContextHolder with the logged-in user, but none of the permitAll endpoints will require authentication.
I am developing Spring boot application with microservices architecture. I am using JWT authentication.
1-http://localhost:8762/auth {"username":"admin", "password":"12345"} (POST request)
2-http://localhost:8762/auth/loginPage (GET request for page)
When i try first request, authentication is working well and i get login info and jwt token.
But when i try second request for getting login page, spring is trying to authenticate and returns 401 error.
How can i ignore authentication for login page.
I have zull project as gateway and authentication project as auth.
if(header == null || !header.startsWith(jwtConfig.getPrefix())) {
chain.doFilter(request, response); // If not valid, go to the next filter.
return;
}
I think at this point, i have to override filter. But i don't know how i write filter.
Here is my code for authentication.
auth project -> WebSecurityConfigurerAdapter
#EnableWebSecurity
public class SecurityCredentialsConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtConfig jwtConfig;
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
// make sure we use stateless session; session won't be used to store user's state.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// handle an authorized attempts
.exceptionHandling().authenticationEntryPoint((req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
// Add a filter to validate user credentials and add token in the response header
// What's the authenticationManager()?
// An object provided by WebSecurityConfigurerAdapter, used to authenticate the user passing user's credentials
// The filter needs this auth manager to authenticate the user.
.addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(), jwtConfig()))
.authorizeRequests()
// allow all POST requests
.antMatchers("/auth/**").permitAll()
.antMatchers("/user/register").permitAll()
// any other requests must be authenticated
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/auth/loginPage");
}
// Spring has UserDetailsService interface, which can be overriden to provide our implementation for fetching user from database (or any other source).
// The UserDetailsService object is used by the auth manager to load the user from database.
// In addition, we need to define the password encoder also. So, auth manager can compare and verify passwords.
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
#Bean
public JwtConfig jwtConfig() {
return new JwtConfig();
}
}
auth -> UsernamePasswordAuthenticationFilter
public class JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authManager;
private final JwtConfig jwtConfig;
public JwtUsernameAndPasswordAuthenticationFilter(AuthenticationManager authManager, JwtConfig jwtConfig) {
this.authManager = authManager;
this.jwtConfig = jwtConfig;
// By default, UsernamePasswordAuthenticationFilter listens to "/login" path.
// In our case, we use "/auth". So, we need to override the defaults.
//this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(jwtConfig.getUri(), "POST"));
this.setRequiresAuthenticationRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/auth/**")
, new AntPathRequestMatcher("/user/register")
));
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
try {
// 1. Get credentials from request
UserDTO creds = new ObjectMapper().readValue(request.getInputStream(), UserDTO.class);
// 2. Create auth object (contains credentials) which will be used by auth manager
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
creds.getUsername(), creds.getPassword(), Collections.emptyList());
// 3. Authentication manager authenticate the user, and use UserDetialsServiceImpl::loadUserByUsername() method to load the user.
return authManager.authenticate(authToken);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// Upon successful authentication, generate a token.
// The 'auth' passed to successfulAuthentication() is the current authenticated user.
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication auth) throws IOException, ServletException {
Long now = System.currentTimeMillis();
String token = Jwts.builder()
.setSubject(auth.getName())
// Convert to list of strings.
// This is important because it affects the way we get them back in the Gateway.
.claim("authorities", auth.getAuthorities().stream()
.map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
.setIssuedAt(new Date(now))
.setExpiration(new Date(now + jwtConfig.getExpiration() * 1000)) // in milliseconds
.signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret().getBytes())
.compact();
// Add token to header
response.addHeader(jwtConfig.getHeader(), jwtConfig.getPrefix() + token);
}
}
Controller
#GetMapping("/auth/loginPage")
public String loginPage() {
return "login";
}
I think your problem is here in JwtUsernameAndPasswordAuthenticationFilter
You also have this point commented out. You are triggering this filter on POST and GET. You only want to trigger it for POST.
Current method
this.setRequiresAuthenticationRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/auth/**")
, new AntPathRequestMatcher("/user/register")
));
Updated
this.setRequiresAuthenticationRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/auth/**", "POST")
, new AntPathRequestMatcher("/user/register", "POST")
));
By doing this:
this.setRequiresAuthenticationRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/auth/**")
, new AntPathRequestMatcher("/user/register")
));
the filter will authenticate any request to /auth/** (thus /auth/loginPage) and because you set your authentication entry point to just return 401 status you will have that issue.
just comment this:
.and()
// handle an authorized attempts
.exceptionHandling().authenticationEntryPoint((req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED))
and it should redirect you to the login page.
PS: Based on your configuration if I'm not authenticated and trying to access /auth/loginPage I'll be redirected to /auth/LoginPage, and once I enter the creds I'll be authenticated successfully and redirected again to the same page /auth/loginPage
How can i ignore authentication for login page.
OncePerRequestFilter has a method shouldNotFilter that you can override.
For example:
#Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return new AntPathMatcher().match("/auth/loginPage", request.getServletPath());
}
The problem is as follows. I implemented the login through Keycloak Bearer Spring security like this
public class KeycloakSecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Override
public void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.sessionAuthenticationStrategy(sessionAuthenticationStrategy())
.and()
.csrf().disable()
.addFilterBefore(keycloakPreAuthActionsFilter(), LogoutFilter.class)
.addFilterBefore(keycloakAuthenticationProcessingFilter(), X509AuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
.and()
.authorizeRequests().antMatchers(Constants.API_BASE_PATH + "/**").authenticated();
}
}
when I send the Authorization request header empty, keycloak throws error 401. Which I cannot catch through #ExceptionHandler like this :
#ExceptionHandler(RuntimeException.class)
protected ResponseEntity<Object> keycloakAuthenticationExceptionn(RuntimeException ex) {
return buildResponseEntity(new ErrorResponseWrapper(BAD_REQUEST,new
MessageResponse(ex.getLocalizedMessage()),ex,ErrorCode.NOT_AUTHORIZED));
}
KeyCloak has a KeycloakAuthenticationFailureHandler that handles authentication failures.
I was able to solve a similar problem by creating a Custom KeycloakAuthenticationFailureHandler then set my Custom class while overriding the
KeycloakAuthenticationProcessingFilter.
#Bean
#Override
protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(this.authenticationManagerBean());
filter.setSessionAuthenticationStrategy(this.sessionAuthenticationStrategy());
filter.setAuthenticationFailureHandler(new CustomKeycloakAuthenticationFailureHandler());
return filter;
}
Inside my Custom Class...
public class CustomKeycloakAuthenticationFailureHandler implements AuthenticationFailureHandler {
public CustomKeycloakAuthenticationFailureHandler() {}
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
if (!response.isCommitted()) {
if (KeycloakCookieBasedRedirect.getRedirectUrlFromCookie(request) != null) {
response.addCookie(KeycloakCookieBasedRedirect.createCookieFromRedirectUrl((String)null));
}
//response.sendError(401, "Unable to authenticate using the Authorization header");
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getOutputStream().println("{ \"error\": \"" + exception.getMessage() + "\" }");
} else if (200 <= response.getStatus() && response.getStatus() < 300) {
throw new RuntimeException("Success response was committed while authentication failed!", exception);
}
}
}
I am able to use the response OutputStream to customize my response to the client.
I commented the default KeyCloak response.sendError line.
It looks like KeyCloak handles the exceptions internally.
This solution also solves the issue where the header: WWW-Authenticate
is not duplicated in the response.
Turning on DEBUG for KeyCloak troubleshooting HELPS: logging.level.org.keycloak=DEBUG
Hope this helps.
I'm not sure about the answer but i faced something like your question. ExceptionHandler working after http filters, so maybe Keycloak exception throws in his filter, before the request can to be handled using your ExceptionHandler. So you can trace the whole logs to see from where the exception thrown. I hope that will help you
**Thanks for answers!**
I think, this is the problem:
Earlier I used dependency keycloak version 4.0.0.Final
pom.xml dependency
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
<version>4.0.0.Final</version>
</dependency>
and spring boot version 1.5.4.RELEASE. Everything worked great.
Now I'm using spring boot version 2.1.5.RELEASE and keycloak version 10.0.1
pom.xml dependency
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-2-adapter</artifactId>
<version>10.0.1</version>
</dependency>
I checked it again from Postman when sending Authorization token "bearer " + "token" in the request header. I get a response with two the same WWW-Authenticate values in the header.
Earlier, header came in a single copy.
Can you please tell me what the problem ?.
We have a tomcat application which works fine in IE7/8 and Firefox. The only browser we are having issues with (that we care about at this point) is google Chrome(above 47 version).
Users can navigate to the application fine and log in and do whatever they need to do in Firefox and IE. However, when trying to log in with Chrome, the session is apparently lost immediately after log in and when the authenticated user tries to navigate to another page they are bumped back to the log in page. This happens consistently.
I have looked at the JSESSIONID that is set in the cookie, which is sent back to mozila and IE on every request, while it does not for Chrome(above 47 version).
It's clear Spring SecurityContextHolder in which we set after the login. and take new SecurityContextHolder in every request.
here I am atteched my code where I am spring security is configure.
Any ideas are welcome!
We are using tomcat 8.0.33 and spring boot 4.2.4.RELEASE
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**","/restServiceFromKwh360/**","/cloudSSO/ssoLogin","/cloudSSO/ssoLogout","/cloudSSO/ssoCallback",
"/cloudSSO/ssoLoginError","/cloudSSO/ssoReturnFromKwh360Services").permitAll()
.antMatchers("/energyAudit/**").access("hasRole('ROLE_CUSTOMER_ADMIN')")
.and().formLogin().loginPage("/cloudSSO/ssoLogin").permitAll()
.and().exceptionHandling().accessDeniedPage("/cloudSSO/ssoLoginError")
.and()
.csrf().disable();
}
}
and here I am set Authentication object manually
#Override
public void AutoLoginUser(String username, HttpServletRequest request) {
try{
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(authentication.getPrincipal() == "anonymousUser"){
User user;
try {
user = userDao.getUserByEmail(username);
} catch (KWH360DAOException e) {
e.printStackTrace();
logger.error("Request user is not registred with the system >>" + username);
throw new UsernameNotFoundException("You are not registered");
}
if (user == null){
logger.error("Request user is not registred with the system >>" + username);
throw new UsernameNotFoundException("You are not registered");
}
List<GrantedAuthority> authorities = buildUserAuthority(user.getRole().getName());
authentication = new UsernamePasswordAuthenticationToken(user,user.getPassword().toString(), authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext());
}
}catch(Exception e){
e.printStackTrace();
}
}