Spring receive login credentials in Post endpoint via #RequestBody and return JSESSIONID - spring

I'm creating a Spring JPA application with Spring Security with Basic Authentication for a project.
I want to send login credentials of a user in body, then return JSESSIONID back to user
This is my endpoint that returns a default api endpoint for specific logged in user.
#GetMapping(value = "/success" ,produces = MediaType.APPLICATION_JSON_VALUE)
public java.util.Map<String, String> index() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (!(auth instanceof AnonymousAuthenticationToken))
return Collections.singletonMap("href" ,loginSuccessHandler(((MyUserPrincipal)auth.getPrincipal()).getUser().getId()));
return Collections.singletonMap("href" ,"/login/error");
}
I have attempted to make custom login with following endpoint (idea is to have a single hardcodable "/success" endpoint after login to give the actual user specific endpoint to frontend).
#RequestMapping(value = "/login", method = RequestMethod.POST)
public Authentication login(#RequestBody ObjectNode JSONObject) {
String username = JSONObject.get("username").asText();
String pwd = JSONObject.get("password").asText();
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, pwd));
boolean isAuthenticated = isAuthenticated(authentication);
if (isAuthenticated) {
SecurityContextHolder.getContext().setAuthentication(authentication);
}
return authentication;
}
With following SecurityConfig:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().configurationSource(corsConfigurationSource()).and()
.authorizeRequests()
// ID ról
// admin = 2
// student = 1
// caretaker = 3
// teacher = 4
.antMatchers("/api/**").access("hasAnyAuthority('1','2','3','4')")
.antMatchers("/login").anonymous()
.antMatchers("/login").permitAll()
.and()
.formLogin()
.loginPage("/login").permitAll()
// .loginProcessingUrl("/login").permitAll() //tried to use this but it does nothing
.defaultSuccessUrl("/success", true)
.and()
.logout().and()
.httpBasic();
}
Example postman request to send login credentials to Spring:
Postman post request with credentials in body
But said request never enters the "/login" post endpoint.
The only response is whatever I put in the #get "/login" endpoint.
I wish to know how to set up the configuration and the /login endpoints in order for them to authorize user based on credentials from #RequestBody.

Answer:
#CrossOrigin(origins = "http://localhost:3000")
#GetMapping("/login")
public java.util.Map<String, String> getLogin(#RequestBody ObjectNode JSONObject)
{
String username = JSONObject.get("username").asText();
String pwd = JSONObject.get("password").asText();
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, pwd));
boolean isAuthenticated = isAuthenticated(authentication);
if (isAuthenticated) {
SecurityContextHolder.getContext().setAuthentication(authentication);
}
return Collections.singletonMap("href" ,loginSuccessHandler(((MyUserPrincipal)authentication.getPrincipal()).getUser().getId()));
}
get mapping does authorization like here.
Postman result: Postman request authorized
By getting the cookie from that via(in Tests tab):
var a = pm.cookies.get("JSESSIONID")
pm.globals.set("JSESSIONID", a)
Access is possible to secured endpoint: Working secured endpoint

Related

How to get proper user from SecurityContextHolder Authentication within integration test using rest assured?

I cannot successfully get the right username within integration test using Rest assured. I do not understand why but Authentication from SecurityContextHolder returns "anonymousUser" instead of "myuser". What is wrong with my config?
Response body doesn't match expectation. Expected: is "Authentication
successful for user myuser" Actual: Authentication successful for
user anonymousUser
Controller:
#RestController
#RequestMapping(value = "/api/ping", produces = TEXT_PLAIN_VALUE)
public class PingController {
#GetMapping
public #ResponseBody String ping() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return "Authentication successful for user " + authentication.getName();
}
}
Security config:
http.authorizeRequests()
.antMatchers("/api/ping").permitAll()
.antMatchers("/api/**").hasRole(ROLE_API)
//...another roles...
.anyRequest().permitAll()
.and()
.httpBasic()
.authenticationEntryPoint(basicRestAuthenticationEntryPoint)
.and()
.headers().frameOptions().disable()
.and().cors()
.and()
.csrf().disable();
Test:
#Test
void test() {
given()
.contentType(TEXT)
.auth().basic("myuser", "password").
when()
.get("/api/ping").
then()
.statusCode(200)
.body(is("Authentication successful for user: myuser"));
}

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

How can I get the OAuth2AccessToken with Spring-Boot?

