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

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.

Related

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

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

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 authorization returns 403 for any authorization request using #RolesAllowed, #Secured or #PreAuthorize

I've been working from this article (and a few other similar ones): https://medium.com/omarelgabrys-blog/microservices-with-spring-boot-authentication-with-jwt-part-3-fafc9d7187e8
The client is an Angular 8 app which acquires a Jwt from an independent microservice. Trying to add filter(s) to a different microservice to require specific authorization via jwt roles.
Consistently receiving 403 errors.
Security Config:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true,
securedEnabled = true,
jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurityConfig() {}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable()
// make sure we use stateless session; session won't be used to store user's state.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// Add a filter to validate the tokens with every request
.addFilterAfter(new JwtAuthorizationFilter2(), UsernamePasswordAuthenticationFilter.class)
// authorization requests config
.authorizeRequests()
// Any other request must be authenticated
.anyRequest().authenticated();
}
}
Filter:
public class JwtAuthorizationFilter2 extends OncePerRequestFilter {
private final String HEADER = "Authorization";
private final String PREFIX = "Bearer ";
private final String SECRET = "foo";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String token = request.getHeader(SecurityConstants.HEADER_STRING);
if (token != null) {
// parse the token.
DecodedJWT decoded = JWT.require(Algorithm.HMAC512(SecurityConstants.SECRET.getBytes()))
.build()
.verify(token.replace(SecurityConstants.TOKEN_PREFIX, ""));
String user = decoded.getSubject();
List<SimpleGrantedAuthority> sgas = Arrays.stream(
decoded.getClaim("roles").asArray(String.class))
.map( s -> new SimpleGrantedAuthority(s))
.collect( Collectors.toList());
if (sgas != null) {
sgas.add(new SimpleGrantedAuthority("FOO_Admin"));
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
user,
null,
sgas);
SecurityContextHolder.getContext().setAuthentication(auth);
}
else {
SecurityContextHolder.clearContext();
}
chain.doFilter(request, response);
}
}
}
This code works fine without any authorization requirements defined, but if an authorization is defined in WebSecurityConfig, or by decorating a controller method, http 403 is received for all requests in scope.
Examples:
.authorizeRequests().antMatchers("/**").hasRole("FOO_Admin")
// or any of these
#PreAuthorize("hasRole('FOO_Admin')")
#RolesAllowed({"FOO_Admin"})
#Secured({"FOO_Admin"})
Device get(#PathVariable String id) {
// some code
}
When code is halted at SecurityContextHolder.getContext().setAuthentication(auth),
auth.authenticated = true
and
auth.authorities includes a SimpleGrantedAuthority for "FOO_Admin"
So I'm wondering whether:
The FilterChain needs an Authentication Filter (or does authentication occur in JwtAuthorizationFilter2?)?
There is not a spelling or formatting or capitalization difference to role name.
I'm stupefied. Any help would be greatly appreciated.
#PreAuthorize("hasRole('FOO_Admin')) expects the user has an authority ROLE_FOO_Admin, which will be prefixed by ROLE_. However, the user only has the authority FOO_Admin , hence it fails to access the method.
You have several options:
(1) Change the prefix by declaring a GrantedAuthorityDefaults bean:
#Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("FOO");
}
And use #PreAuthorize(hasRole('Admin')) to secure the method.
(2) Or more simpler is to use #PreAuthorize("hasAuthority('FOO_Admin')") , which will directly check if the user has the authority FOO_Admin , without adding any prefix to it.
P.S JwtAuthorizationFilter2 only verifies if an user is valid and get the related user information which prepare for the authorization user later. It is an authentication and I would rename it to JwtAuthenticationFilter2 to describe more exactly what it does actually.

Spring Boot Security , Form based login to invoke a custom API for authentication

So wondering if it's possible or not. I'm trying to authenticate my rest API's in spring boot with a post API which is already present which validate the user.
I'm using fromLogin based authentication and trying to invoke that Rest Controller and use that login API by passing the post parameter. There I'm creating the spring security context
I'm trying to invoke the POST API of login on login submit. Authentication is not working.
Can we achieve this using form login? Let me know if my understanding very new to spring boot security
My code somewhat looks like this
Controller
#RestController
#RequestMapping("/somemapping")
public class AuthController {
#RequestMapping(value = "/login", method = RequestMethod.POST)
public UserData authenticateUser(#RequestBody(required = true) UserInfo userInfo, HttpServletRequest request) {
// get user data goes here
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userdata.getUsername(), userdata.getPassword(), new ArrayList < > ());
authentication.setDetails(userdata);
SecurityContextHolder.getContext().setAuthentication(authentication);
send the info to caller
return userdata;
}
//Security Adapter
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/somemapping/**", "/login*", ).permitAll()
.anyRequest().authenticated().and()
.formLogin().loginPage("/")
.and().authenticationEntryPoint(authenticationPoint);
}

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 "/";
}

Resources