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";
}
}
Related
I have this error related to Spring security that I cannot fix, and it is happening only though the browser. When I try to replicate it through postman, it suddenly works and everything's fine.
I authorized unauthenticated requests for /login, and authenticated requests to the rest of the API.
Therefore when I make a call to /login, it does let me authenticate.
But, when I make a call to any of my API endpoints that is not /login, and I'm already authenticated, my backend returns this strange error:
I don't really know what this error means.
However, when I do the same process through Postman, it works and the endpoint returns what it is supposed to return.
Login API call:
Another API call that is not login:
As you can see, through Postman it does work well.
But doing the exact same through the Angular UI I'm developing leads to the former error.
My Spring security configuration:
#Configuration
#AllArgsConstructor
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String FRONTEND_URL = "http://localhost:4200/";
private final UserService userService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/v*/registration/**", "/login-status/**")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.loginPage(FRONTEND_URL + "/login")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/login-status/success", true)
.failureUrl("/login-status/failure")
.and()
.logout()
.permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(bCryptPasswordEncoder);
provider.setUserDetailsService(userService);
return provider;
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList(FRONTEND_URL));
configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
I hope you could help me cause I'm really stuck with this. I'm a front-end dev who is trying to find my ways to develop a backend, just to learn, but man, shits hard. Thank you very much, and I hope my bad English didn't hurt your eyes too much. Thank you for your help.
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.
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.
Preconditions
I have two Java Spring applications(App 'A' and App 'B') that were created via JHipster(monolithic application). Both applications uses keycloak for authentication/authorization.
Both applications have an angular frontend and support login via ouath (spring-security). Here ist my SecurityConfiguration of Application A and B:
#Configuration
#Import(SecurityProblemSupport.class)
#EnableOAuth2Sso
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CorsFilter corsFilter;
private final SecurityProblemSupport problemSupport;
public SecurityConfiguration(CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
this.corsFilter = corsFilter;
this.problemSupport = problemSupport;
}
#Bean
public AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler() {
return new AjaxLogoutSuccessHandler();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/test/**")
.antMatchers("/h2-console/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.addFilterBefore(corsFilter, CsrfFilter.class)
.exceptionHandling()
.authenticationEntryPoint(problemSupport)
.accessDeniedHandler(problemSupport)
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler())
.permitAll()
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers("/api/profile-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/v2/api-docs/**").permitAll()
.antMatchers("/swagger-resources/configuration/ui").permitAll()
.antMatchers("/swagger-ui/index.html").hasAuthority(AuthoritiesConstants.ADMIN);
}
}
In App B i also have an ResourceServerConfiguration. This checks if the header contains an "Authorization" key. If true, the user can login via JWT(Bearer Authentication). I tested this via Postman and it works fine:
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(final HttpSecurity http) throws Exception {
http
.requestMatcher(new RequestHeaderRequestMatcher("Authorization")).authorizeRequests()
.anyRequest().authenticated();
}
}
Further more both apps are in the same keycloak realm and have the access-type "public".
Problem:
Now i want to call an endpoint of App B via an Spring RestTemplate from App A. The problem is, that i do not have an access_token that i can put in my rest request/restTemplate. When i look in my request that is send from my frontend, i only got an JSESSIONID. There is no access_token/JWT in the header.
Question
Is there a way to get the access_token of the current user out of the JSESSIONID/the HttpSession or the spring security context? Do i need something like a Tokenstore where i store every token that comes from keycloak?
Did anyone else have similar problems or any idea how i could solve that problem?
After some research it turns out that the problem lies within the generated jhipster code.
I followed the authentication process in the application and saw, that there was a call to the /account endpoint directly after authentication, where the user information were retrieved. The call is triggerd by the frontend. First time this endpoint is called, there is a principal with a bearer token available. Within the /account endpoint, a call to the userService with the principal object is performed. More precisley
getUserFromAuthentication(OAuth2Authentication authentication)
is called. Within this method there is a part that replaces the OAuth2Authentication with a new UsernamePasswordAuthenticationToken and inserts it into the SecurityContext:
UsernamePasswordAuthenticationToken token = getToken(details, user,
grantedAuthorities);
authentication = new OAuth2Authentication(authentication.getOAuth2Request(), token);
SecurityContextHolder.getContext().setAuthentication(authentication);
So after that, the access_token is lost. I am not quite sure, why it was replaced with the new OAuth2Authentication, but i tend to extend this part and keep the access_token in my securityContext for further restcalls.