I am using Spring-Boot and Spring Security with an OAuth2 login from a third party.
The SSO provider has an accesstoken end point which returns the following JSON
{
"access_token": "CGjok",
"refresh_token": "TSHO6E",
"scope": "openid profile ",
"id_token": "eyJ0eXAiOiJKV1QiLCg",
"token_type": "Bearer",
"expires_in": 7199,
"nonce": "ImplicitFlowTest"
}
The login is working with the #EnableOAuth2Sso annotation as follows:
#EnableOAuth2Sso
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/restapi/**").hasAuthority("Mitarbeiter")
.antMatchers("/login", "/static/**", "/", "/actuator/prometheus","/error**","/logout").permitAll()
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).invalidateHttpSession(true)
.deleteCookies("SMSESSION", "JSESSIONID", "XSRF-TOKEN").logoutSuccessUrl("/");
http
// CSRF Token
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
We are able to logout of the application but we also want to send a request to the Authorization Server. To do so I need to access the token info endpoint.
Within my controllers I am able to see the Principal is getting the correct information from the user endpoint but where in Spring Boot is the information from the accessToken endpoint stored. I have found the class OAuth2AccessToken but cannot figure out how to read it in Spring Controller. I can access the OAuth2Authentication by casting the Principal as expected.
The SSO authorization server has the following endpoint that I need to call:
/oauth2/connect/endSession?id_token_hint=<oidc-token>&post_logout_redirect_uri=<post-logout-redirect-uri>
The refers to the value in the JSON from the accesstoken endpoint. How can I access these values given my setup?
Read token value from Security Context
String tokenValue = null;
final Authentication authenticationObject = SecurityContextHolder.getContext().getAuthentication();
if (authenticationObject != null) {
final Object detailObject = authenticationObject.getDetails();
if (detailObject instanceof OAuth2AuthenticationDetails) {
final OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) detailObject;
tokenValue = details.getTokenValue();
} else if (detailObject instanceof OAuth2AccessToken) {
final OAuth2AccessToken token = (OAuth2AccessToken) detailObject;
tokenValue = token.getValue();
} else {
tokenValue = null;
}
}

how to fetch JWT tokens from keycloak API in a springboot application

I am new to Keycloak. I started using it as a part of my spring-boot application.
I have extended KeycloakWebSecurityConfigurerAdapter and overridden methods like configure, configureGloabl etc., to have a specific authentication (see below code snippet.)
I am wondering if there is a possibility to get access of JWT token object for fetching further properties. Also it is not clear to me how to invalidate the token once the user is logged-out. At present once a single user is logged in, I am unable to sign him out and the JWT token seems to be remaining all the time.
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/customers*")
.hasRole("user")
.anyRequest()
.permitAll();
}
you can get the access token in postman using this;
here ConfigKeycloak is realm name and config-app is client name.
Another way to get the access token is this.
#RequestMapping(value = "/customers", method = RequestMethod.GET)
#PreAuthorize("hasRole('ROLE_USER')")
public String getCustomers(){
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) request.getUserPrincipal();
KeycloakPrincipal principal=(KeycloakPrincipal)token.getPrincipal();
KeycloakSecurityContext session = principal.getKeycloakSecurityContext();
AccessToken accessToken = session.getToken();
String a = principal.getName();
username = accessToken.getPreferredUsername();
emailID = accessToken.getEmail();
lastname = accessToken.getFamilyName();
firstname = accessToken.getGivenName();
realmName = accessToken.getIssuer();
AccessToken.Access realmAccess = accessToken.getRealmAccess();
}
you can logout from the session using this.
#RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(HttpServletRequest request) throws ServletException {
request.logout();
return "/";
}

How to have sessions in http basic authentication in a spring based server

I am trying to create a Spring server for an android client and am using basic authentication. I have a controller as follows:
#RequestMapping(value = "login", method = RequestMethod.GET, produces = "application/json")
public #ResponseBody Message login(HttpSession session) {
logger.info("Accessing protected resource sid:"+session.getId());
return new Message(100, "Congratulations!", "You have logged in.");
}
#RequestMapping(value = "play", method = RequestMethod.GET, produces = "application/json")
public #ResponseBody Message play(HttpSession session) {
logger.info("Accessing protected play resource");
return new Message(100, "Congratulations!", "Launching play.");
}
Once the client has authenticated, during login, I don't want it to need to reauthenticate while calling play.
My security config is:
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/signup","/about").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
// #formatter:on
}
}
I have tried to enable sessions above but if I try to print the session id in the request handlers in login and play, I get different ids after logging in with authentication. I am using HttpBasic security. Is it possible to have sessions in HttpBasic security? I read some articles which seemed to indicate that it is stateless and one cannot. Is there a workaround or do I have to switch to a different security model?
Client code:
On the client side, I send requests as follows for login.
#Override
protected Message doInBackground(Void... params) {
//final String url = getString(R.string.base_uri) + "/getmessage";
final String url = "http://10.0.2.2:8080/login";
// Populate the HTTP Basic Authentitcation header with the username and password
HttpAuthentication authHeader = new HttpBasicAuthentication(username, password);
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAuthorization(authHeader);
requestHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
// Create a new RestTemplate instance
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJacksonHttpMessageConverter());
try { // Make the network request
Log.d(TAG, url);
ResponseEntity<Message> response = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<Object>(requestHeaders), Message.class);
if(response.getBody().getId() == 100)
login_success = true;
return response.getBody();
I am trying to write the client code for 'play' similarly but without the need to pass the authentication header. I feel, I should somehow pass a session related parameter in the request header to notify the server that it is part of the same session but cant figure out how to do that.
You are trying to archive stateful communication with sessions, but it won't work since RestTemplate is working in a stateless manner. It will not keep the state (i.e. the session id) from the login to the next call. Only by setting a custom ClientHttpRequestFactory I could see this work (I'm not sure on Android).
If you want communication with state, you could look at Apache HttpClient. By using it with a HttpContext that you re-use between requests, it will keep the state.
There is a possibility to fiddle with the session id yourself, but I can't recommend it.

Resources