I want to authenticate the user from a POST method in spring security. The post hits the controller method but the user never gets authenticated. Here is the scenario
#Autowired
private AuthenticationManagerBuilder builder;
#RequestMapping(value="/signin", method = RequestMethod.POST)
public ResponseData<Client> login(#RequestParam(value="username") String name, #RequestParam(value="password") String password,HttpServletRequest req) {
System.out.println("here..."); //this executes
Client ac = accountRepository.findByEmailAndActive(name,true);
//does the authentication
final Authentication authentication = builder.getOrBuild().authenticate(
new UsernamePasswordAuthenticationToken(
name,
password
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
return ResponseData.successData(ac);
}
This is my spring security methods/handler
.antMatchers(HttpMethod.POST,"/signin").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").defaultSuccessUrl("/index")
.loginProcessingUrl("/signin2")
.permitAll()
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout=true")
.deleteCookies("JSESSIONID", "remember-me")
.invalidateHttpSession(true)
.and().csrf().disable()
.rememberMe().tokenRepository(persistentTokenRepository()).tokenValiditySeconds(1200000);
Kindly assist
Default login path in spring security is something like http://localhost:8080/login. Change this path following this instructions. If you already did this please provide your implementation of it.
If you want the /signin endpoint to be authenticated, you must remove .antMatchers(HttpMethod.POST,"/signin").permitAll() from your security config.
Related
I understand this question has been asked before, but none of what worked for the others worked for me. That said, here it is:
After setting up .logout() and its relevant associate methods, I still cannot make my Spring server logout properly. To be precise, this is the sequence I'm following:
POST login, get token
Use token for other HTTP requests, such as a standard GET for DB data
Logout using the vanilla logout URL
Try to do step 2 again, to see if logout was successful.
At 4, for a reason that is unknown to me, I'm still able to GET the data, which is wrong.
Some of the things I've tried are:
.clearAuthentication(true).invalidateHttpSession(true)
Setting up the following bean on my SecurityConfig:
#Bean
public static ServletListenerRegistrationBean httpSessionEventPublisher() {
return new ServletListenerRegistrationBean(new HttpSessionEventPublisher());
}
None of it worked. Below are my security configurations. Any clues and help is much appreciated, and I thank you in advance.
SecurityConfig.java
#Configuration
public class SecurityConfig {
#Autowired
private AuthenticationManager authenticationManager;
private final AuthSuccessHandler authSuccessHandler;
private final JwtUserDetailsService jwtUserDetailsService;
private final String secret;
SecurityConfig(AuthSuccessHandler authSuccessHandler, JwtUserDetailsService jwtUserDetailsService, #Value("${jwt.secret}") String secret) {
this.authSuccessHandler = authSuccessHandler;
this.jwtUserDetailsService = jwtUserDetailsService;
this.secret = secret;
}
#Bean
public static ServletListenerRegistrationBean httpSessionEventPublisher() {
return new ServletListenerRegistrationBean(new HttpSessionEventPublisher());
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.authorizeHttpRequests((auth) -> {
try {
auth
.requestMatchers("/user").hasRole("USER")
.requestMatchers("/admin").hasRole("ADMIN")
.requestMatchers("**/encoding-results/**", "**/encoding-results").hasAnyRole("ADMIN")
.requestMatchers("**/video/**").hasAnyRole("ADMIN")
.requestMatchers("**/codec/**").hasAnyRole("ADMIN")
.requestMatchers("**/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(authenticationFilter())
.addFilter(new JwtAuthorizationFilter(authenticationManager, jwtUserDetailsService, secret))
.logout(logout -> logout
.clearAuthentication(true)
.logoutUrl("/logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID"))
.exceptionHandling()
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.httpBasic(Customizer.withDefaults());
return http.build();
}
#Bean
public JsonObjectAuthenticationFilter authenticationFilter() throws Exception {
JsonObjectAuthenticationFilter filter = new JsonObjectAuthenticationFilter();
filter.setAuthenticationSuccessHandler(authSuccessHandler);
filter.setAuthenticationManager(authenticationManager);
return filter;
}
}
You cant logout a JWT, a JWT is valid as long as you have set the validation time to be.
OWASP has written about this problem No Built-In Token Revocation by the User
You have written a custom login that issues tokens. The server has no idea how many tokens that have been issued and which tokens that are logged in or not.
So no you can't logout the user, unless you keep some form of state of the issued tokens on the server side, as in storing the tokens in a database and invalidating them when someone does a logout.
And ofc deleting the sessionid cookie wont do anything since you are not using session cookies.
This is the exact reason why you shouldn't be using JWTs as session replacements.
Stop using JWT for sessions
Stop using JWT for sessions, part 2
JSON Web Tokens (JWT) are Dangerous for User Sessions
Why JWTs Suck as Session Tokens
Thanks for looking into my issue.
I'm practicing with Spring Boot and Spring Security. I've created a simple project with basic registration, but I can't get logging in to work. I'm trying to manually log users in in a POST: /login method, but the method is not firing. When I try to login with POST: /login, it just 302 redirects to GET /login. I'm pretty sure I've set up the security configuration and the method annotations correctly. But the post method isn't even running. (I know because I have a print statement in my post method that's not printing anything even when I start the application and create a user and log in.) How can I fix this?
(I'm not sure if the post method will actually log users in correctly, I just want to get it to run so I can figure that part out.)
The full code is here: https://github.com/Skyler827/SpacePong/tree/7530377a634f1a2fe55ce490402d947616439e72
The Security configurer method:
protected void configure (HttpSecurity http) throws Exception {
http
.csrf()
.ignoringAntMatchers("/h2/**")
.and()
.headers()
.frameOptions().sameOrigin().and()
.authorizeRequests()
.antMatchers("/login", "/register", "/h2/**").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login")
.loginPage("/login")
.failureUrl("/login?error=true")
.successForwardUrl("/")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.permitAll();
}
And the Controller which should be working but isn't:
#Controller
#RequestMapping("/login")
public class LoginController {
private final AuthenticationManager authManager;
private final PlayerService playerService;
public LoginController(AuthenticationManager authManager, PlayerService playerService) {
this.authManager = authManager;
this.playerService = playerService;
}
#GetMapping
public String getLogin(Model model) {
model.addAttribute("playerdto", new PlayerDto());
System.out.println("running getLogin()");
return "login";
}
#PostMapping
public String postLogin(#ModelAttribute("playerdto") PlayerDto playerDto, Model model) {
System.out.println("running postLogin()");
Player player = playerService.getPlayerByName(playerDto.getUsername());
if (player == null) {
model.addAttribute("error", "invalid login");
return "login";
}
UsernamePasswordAuthenticationToken authReq = new UsernamePasswordAuthenticationToken(
playerDto.getUsername(), playerDto.getPassword());
Authentication auth = authManager.authenticate(authReq);
return "redirect:/";
}
}
The Form Login sample has an example of how to configure a custom login page. Some usual issues are not using the correct form action (e.g. action="/login"), incorrectly named form inputs (e.g. name="username", name="password") and missing hidden CSRF input (e.g. type="hidden" name="_csrf" value="...").
The most important detail to remember is that when configuring .formLogin(), Spring Security's filter chain is handling the POST /login request and returning a response prior to your controller, even when .antMatchers("/login").permitAll() is used. This is why you don't see anything hitting your controller method.
While I don't recommend it in most cases, you can play around with handling the POST /login request yourself by simply omitting the .formLogin() part of the DSL. When you do this, you are responsible for setting up the SecurityContext yourself.
I was able to find a workaround solution; I still can't get the custom controller method to execute, but I can log users in, which was my goal.
This project is using Java 17, by the way.
I enabled user login to work by deleting the whole postLogin() method in the controller, and deleting all of the configurations under formLogin() in the configure(HttpSecurity http) method. Once I did this, I no longer had my custom login page, but I did have a default login page, and it did work.
I tried to add the .loginPage("/login") directive back into the configure method to specify the custom login page, but that caused the login form to go back to 302 redirecting to itself, even after my incorrect controller was deleted.
My corrected configure method:
protected void configure (HttpSecurity http) throws Exception {
http
.csrf()
.ignoringAntMatchers("/h2/**")
.and()
.headers()
.frameOptions().sameOrigin().and()
.authorizeRequests()
.antMatchers("/login", "/register", "/h2/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.permitAll();
}
My corrected controller:
#Controller
#RequestMapping("/login")
public class LoginController {
private final AuthenticationManager authManager;
private final PlayerService playerService;
public LoginController(AuthenticationManager authManager, PlayerService playerService) {
this.authManager = authManager;
this.playerService = playerService;
}
#GetMapping
public String getLogin(Model model) {
model.addAttribute("playerdto", new PlayerDto());
System.out.println("running getLogin()");
return "login";
}
}
My app using Spring Session (with Redis). And i use custom login controller, because i use external React client, not default Spring login page.
Login controller:
#PostMapping(value = "/login", consumes = MediaType.APPLICATION_JSON_VALUE)
public String login(#RequestBody LoginDataTo loginData) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
loginData.getEmail(),
loginData.getPassword());
Authentication authentication = this.authenticationManager.authenticate(token);
SecurityContextHolder
.getContext()
.setAuthentication(authentication);
return "OK";
}
Security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
http
.httpBasic().disable()
.formLogin().disable() // login form is disable, because i use external React client
.csrf().disable()
.cors().disable();
http
.logout()
.logoutUrl("/logout")
.invalidateHttpSession(true);
http
.authorizeRequests()
.antMatchers("/login").anonymous()
.antMatchers("/logout").authenticated()
.anyRequest().authenticated();
}
So... The /login endpoint's work is correct. But /logout endpoint work is incorrect. When calling /logout, it returns json:
{
"timestamp": "2021-03-30T13:45:09.142+00:00",
"status": 405,
"error": "Method Not Allowed",
"message": "",
"path": "/login"
}
Here is the request, which i using in Postman:
GET http://localhost:8080/logout
Cookie and session are deleted, that is logout's work is correct, but why is it returning this json?
I solved the problem by means of logoutSuccessHandler setting:
http
.logout()
.invalidateHttpSession(true)
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK));
Now /logout calling returns 200 OK.
All,
I have a spring boot app with MVC and REST end points. The users of the web application login using form based authentication (first configuration below). There are also REST endpoints exposed that will be invoked by external apps - these are authenticated using JWT (second configuration below).
Once the users login, I would like to invoke the REST end points (/api/**) without authenticating via JWT since the user has already logged in via form based auth. So I would basically like to use JWT authentication only if the user is not authenticated (SecurityContext does not have the auth). I am not sure how this can be achieved.
Any help is much appreciated.
Thanks
#Configuration
#Order(1)
public static class UserRole1ConfigAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(new NegatedRequestMatcher(new AntPathRequestMatcher("/api/**")))
.authorizeRequests()
.antMatchers("/userrole1/**")
.hasRole(Role.USERROLE1.name())
.and()
.formLogin()
.loginPage("/userrole1/login")
.permitAll()
.loginProcessingUrl("/userrole1/login")
.defaultSuccessUrl("/userrole1/dashboard")
.and()
.logout()
.logoutUrl("/userrole1/logout")
.logoutSuccessUrl("/userrole1/login")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll();
}
}
#Configuration
#Order(2)
public static class RestConfigAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/register")
.permitAll()
.antMatchers("/api/userrole1/**")
.hasRole(Role.USERROLE1.name())
.antMatchers("/api/userrole2/**")
.hasRole(Role.USERROLE2.name())
.addFilter(new JwtAuthenticationFilter(authenticationManager(), jwtUtil, jwtConfig))
.addFilter(new JwtAuthorizationFilter(jwtUtil, jwtConfig, userDetailsService, authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf()
.disable();
}
}
You have this already
.antMatchers("/api/register")
.permitAll()
This will allow unauthenticated requests.
What else are you looking for exactly?
This question already has an answer here:
CORS issue while making an Ajax request for oauth2 access token
(1 answer)
Closed 3 years ago.
I have problem with CORS error. I do request for Google oAuth2 and i get a CORS ERROR:
I want to get google authentication and generate a JWT token. When I do it without using the client everything is fine. When I send angular requests this is a problem with CORS. I allow all types of CORS. Why am I getting this error?
Access to XMLHttpRequest at 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=1020159669873-d9r35ssmnejud852bam87d8gqtcj5qf1.apps.googleusercontent.com&scope=openid%20profile%20email&state=8nizHP1X2z9sA8m0vqM4Lzd6VT24R15eSw5flteTywM%3D&redirect_uri=http://localhost:8080/oauth2/callback/google' (redirected from 'http://localhost:8080/oauth2/authorization/google')
from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Cross-Origin Read Blocking (CORB) blocked cross-origin response https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=1020159669873-d9r35ssmnejud852bam87d8gqtcj5qf1.apps.googleusercontent.com&scope=openid%20profile%20email&state=8nizHP1X2z9sA8m0vqM4Lzd6VT24R15eSw5flteTywM%3D&redirect_uri=http://localhost:8080/oauth2/callback/google with MIME type text/html. See https://www.chromestatus.com/feature/5629709824032768 for more details.
My Angular request:
googleLogin(): Observable<LoginResponse> {
return this.http.get<LoginResponse>
(environment.baseUrl + '/oauth2/authorization/google')
.pipe(tap(response => {
localStorage.setItem('access_token', response.accessToken);
}));
}
//...
public onGoogleLogin(): void {
this.authService.googleLogin().subscribe();
}
//...
CORS CONFIG:
#Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("HEAD", "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE")
.maxAge(MAX_AGE_SECS);
}
Security configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/",
"/favicon.ico",
"/**/*.png",
"/**/*.gif",
"/**/*.svg",
"/**/*.jpg",
"/**/*.html",
"/**/*.css",
"/**/*.js")
.permitAll()
.antMatchers("/api/v1/oauth0/**")
.permitAll()
.antMatchers("/api/v1/oauth2/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
// włączenie obslugi oauth2
.oauth2Login()
.successHandler(this.successHandler)
.redirectionEndpoint()
.baseUri("/oauth2/callback/*")
.and()
.userInfoEndpoint()
.oidcUserService(customOidcUserService);
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
Success Handler:
#Autowired
private UserRepository userRepository;
#Autowired
private JwtTokenProvider tokenProvider;
private final static String URL = "http://localhost:8080/api/v1/oauth2/authenticate";
#Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
if (response.isCommitted()) {
return; }
DefaultOidcUser oidcUser = (DefaultOidcUser) authentication.getPrincipal();
System.out.println(oidcUser);
Map attributes = oidcUser.getAttributes();
String email = attributes.get("email").toString();
User user = userRepository.findByEmail(email).orElseThrow(
() -> new ResourceNotFoundException("User", "email", email)
);
String token = tokenProvider.generateToken(user);
String redirectionUrl = UriComponentsBuilder.fromUriString(URL).queryParam("token", token)
.build().toUriString();
getRedirectStrategy().sendRedirect(request, response, redirectionUrl);
}
}
Controller:
#RestController
#RequestMapping("/api/v1/oauth2")
public class OAuth2Controller {
#GetMapping("/authenticate")
public ResponseEntity<?> authenticateUser(#RequestParam String token) {
return ResponseEntity.ok(new JwtAuthenticationResponse(token));
}
}
You cannot get the token in this example as you need to make actual redirects. There are couple of ways you could circumvent this requirement which is detailed in RFC https://www.rfc-editor.org/rfc/rfc6749#section-1.2
Initiate authorization flow in a popup and pass back the token received by server via postMessage() API provided in the browser, from the popup window back to the webapp.
Save the state, whatever it is, redirect to server which will initiate authorization flow and after token is exchanged for a grant, redirect back to the webapp with a token as a query string parameter. Then use it and restore the state.