OAuth2 with Google - CORS Error (Angular + Spring boot) [duplicate] - java

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.

Related

Spring Boot post request handler not firing

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

Why /logout calling throws "Method not allowed"?

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.

Maximum user sessions not working when user signs in Springboot/Security

I'm trying to add maximum sessions to my spring security context, however the settings are not taking effect. I've been following this code to implement JWT tokens on a spring backend using this tutorial.
I've added the relevant code I believe to the security config:
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
JwtAuthenticationFilter filter = new JwtAuthenticationFilter();
return filter;
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider());
}
http
.cors()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.authorizeRequests()
.antMatchers("/",
"/favicon.ico",
"/**/*.png",
"/**/*.gif",
"/**/*.svg",
"/**/*.jpg",
"/**/*.html",
"/**/*.css",
"/**/*.js")
.permitAll()
.antMatchers("/api/auth/**")
.permitAll()
.antMatchers("/api/user/checkUsernameAvailability", "/api/user/checkEmailAvailability")
.permitAll()
.antMatchers(HttpMethod.GET, "/api/polls/**", "/api/users/**")
.permitAll()
.antMatchers("/api/configuration/**")
.permitAll()
.anyRequest()
.authenticated()
.and().sessionManagement()
.maximumSessions(1)
.sessionRegistry(sessionRegistry());
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
However when I try and login with a user - the user can login however many times they wish.
I tried looking in the user registry to see if there were multiple user principles being created for each login but there isn't.
I also tried to step through ConcurrentSessionControlAuthenticationStrategy.onAuthentication however that doesn't seem to be being called - but I have no idea why.
Edit:
Adding the customer AuthenticationEntryPoint:
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final Logger log = LogManager.getLogger(AuthController.class);
#Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
log.error("Responding with unauthorized error. Message - {}", e.getMessage());
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Sorry, You're not authorized to access this resource.");
}
}
EDIT:
The session is setting set correctly in the session registry now. However because my login endpoint has permitAll() (I think)
ConcurrentSessionControlAuthenticationStrategy.onAuthentication() is not being called and adding the newly created session to the registry.
I believe it might be something to do with the way my CustomAuthenticationProvider is setup:
#PostMapping("/signin")
public ResponseEntity<?> authenticateUser(#Valid #RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsernameOrEmail(),
loginRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication);
return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
}
So signin is always allowing new logins as ConcurrentSessionControlAuthenticationStrategy.onAuthentication() is not being called after authentication.

Implement logout in Spring for Single Page Application

I'm using Java Web Token (JWT) for authentication in my web app. I want to create a /logout Rest endpoint that deletes the JSession cookie on the client side, invalidates the session AND does whatever else is necessary.
The front end is a SPA written in React.
I have the following configure method in the WebSecurityConfig class that extends WebSecurityConfigurerAdapter:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.and()
.authorizeRequests()
.antMatchers("/signup").permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(
new JWTAuthenticationFilter(userDetailsServiceBean()),
UsernamePasswordAuthenticationFilter.class);
}
I had the following code, but it returns 404 Not found error with the path set to /login. I want to get a HTTP response of 200 and some cleanups instead. what should I do ?
.logout().logoutUrl("/logout").logoutSuccessHandler(logoutHandler).logoutSuccessUrl("/login").invalidateHttpSession(true)
After some research I found out that if I want to use stateless Rest API design, I shouldn't use any cookie, including JSESSIONID.
Therefore I changed my code to:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(
new JWTAuthenticationFilter(userDetailsServiceBean()),
UsernamePasswordAuthenticationFilter.class);
}
Could you first try for login request like this. First add JWTLoginFilter in your WebSecurityConfig.
Pasting code from my sample project:
http.csrf().disable() // disable csrf for our requests.
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.POST,"/login").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);
You wouldn't require CORSFilter if your front end and back end are on same server.
Also find below JWTLoginFilter class
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter
{
private TokenAuthenticationService tokenAuthenticationService;
public JWTLoginFilter(String url, AuthenticationManager authenticationManager) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(authenticationManager);
tokenAuthenticationService = new TokenAuthenticationService();
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws AuthenticationException, IOException, ServletException {
AccountCredentials credentials = new ObjectMapper().readValue(httpServletRequest.getInputStream(), AccountCredentials.class);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword());
return getAuthenticationManager().authenticate(token);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication)
throws IOException, ServletException {
String name = authentication.getName();
tokenAuthenticationService.addAuthentication(response, name);
}
}
AccountCredential class is simple POJO class containing two fields username and password, which I was using to receive the request.
Also please note that UsernamePasswordAuthenticationFilter that we are using require two fields in login request 'username' and 'password'. Like {"username":"user1","password":"secret1"}

how to send refreshToken to the token endpoint?

I am using JWT Tokens with springboot-security-jwt, that have some documentation about token generation, but none about how to send the refreshToken to the token endpoint: using POST? GET? packing parameters in JSON? there are an example of the JSON pack?
NOTES
My endpoint can be used as localhost, https://localhost:8080/api/user/register, and it is working fine... return a JSON like this,
{
"refreshToken": "eyJhbGciOiJIUzUxMiJ9......Jj3hnQuMd6Im9AJhmmxaA7ILiERqHuTUf0BYCerWe4ziggvs2PiCfB_3J2f_Gc3hOqY1IgJWJRm_LrTs1UcxwQ",
"token": "eyJhbGciOiJIUzUxMiJ9......-CWgg4srJoevN7PVKOQfsQXAE3h5ySkabUb-Q-xPsEQO18KSYXWw"
}
but, how to send refreshToken to api/auth/token endpoint?
(I not see any clues at your article)
Using postman with a POST to https://localhost:8080/api/auth/token with body
{"refreshToken": "eyJhbGciOiJIUzUxMiJ9.......Jj3hnQuMd6Im9AJhmmxaA7ILiERqHuTUf0BYCerWe4ziggvs2PiCfB_3J2f_Gc3hOqY1IgJWJRm_LrTs1UcxwQ",
}
I have response
{
"errorCode": 10,
"message": "Authentication failed",
"status": 401,
"timestamp": 1481753363749
}
Perhaps other problem...
(edit with more clues about my implementation)
My configs at my equivalent WebSecurityConfig.java
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public static final String JWT_TOKEN_HEADER_PARAM = "Authorization";
public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/login";
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/auth/**";
public static final String TOKEN_REFRESH_ENTRY_POINT = "/token";
...
}
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // We don't need CSRF for JWT based authentication
.exceptionHandling()
.authenticationEntryPoint(this.authenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point
.antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point
.and()
.authorizeRequests()
.antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points
.and()
.addFilterBefore(buildAjaxLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
}
so, perhaps (?) no endpoint /token exist (!) with this changes.
... Where the springboot-security-jwt /token implementation? to check it (or a kind of "health endpoint test")...
PS: the spected endpoint "/api/token" and any other "api/Mammy" returns 405 (Method Not Allowed), and testing as authorized endpoint, "auth/token" or "api/Mammy", returns 401 and error 10 (Authentication failed).

Categories

Resources