How to set up login page in Spring Boot 2 with WebFlux? - spring-boot

I have created Spring Boot 2 WebFlux application (based on Spring Cloud Gateway project) and now try to configure custom login page instead of standard:
#Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
return http.httpBasic().and()
.authorizeExchange()
.anyExchange().authenticated()
.and()
.formLogin().loginPage("/login")
.and()
.csrf().disable()
.build();
}
I tried to use Thymeleaf to render this page by means of login html page creation and controller setup:
#Controller
public class LoginController {
#RequestMapping(value = "/login")
public Mono<String> getLoginPage() {
return Mono.just("/templates/login.html");
}
}
But it don't working. Can anybody explain how to do this and should I using Thymeleaf at all? Maybe this have already implemented and is on GitHub?

Try
#Controller
public class LoginController {
#GetMapping("/login")
public String getLoginPage() {
// assuming that Thymeleaf is present
// and a valid src/main/resources/templates/login.html template
return "login";
}
}

Related

How to request authentication to all routes except welcome which has to be the login page in Spring Boot

I want to request authentication to all available routes except one "/welcome" which has to be the login page too!
I'm using Spring Boot Security and my SecurityFilterChain is coded like this:
#Configuration
public class AppConfig {
#Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/welcome").permitAll()
.requestMatchers("/**").authenticated()
.and().formLogin().loginPage("/welcome")
.and().httpBasic();
return http.build();
}
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
I want the user to be redirected to the welcome url/page when not authenticated. And when authenticated to be redirected to the root "/". However with this configuration the server keeps telling me ERR_TOO_MANY_REDIRECTS
Where am I doing wrong?
How to allow public access only to the "/welcome" url and not the rest?

Securing Spring Cloud Gateway with Spring Security

I am struggling with configuring security for my Spring Cloud Gateway service.
For now i have configured in my api-gateway just one route to user service /api/v1/users. Requests are correctly routed to user service untill I add Spring Security to the dependescies.
Even with that simple config, that should allow all traffic, I am still getting 401 Unathorized response:
#Configuration
#EnableWebFluxSecurity
class SecurityConfiguration {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity serverHttpSecurity) {
return serverHttpSecurity
.authorizeExchange()
.anyExchange().permitAll().and()
.csrf().disable()
.build();
}
}
What am I doing wrong?
You need to create user to do that. See the sample attached in below. I am using in-memory user to authenticate. Note in-memory user is just for testing purpose only.
#Configuration
public class InMemoryUserSecurityAdapter {
#Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/school-library-service/**").authenticated()
.and().authenticationManager(reactiveAuthenticationManager())
.authorizeExchange().anyExchange().permitAll().and()
.httpBasic().and()
.build();
}
#Bean
ReactiveAuthenticationManager reactiveAuthenticationManager(){
return new UserDetailsRepositoryReactiveAuthenticationManager(getInMemoryUserDetails());
}
#Bean
public MapReactiveUserDetailsService getInMemoryUserDetails() {
UserDetails admin = User.withDefaultPasswordEncoder().username("admin1").password("password")
.roles("ADMIN")
.build();
return new MapReactiveUserDetailsService(admin);
}
}
https://github.com/DeepuGeorgeJacob/school-management/blob/main/security/in-memory-user-security/src/main/java/com/school/management/config/InMemoryUserSecurityAdapter.java
Happy coding :)

How to redirect, with Spring Security, a logged user to his front page and a non logged one to a different one

