I am currently building an Application with Springboot on the back- and Reactjs on the frontend.
The springboot security function very well. the backend runs on http://localhost:8080/ and Reactjs (frontend) on http://localhost:3000/.
How can i get the default springboot loginpage on my Reactjs login Page.
here is my SecurityConfiguration class
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login/*").permitAll()
.antMatchers("/createNewUser/*").hasAnyRole("Admin")
.antMatchers("/deleteUser/*").hasAnyRole("Admin")
.antMatchers("/test1/**").hasAnyRole("Admin","RegularUser")
.antMatchers("/test5/**").hasAnyRole("Admin")
.antMatchers("/test2/**").authenticated()
.antMatchers("/test4/**").authenticated()
// .antMatchers("/test/**").hasAnyRole("ADMIN")
.and().formLogin()
.loginProcessingUrl("/login/process")
.successHandler(customLoginSuccessHandler)
.and().csrf().disable()
.logout(logout -> logout
.permitAll()
.logoutUrl("/logout")
.logoutSuccessHandler((request, response, authentication) -> {
response.setStatus(HttpServletResponse.SC_OK);
}
));
}
}
For other URL not secured, i can access it on my React application with no problem.
but when i call for example http://localhost:8080/test2/ in Reactjs with Axios i get a 403 error(acces forbiden).
but on the browser when i call the same url i can authenticate myself and access the ressources needed.
So to conclude the two application works perfectly but there is no connection between them.
I finally got an answer to my question by doing many researches.
I needed to implement JWT(Json Web Token)
and then get rid of, because the authentification is done through an Url
.and().formLogin()
.loginProcessingUrl("/login/process")
.successHandler(customLoginSuccessHandler)
.and().csrf().disable()
.logout(logout -> logout
.permitAll()
.logoutUrl("/logout")
.logoutSuccessHandler((request, response, authentication) -> {
response.setStatus(HttpServletResponse.SC_OK);
}
));
Related
I'm using React JS as client and my Authorization server uses new OAuth2 with PKCE. The whole flow works but during authentication process, spring security returns me default login page. I have made a change to get the custom React JS login page but the question is, how to handle the submit of the login page from React JS? I am using PKCE so once I submit login page, I need to get the 'code' back in response which I need to further pass (along with verification code) to request JWT Token. Application details are given below,
I got a separate Authorization server OAuth2 using <artifactId>spring-security-oauth2-authorization-server</artifactId>. Authorization server issues JWT upon successful authentication using PKCE. I'm using PKCE because front end is React JS and I do not wish to save client secret at Front end. This is how the flow works (without explaining minute details),
UI calls ${AUTHORIZATION_SERVER_BASE_URL}/oauth2/authorize?response_type=code&client_id=${CLIENT_ID}&scope=openid&redirect_uri=${REDIRECT_URI}&code_challenge=${codeChallenge}&code_challenge_method=S256;
Spring gives back default login page
User enters credentials
Upon successful authentication, spring returns 'code' to the specified 'REDIRECT_URI'
I make Axios POST request passing 'code' and original code_verifier to get JWT Token eg endpoint post(${AUTHORIZATION_SERVER_BASE_URL}/oauth2/token, paramsEncoded, axiosConfig)
AuthorizationServerConfig class is pasted below.
#Configuration
#AllArgsConstructor
public class AuthorizationServerConfig {
private final CORSCustomizer corsCustomizer;
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
corsCustomizer.corsCustomizer(http);
return http.formLogin().loginPage("http://127.0.0.1:3000/login").and().build();
//return http.formLogin(Customizer.withDefaults()).build();
}
#Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("client")
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:3000/authorized")
.scope(OidcScopes.OPENID)
.clientSettings(ClientSettings.builder()
.requireAuthorizationConsent(true).build())
.tokenSettings(TokenSettings.builder()
.refreshTokenTimeToLive(Duration.ofHours(10))
.build())
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
#Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer("http://auth-server:3001")
.build();
}
#Bean
public JWKSource<SecurityContext> jwkSource() throws NoSuchAlgorithmException {
RSAKey rsaKey = JwksKeys.generateRSAKey();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
#Bean
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
return context -> {
if (context.getTokenType() == OAuth2TokenType.ACCESS_TOKEN) {
Authentication principal = context.getPrincipal();
Set<String> authorities = principal.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
context.getClaims().claim("roles", authorities);
}
};
}
}
My objective is to access spring security protected API from a react js application after authentication.
Spring boot application hosted at http://myserver:8080
React JS application is hosted at http://myserver:3000
I am able to authenticate and access the APIs using curl as follows:
Access login url with credentials . Extract jsessionid token from response header.
Access end url with jsessionid token.
$ curl -i -X POST login_url --data 'username=myusername&password=mypassword'
$ curl end_url -H 'Cookie: JSESSIONID=session_token'
I am trying to replicate the same through React JS Application.
Even though JSESSIONID Cookie is present in response header (verified through curl , and browser dev tools) but axios response header is not able to capture it.
I understand that "Set-Cookie" header in JavaScript code will not work by default. As discussed in this question React Axios, Can't read the Set-Cookie header on axios response
Kindly help with modification required in code to achieve the same. OR suggest alternate way to achieve the objective.
Thanks.
Client side code is as follows:
const onSubmitAuthenticateButton = (e) => {
e.preventDefault();
const loginUrl = 'http://myserver:8080/login';
axios.defaults.withCredentials = true;
axios.post(loginUrl, { username, password})
.then(res => console.log(res.headers))
.catch(err => console.log(err.message));
}
In Spring Secuirty configuration, csrf is disabled and cors allowed origin for "http://myserver:3000".
WebSecurityConfig class
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
private CustomAuthenticationProvider customAuthProvider;
public WebSecurityConfig(CustomAuthenticationProvider customAuthProvider) {
super();
this.customAuthProvider = customAuthProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthProvider);
}
}
WebMvcConfig class
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final long MAX_AGE_SECS = 3600;
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://myserver:3000")
.allowedMethods("GET", "POST")
.exposedHeaders("Set-Cookie")
.maxAge(MAX_AGE_SECS)
.allowCredentials(true);
}
}
I have achieved the objective through alternate way.
Instead of session based authentication, i am now using stateless authentication. Upon successful authentication a jwt token is returned as response. Subsequent API call, the jwt token is attached as payload. The application checks for the validity of token before processing the API call request.
Hi all I'm new to spring boot and react, I'm working on the simple login app using react js and spring boot,
whenever I try to navigate to a different API call (e.g logout, welcome) I get the following message
Failed to authorize filter invocation [GET /welcome] with attributes [authenticated]
I think this is something with WebSecurityConfigurerAdapter
looking for a proper solution
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().sessionManagement().sessionFixation().migrateSession().and()
//.addFilterAfter(new AuthenticationFilter(), UsernamePasswordAuthenticationFilter.class).csrf().disable()
.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and().formLogin().loginPage("/login").and()
.logout()
.logoutUrl("/logout").invalidateHttpSession(true).deleteCookies().clearAuthentication(true)
.permitAll()
.and()
.exceptionHandling().accessDeniedPage("/403").and().httpBasic();
}
handleDashboard() {
axios.get("http://localhost:8080/welcome",{ withCredentials: true }).then(res => {
if (res.data === "success") {
this.props.history.push("/");
} else {
alert("Authentication failure");
}
});
}
WebSecurityConfig
log output
After playing around with spring security & spring boot I was able to find the root cause and fix it, just enable the CORS at the main class file(Global CORS configuration) and will fix the above issue.
ps: even enabling CORS at its method level was not recognized properly, need to add it in the main class
#Bean
public FilterRegistrationBean<CorsFilter> simpleCorsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
config.setAllowedMethods(Collections.singletonList("*"));
config.setAllowedHeaders(Collections.singletonList("*"));
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
I had a similar issue. Turn out my custom filters had too high precedence in the #Order annotation and it caused interference with spring security filters. When changing to a lower precedence everything worker fine again.
Just wanted to put it out there.
We have a fully-working back-end login POST service, implemented using Spring Security, along with Spring Boot and Spring Session. A user needs to be logged-in in order to access other services. The login operation works, and so does the mechanism to restrict/allow access to the other services. This has been tested with Postman, which is "smart enough" to keep the session cookie on successive requests.
Now, we are trying to build the client on React. When using the browser's debug we can see the session cookie is sent in the response header without problems. We were trying to get the session cookie from the header and store it for successive requests, but it doesn't work. When investigating we learnt we are not meant to read the response header from the code, as explained here and here.
Our login operation should redirect to /customer/home, which works in Postman but not on our application. The behaviour we get with this is a 403 Forbidden, and the way we assess it is because the cookie is not set when redirecting, and hence the second operation (GET /customer/home) fails and returns 403. Is our understanding correct? However, the browser does not seem to keep the session cookie automatically. How are we supposed to maintain the session for subsequent requests if the cookie is not set automatically, and we are not supposed to read it manually? Are we supposed to NOT use cookies for this purpose, and issue authentication tokens instead?
We are obviously misunderstanding or missing something. Any pointers please?
Our WebSecurityConfigurerAdapter:
#EnableWebSecurity
#Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationProviderService authenticationProviderService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/customer/register").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.formLogin()
.permitAll()
.defaultSuccessUrl("/customer/home", false)
.and()
.logout()
.permitAll()
.and()
.httpBasic();
http.csrf().disable();
}
//[ . . . ]
}
Our client trying to do a POST:
const mw = store => next => action => {
if(action.type == 'SUBMIT_LOGIN_USER') {
var payload = {
username: action.user.username,
password: action.user.password
};
// Build formData object.
let formData = new FormData();
formData.append('username', action.user.username);
formData.append('password', action.user.password);
return fetch('http://192.168.0.34:8080/login', {
method: 'POST',
body: formData
}).then(
r => (r)
)
.then(function(response) {
console.log(document.cookie) //empty
console.log(response.headers.get('Set-Cookie')) //null
next(action)
})
.catch(function(err) {
console.info(err);
});
} else {
next(action)
}
}
Using JWT (JSON Web Tokens) is a great way to implement security on single page applications like React.
If you're going with the JWT approach it would be efficient to use a package like axios to for http requests from the client side. Axios allows you to easily add an authorization token to all requests without hassle.
Even if you're not using JWT try using axios to send authorization tokens efficiently.
So, I have written a REST API using Spring(java), which is secured using Basic Authentication and also responsible for handling the social-logins. Below is the configuration for facebook login.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//autowired
#Bean
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Bean
public ProviderSignInController providerSignInController() {
return new ProviderSignInController(connectionFactoryLocator(), usersConnectionRepository(),
new FacebookSignInAdapter());
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(NoOpPasswordEncoder.getInstance());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login*", "/signin/**", "/signup/**").permitAll()
.anyRequest().authenticated()
.and().httpBasic()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().csrf().disable()
;
}
#Bean
public ConnectionFactoryLocator connectionFactoryLocator() {
ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(environment.getProperty("facebook.clientId"),
environment.getProperty("facebook.clientSecret")));
return registry;
}
#Bean
public UsersConnectionRepository usersConnectionRepository() {
return new InMemoryUsersConnectionRepository(connectionFactoryLocator());
}
}
Dependency used:
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-facebook</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
Now my frontend is written using React.js and running at https://localhost:3000. It has a button Signin using Facebook which sends a POST request to https://localhost:8443/signin/facebook. /signin/facebook is the URL provided by Spring-Security. The REST API returns a redirect url, to which the browser is blocking for CORS issue. I understand the CORS and have configured at my backend(that's why frontend is able to send request).
Access to XMLHttpRequest at 'https://www.facebook.com/v2.5/dialog/oauth?client_id=2198xxxxxx91&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8443%2Fsignin%2Ffacebook&state=xxxx' (redirected from 'https://localhost:8443/signin/facebook') from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
So, what is the resolution to this? I googled it, and read somewhere that the CORS is handled by backend, not the frontend. But the backend is already handling the CORS. what should be the configuration for this?