I am not being able to implement JWT token on a server side spring boot app - spring-boot

I have created a MVC spring boot app, mapping with a mySQL table, configured security to allow admin to access list of students and users are able to access list of subjects. Till this point everything is working fine.
Now i wanna put into play the JWT token. The problem is every video i follow use the JWT with post man and with simple #RestControllers but no MVC.
When i try to access 1 of the lists after starting the server, the custom login page comes to play and i enter details and everything works fine.
But, how do I implement the token? I have created a no MVC endpoint named "/authenticate" which returns me a token and using post man it works fine. But how do i return it in the app when i try to login using custom login page? Im not getting smthng here. Any help is appriciated. Thanks in advance!

Here is the endpoint for authenticate using postman:
#PostMapping ("/authenticate")
public ResponseEntity<?> createAuthenticationToken(#RequestBody AuthenticationRequest authenticationRequest) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(), authenticationRequest.getPassword()));
} catch (BadCredentialsException e)
{
throw new Exception("Incorrect UserName or Password!",e);
}
UserDetails userDetails =
customUserDetailsService.loadUserByUsername(authenticationRequest.getUsername());
String jwt = jwtUtil.generateToken(userDetails);
return ResponseEntity.ok(new AuthenticationResponse(jwt));
}
Here is my security configuration :
http.
csrf().disable()
.authorizeRequests()
.antMatchers("/","/registration/","/logout","/login","/authenticate").permitAll()
.antMatchers("/helloAdmin").hasRole("Admin")
.antMatchers("/helloUser").hasRole("User")
.antMatchers("/students/").hasRole("Admin")
.antMatchers("/subjects/**").hasAnyRole("User","Admin")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.and()
.logout()
.logoutSuccessUrl("/login?logout");
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);

Related

Spring PKCE flow with custom login page

Hi I have a spring application using PKCE flow, I want to use custom login page in angular application (actually I use defaul login page spring app), follow my configuration in spring:
#Bean
public SecurityFilterChain resourcedefaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/categorias").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable()
.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());
http.logout(logoutConfig -> logoutConfig.logoutSuccessHandler((request, response, auth) -> {
var returnTo = request.getParameter("returnTo");
if (StringUtils.isBlank(returnTo)){
returnTo = algamoneyApiProperty.getSeguranca().getAuthServerUrl();
}
response.setStatus(FOUND);
response.sendRedirect(returnTo);
}));
return http.formLogin(Customizer.withDefaults()).build();// I want disable this config and use my login form in front-en
}
I have a question about this:
I my login page what endpoint I need to send a user credentials for spring app ? '/login', 'oauth2/login' and how params I need to use in this request?
I use spring-authorization-server in 0.3.1 version

Form based or Single REST Controller authentication

Currently, my configuration is using HTTP basic and issuing JWT to the client. Can someone please help to make it such a way that JWT is issued from a single REST controller?
Without using HTTP basic.
I just want to do JWT authentication in my application.
Your effort and support is highly appreciated.
#Bean
public SecurityFilterChain userFilterChain(HttpSecurity http) throws Exception {
http.cors().disable();
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
http.authorizeHttpRequests().antMatchers("/token").permitAll().and()
.antMatcher("/**")
.authorizeHttpRequests(userauthz ->
userauthz.antMatchers("/myaccount").authenticated()
)
.authenticationProvider(userAuthProvider())
.httpBasic();
return http.build();
}

SSO/Oauth login on same application, Login based on UrL

I have spring MVC application and I am trying to register different SSO login on same application. For example if url is (admin.abc.com), It should login from microsoft SSO and if the url is abc.com it should redirect to google login.
Here is my code but when I run the code both sso open with giving me the option to choose.
Is there any way I can set sso login based on domain instead of select option.
#Autowired
ClientRegistrationRepository regRepository;
#Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(Arrays.asList(msClientRegistration(), googleSSOClientRegistration()));
}
and the configuration for antmatcher is like this
#Override
protected void configure(final HttpSecurity http)
throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/login.htm").authenticated()
.antMatchers("/**")
.permitAll().anyRequest()
.authenticated().and().logout()
.logoutSuccessHandler(oauthLogoutSuccessHandler())
.invalidateHttpSession(true)
.logoutUrl("/logout")
.and().oauth2Login()
.failureHandler(new CustomAuthenticationFailureHandler())
.authorizationEndpoint()
.authorizationRequestResolver(
new CustomAuthorizationRequestResolver(regRepository, "/oauth2/authorization"))
.and().tokenEndpoint()
.accessTokenResponseClient(authorizationCodeTokenResponseClient())
.and().and().headers()
.frameOptions()
.sameOrigin().and().csrf()
.disable();
}
How to add antMatcher configuration based on domain url? google sso for abc.com and admin.abc.com for microsoft login with OAuth2.
Instead of having this I want to redirect base on url's.. either Google login or Microsoft.

How Can I Customize Login Page for Oauth2 in Spring Webflux?

I just want to override default oauth2 login url (/login). How can I do that? The config I have tried without success:
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange().pathMatchers(permittedUrls).permitAll()
.anyExchange().authenticated()
.and()
.oauth2Login(Customizer.withDefaults()).formLogin().loginPage("/oauth2_login")
.authenticationSuccessHandler(this::onAuthenticationSuccess)
.and()
.csrf().disable();
return http.build();
I was hoping it will redirect to /oauth2_login url but it didn't work. It still redirect to /login. But this time it returns 404 instead of showing default login page.
The code above is customizing the login page for formLogin which is typically username/password based log in from a form. It's much easier to see what configuration you are impacting using the new lambda style approach, so I have updated the entire configuration to use it. If you want to customize the login page for oauth2Login, then you should change the login page on it. For example:
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.pathMatchers(permittedUrls).permitAll()
.anyExchange().authenticated()
)
.oauth2Login(oauth2 -> oauth2
// you now must render a log in page for the URL /login
.loginPage("/login")
);
// remove formLogin that was for a username/password based log in
// if you are doing oauth2 login I'm guessing you allow users to work within a browser, so you should not disable csrf
return http.build();
}