I'm doing a Spring web-application project as a final project for school. We use SpringBoot with Hibernate and an H2 database. In order to authenticate and authorize, we use Spring Security. We also use the model View-Controller with repositories, services, and controllers.
I have an initial front page (with the URL "/") that is shown when you open the project. Once you log-in, you're redirected to another front page with the URL ("/portadaUsuario")(I'm from Spain so there's a lot of names in Spanish). But, if you somehow end up in the ("/") URL after you've logged in, you're shown the same front page that is shown to non-logged users with the sign-up and log-in options, which is obviously wrong.
I know that I can show different HTML elements with spring security tags and elements, but I've already built my project around having two different front-pages and I would like to keep it that way if possible. If it isn't achievable, please let me know how should I proceed to show different elements in the same HTML file.
Here are the methods of my WellcomeController
#Controller
public class PortadaController {
#GetMapping({"/"})
public String mostrarPortada() {
return "portada";
}
#GetMapping("/portadaUser")
public String mostrarPortadaUsuario() {
return "/usuario/portada";
}
}
Here are the methods that authenticate the users
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/css/**","/js/**","/webjars/**", "/h2-console/**", "/", "/newUser/**").permitAll()
.antMatchers("/admin/**").hasAnyRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.successHandler(customSuccessHandler)
.defaultSuccessUrl("/portadaUser")
.and()
.logout()
.logoutUrl("/logout")
.permitAll()
.logoutSuccessUrl("/")
.and()
.exceptionHandling()
.accessDeniedPage("/acceso-denegado");
http.csrf().disable();
http.headers().frameOptions().disable();
}
What I want is the application detecting when a logged user is requesting the "/" URL, and for it to be redirected to /portadaUser instead.
Assuming, you have two pages, one for signed in (signed-in-home) and another for not signed in(not-signed-in-home), you code something like this -
#GetMapping("/")
public String index(Principal principal, Model model) {
return principal != null ? "signed-in-home" : "not-signed-in-home";
}
Read more about principal here- https://www.baeldung.com/get-user-in-spring-security
Tested in jsp + Spring 4.3.13 + Spring Security 4.2.11
1.checked in jsp
"<%# taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>"
<script>
<sec:authorize access="hasRole('ADMIN')">
location.replace('/yourpage')
</sec:authorize>
</script>
2. checked in controller
SpringSecurityUtil.class
public class SpringSecurityUtil {
public static boolean hasRole(String role) {
Collection<GrantedAuthority> authorities = (Collection<GrantedAuthority>) SecurityContextHolder.getContext().getAuthentication().getAuthorities();
boolean hasRole = false;
for (GrantedAuthority authority : authorities) {
if(authority.getAuthority().equals(role)) {
hasRole = true;
break;
}
}
return hasRole;
}
}
HomeController.class
#Controller
public class HomeController {
#RequestMapping(value = "/", method = RequestMethod.GET)
public String home(HttpServletResponse resp) {
if(SpringSecurityUtil.hasRole("ADMIN"))
//if has role send your page
else
//if has not role send your page
}
}
3. add antMatchers in your Spring Security Config class.
antMatchers("/").hasAnyRole("ADMIN")
4. use #PreAuthority
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
}
HomeController.class
#Controller
public class HomeController {
#PreAuthorize("hasAuthority('ADMIN')")
#RequestMapping(value = "/", method = RequestMethod.GET)
public String home() {
//If this code starts, you have ADMIN privileges.
}
}
see more Spring Security Method Security

Custom login page in Spring Security 5 using oauth2 returns null

I am developing custom login page in Spring Security 5 using oauth2. So I have customized settings:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login().loginPage("/login/oauth2").permitAll();
}
and creating controller with #RequestMapping("/login/oauth2"):
#GetMapping("/")
public String index() {
return "index";
}
#GetMapping("/login/oauth2")
public String login(Model model, OAuth2AuthenticationToken authentication) {
return "login";
}
The login.html page is a regular form which redirect to login method from controller:
<h1>Logowanie</h1>
<a>ZALOGUJ</a>
<a class="btn" href="/login/oauth2/code/google">Login</a>
With this configuration OAuth2AuthenticationToken authentication is null and therefore authentication can't be applied. With default Spring Security 5 configuration everything works fine. The example on which I based is described here: https://docs.spring.io/spring-security/site/docs/5.0.0.RELEASE/reference/htmlsingle/#oauth2login-advanced-login-page; section 31.1 OAuth 2.0 Login Page.
In my app to work I had to create my custom WebMvc configuration:
#Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
}
Then in WebSecurityConfig:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.loginPage("/login")
.permitAll()
.and().csrf().disable()
.logout().permitAll();
}
I think in this case You don't need custom controller.
I wrote a blog post about silent token refresh in implicit flow, but there You will find full working app with custom login page:
https://regulargeek.blogspot.com/2018/05/angular-5-springboot-2-oauth2-and.html

denyAll() redirect to login url but I need another url

This code blocked properly every request which is finished on XHTML but I would like redirect the request to url like "/spring/denied" not "/spring/login" which is setted on method formLogic()
http
.formLogin()
.loginPage("/spring/login")
.loginProcessingUrl("/spring/loginProcess")
.defaultSuccessUrl("/spring/main")
.failureUrl("/spring/login?login_error=1")
.and()
.logout()
.logoutUrl("/spring/logout")
.logoutSuccessUrl("/spring/logoutSuccess")
.and()
.authorizeRequests().antMatchers("/spring/**/*.xhtml").denyAll()
.and()
// Disable CSRF (won't work with JSF) but ensure last HTTP POST request is saved
// See https://jira.springsource.org/browse/SEC-2498
.csrf().disable()
.requestCache()
.requestCache(new HttpSessionRequestCache());
So, I think there will be theses possible scenes:
Someone intent to access to any real XHTML file directly (/main/index.xhtml): Behavior: Request blocked and redirected to denied url, if someone wish it , he must interact using right flow definition (p.e. /main, /groups....)
Someone intent to access to secured url without right permissions or anonymous (p.e. /admin, /authenticate...): Behavior: Spring security intercept request and redirect to login url
Some intent to access to secured url with right permissions (p.e /admin, /authenticate....): Behavior: Spring security grants access and spring web flow make its task redirecting properly
Someone intent to access unknown url (p.e. /ImAUnluckyGuyAndThisUrlIsUnreal): Behavior: Spring webflow intercept request and redirect to last flow known.
Using XML configuration above cases are right. Furthermore I used spring webflow 2.3 instead 2.4.0RC1 and Annotations configurations
Case 1: Adding this code on web.xml, I don't know how replace fot annotations configurations
<security-constraint>
<display-name>Restrict direct access to XHTML access</display-name>
<web-resource-collection>
<web-resource-name>XHTML</web-resource-name>
<url-pattern>*.xhtml</url-pattern>
</web-resource-collection>
<auth-constraint />
</security-constraint>
Case 4: Adding this code on a abstract flow definition, I don't know if doesn't work on Spring webflow 2.4.0RC1 or it's a annotations configuration problem.
<global-transitions>
<transition on-exception="org.springframework.webflow.engine.NoMatchingTransitionException" to="handlingViewState">
<evaluate expression="handlingBean.handle(flowExecutionException)"> </evaluate>
</transition>
</global-transitions>
Case 2 and 3: These are not problematic. if user is authenticated and doesn't got permissions is redirected using .exceptionHandling().accessDeniedPage("/spring/denied") or user is anonymous is redirected to loginPage()
Webflow configuration
#Bean
public FlowExecutor flowExecutor() {
return getFlowExecutorBuilder(flowRegistry())
.addFlowExecutionListener(new FlowFacesContextLifecycleListener())
.addFlowExecutionListener(new SecurityFlowExecutionListener())
.build();
}
#Bean
public FlowDefinitionRegistry flowRegistry() {
return getFlowDefinitionRegistryBuilder(flowBuilderServices())
.setBasePath("/WEB-INF/flows")
.addFlowLocationPattern("/**/*-flow.xml")
.build();
}
#Bean
public FlowBuilderServices flowBuilderServices() {
return getFlowBuilderServicesBuilder().setDevelopmentMode(true).build();
}
}
MVC configuration
#Autowired
private WebFlowConfig webFlowConfig;
#Bean
public FlowHandlerMapping flowHandlerMapping() {
FlowHandlerMapping mapping = new FlowHandlerMapping();
mapping.setOrder(1);
mapping.setFlowRegistry(this.webFlowConfig.flowRegistry());
/* If no flow matches, map the path to a view, e.g. "/intro" maps to a view named "intro" */
mapping.setDefaultHandler(new UrlFilenameViewController());
return mapping;
}
#Bean
public FlowHandlerAdapter flowHandlerAdapter() {
JsfFlowHandlerAdapter adapter = new JsfFlowHandlerAdapter();
adapter.setFlowExecutor(this.webFlowConfig.flowExecutor());
return adapter;
}
#Bean
public UrlBasedViewResolver faceletsViewResolver() {
UrlBasedViewResolver resolver = new UrlBasedViewResolver();
resolver.setViewClass(JsfView.class);
resolver.setPrefix("/WEB-INF/");
resolver.setSuffix(".xhtml");
return resolver;
}
#Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
What you need is multiple <http> [HttpSecurity] configuration and you need to provide a custom AuthenticationEntryPoint implementation.
Below is the HttpSecurity configuration for case 1. (I hope you can come-up with a configuration for case 2 & 3 and you can use the one you already have.)
#Configuration
#Order(1)
public static class XHTMLAccessDenyWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/spring/**/*.xhtml")
.exceptionHandling().authenticationEntryPoint(new AccessDenyEntryPoint()).and()
.authorizeRequests().antMatchers("/spring/**/*.xhtml").denyAll();
}
}
Note: The order of the above security configuration should be higher than the security configuration for case 2 & 3; therefore, #Order is used.
Custom AuthenticationEntryPoint implementation would simply redirect the request to /spring/deny page as below
public class AccessDenyEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
request.getRequestDispatcher("/spring/denied").forward(request, response);
}
}

Resources