How to allow original HttpServletRequest to handle login using Spring Boot Security - spring

I am using Spring Security and I would like to handle authentication by the jee container.
So the only reason for spring security is authorization. All the pre authenticated filters are configured already and working.
I created a custom endpoint to trigger authentication.
import javax.http.HttpServletRequest;
...
#PostMapping("/auth/login")
public void login(HttpServletRequest request) throws ServletException {
request.login("something", "something");
}
...
It seems that HttpServletRequest is wrapped by SecurityContextHolderAwareRequestWrapper. The following method (which is provided by spring boot) handles authentication:
HttpServlet3RequestFactory$Servlet3SecurityContextHolderAwareRequestWrapper.login(...)
#Override
public void login(String username, String password) throws ServletException {
if (isAuthenticated()) {
throw new ServletException("Cannot perform login for '" + username
+ "' already authenticated as '" + getRemoteUser() + "'");
}
AuthenticationManager authManager = HttpServlet3RequestFactory.this.authenticationManager;
if (authManager == null) {
HttpServlet3RequestFactory.this.logger.debug(
"authenticationManager is null, so allowing original HttpServletRequest to handle login");
super.login(username, password);
return;
}
Authentication authentication;
try {
authentication = authManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password));
}
catch (AuthenticationException loginFailed) {
SecurityContextHolder.clearContext();
throw new ServletException(loginFailed.getMessage(), loginFailed);
}
SecurityContextHolder.getContext().setAuthentication(authentication);
}
If authManager is null it should trigger login from the original HttpServletRequest.
But that is the thing it isn't null because of the AnonymousAuthenticationProvider and PreAuthenticatedAuthenticationProvider attached to the ProviderManager. Is there another way to trigger login on its original HttpServletRequest?

I got around it by using my container servlet request in my method:
import com.ibm.ws.webcontainer.srt.SRTServletRequest;
#PostMapping("/auth/login")
public void login(SRTServletRequest request) throws ServletException {
request.login("something", "something");
}

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.

Authentication object is null after bypassing the particular request

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.

Spring Security - when get login page, security try to authenticate and return 401 error

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());
}

Spring Boot application generate new SecurityContextHolder in every request

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();
}
}

How to bypass UsernamePasswordAuthentication in Spring Security

I'm implementing an API that accepts a JWT as request parameter and on authentication, returns a new JWT.
#RequestMapping(value = "/authenticate/token", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public ResponseEntity authenticate(#RequestParam("login_token") final String token, HttpServletResponse response) {
LOG.debug("Request to login with token : {}", token);
try {
String jwt = authService.loginByToken(token);
response.addHeader(JWTConfigurer.AUTHORIZATION_HEADER, "Bearer " + jwt);
return ResponseEntity.ok(new IdentityToken(jwt));
} catch (AuthenticationException ae) {
LOG.trace("Authentication exception trace: {}", ae);
return new ResponseEntity<>(Collections.singletonMap("AuthenticationException",
ae.getLocalizedMessage()), HttpStatus.UNAUTHORIZED);
}
}
My loginByToken implementation looks as below
#Override public String loginByToken(String token) {
if (!tokenProvider.validateToken(token)) {
throw new BadCredentialsException("Token is invalid.");
}
SecureToken secureToken = tokenProvider.parseJwtToken(token);
User user = userRepository.findByEmail(secureToken.getEmail());
// TODO: Check Account Status is valid, User status is valid
Calendar c = Calendar.getInstance();
c.setTime(new Date());
c.add(Calendar.DATE, Constants.PASSWORD_EXPIRY_DAYS);
if (user.getPasswordExpiryDt() != null
&& user.getPasswordExpiryDt().after(c.getTime())) {
throw new BadCredentialsException("Password changed");
}
// TODO: Find how to create authentication object and return ID token.
// return tokenProvider.createToken(authentication, false);
return token;
}
At this point, I'm not sure how to create an authentication object that contains all user details that I could pass to createToken function that creates an identity token.
Here is my project without the changes mentioned in this post - https://github.com/santoshkt/ngx-pipes-test.
I have read about Anonymous Authentication, PreAuthenticated etc but not sure how to deal with this case. Will appreciate any pointers on how to do this.
If you want to use Spring Security, you should probably not use a Spring MVC endpoint to handle (pre-)authentication.
In your case you probably want to change your Spring security configuration so that it will have a filter that obtains your token from your request parameters and an authentication provider that retrieves the user/authentication object from your token:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/authenticate/token")
.authorizeRequests()
.anyRequest().authenticated()
.and()
// This is a filter bean you'll have to write
.addFilterBefore(filter(), RequestHeaderAuthenticationFilter.class)
// This is your token verifier/decoder
.authenticationProvider(authenticationProvider())
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
For the filter you could extend from AbstractPreAuthenticatedProcessingFilter and make it return the login_token parameter. In here you have to implement two methods being getPreAuthenticatedPrincipal() and getPreAuthenticatedCredentials().
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
// You could already decode your token here to return your username
return request.getParameter("login_token");
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return request.getParameter("login_token");
}
Your authentication provider should be of type PreAuthenticatedAuthenticationProvider and in here you can set an AuthenticationUserDetailsService:
#Bean
public AuthenticationProvider authenticationProvider() {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
// service is a bean of type AuthenticationUserDetailsService
// You could autowire this in your security configuration class
provider.setPreAuthenticatedUserDetailsService(service);
return provider;
}
Now you can create your own AuthenticationUserDetailsService to retrieve a UserDetails object based on your token:
#Service
public class TokenAuthenticationUserDetailsService implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
#Override
public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken authentication) throws UsernameNotFoundException {
// In this case the authentication.getCredentials() will contain your token and you can return a UserDetails object
return new User(/** ... */);
}
}
Since you want to provide the HTML page for the JWT token request the best approach is that you create you own Spring Security Custom Entry Point
You may give a look here for an example
If it's another system to manage the authentication and you want just manage the authorization you can "trust" the other System and then manage your own authorizations; in this case you can use the PreAuthentication Scenario as described here; you can find a sample here
I hope it's useful

Resources