Spring Security unexpected behavior for REST endpoints authentication?

The scenario we are looking for is as follows:
client connects with REST to a REST login url
Spring microservice (using Spring Security) should return 200 OK and a login token
the client keeps the token
the client calls other REST endpoints using the same token.
However, I see that the client is getting 302 and a Location header, together with the token. So it does authenticate, but with un-desired HTTP response status code and header.
The Spring Security configuration looks like this:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable() // Refactor login form
// See https://jira.springsource.org/browse/SPR-11496
.headers()
.addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN))
.and()
.formLogin()
.loginPage("/signin")
.permitAll()
.and()
.logout()
.logoutUrl("/signout")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated();
...
}
I tried adding interceptors and filters but can't see where 302 and Location being set and added in Spring side.
However, the Location header does show in the response headers received at the client side (together with the rest of the Spring Security headers LINK):
Server=Apache-Coyote/1.1
X-Content-Type-Options=nosniff
X-XSS-Protection=1; mode=block
Cache-Control=no-cache, no-store, max-age=0, must-revalidate
Pragma=no-cache
Expires=0
X-Frame-Options=DENY, SAMEORIGIN
Set-Cookie=JSESSIONID=D1C1F1CE1FF4E1B3DDF6FA302D48A905; Path=/; HttpOnly
Location=http://ec2-35-166-130-246.us-west-2.compute.amazonaws.com:8108/ <---- ouch
Content-Length=0
Date=Thu, 22 Dec 2016 20:15:20 GMT
Any suggestion how to make it work as expected ("200 OK", no Location header and the token)?
NOTE: using Spring Boot, Spring Security, no UI, just client code calling REST endpoints.
If you need a rest api, you must not use http.formLogin(). It generates form based login as described here.
Instead you can have this configuration
httpSecurity
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.logout()
.disable()
.addFilterBefore(authTokenFilter, UsernamePasswordAuthenticationFilter.class);
Create a class, AuthTokenFilter which extends Spring UsernamePasswordAuthenticationFilter and override doFilter method, which checks for an authentication token in every request and sets SecurityContextHolder accordingly.
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) response;
resp.setHeader("Access-Control-Allow-Origin", "*");
resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
resp.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, " + tokenHeader);
HttpServletRequest httpRequest = (HttpServletRequest) request;
String authToken = httpRequest.getHeader(tokenHeader);
String username = this.tokenUtils.getUsernameFromToken(authToken); // Create some token utility class to manage tokens
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(-------------);
// Create an authnetication as above and set SecurityContextHolder
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
Then create an AuthenticationController, mapped with /login url, which checks credentials, and returns token.
/*
* Perform the authentication. This will call Spring UserDetailsService's loadUserByUsername implicitly
* BadCredentialsException is thrown if username and password mismatch
*/
Authentication authentication = this.authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetailsImp userDetails = (UserDetailsImp) authentication.getPrincipal();
// Generate token using some Token Utils class methods, using this principal
To understand loadUserByUsername , UserDetailsService and UserDetails, please refer Spring security docs
}
For better understanding, please thoroughly read above link and subsequent chapters.
It's a 302 response telling the browser to redirect to your login page. What do you expect to happen? 302 response must have a Location header.
http.formLogin()
is designed for form-based login. So the 302 status and Location header in the response is expected if you attempt to access a protected resource without being authenticated.
Based on your requirement/scenario,
client connects with REST to a REST login url
have you considered using HTTP Basic for authentication?
http.httpBasic()
Using HTTP Basic, you can populate the Authorization header with the username/password and the BasicAuthenticationFilter will take care of authenticating the credentials and populating the SecurityContext accordingly.
I have a working example of this using Angular on the client-side and Spring Boot-Spring Security on back-end.
If you look at security-service.js, you will see a factory named securityService which provides a login() function. This function calls the /principal endpoint with the Authorization header populated with the username/password as per HTTP Basic format, for example:
Authorization : Basic base64Encoded(username:passsword)
The BasicAuthenticationFilter will process this request by extracting the credentials and ultimately authenticating the user and populating the SecurityContext with the authenticated principal. After authentication is successful, the request will proceed to the destined endpoint /principal which is mapped to SecurityController.currentPrincipal which simply returns a json representation of the authenticated principal.
For your remaining requirements:
Spring microservice (using Spring Security) should return 200 OK and a login token
the client keeps the token
the client calls other REST endpoints using the same token.
You can generate a security/login token and return that instead of the user info. However, I would highly recommend looking at Spring Security OAuth if you have a number of REST endpoints deployed across different Microservices that need to be protected via a security token. Building out your own STS (Security Token Service) can become very involved and complicated so not recommended.
You can implement your custom AuthenticationSuccessHandler and override method "onAuthenticationSuccess" to change the response status as per your need.
Example:
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
ObjectMapper mapper = new ObjectMapper();
Map<String, String> tokenMap = new HashMap<String, String>();
tokenMap.put("token", accessToken.getToken());
tokenMap.put("refreshToken", refreshToken.getToken());
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
mapper.writeValue(response.getWriter(), tokenMap);
}
You need to override the default logout success handler to avoid redirect. In spring boot2 you can do as below:
....logout().logoutSuccessHandler((httpServletRequest,httpServletResponse,authentication)->{
//do nothing not to redirect
})
For more details: Please check this.
You can use headers().defaultsDisabled() and then chain that method to add the specific headers you want.

Resources