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

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

Related

display spring security authentication object when SessionCreationPolicy.STATELESS

#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
MyUserDetailsService myUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/index").permitAll();
http.authorizeRequests().antMatchers("/main").permitAll();
http.formLogin().loginPage("/login").permitAll().successHandler(successHandler());
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // Session is STATELESS
}
I set spring security sessionpolicy to STATLESS
because I'm using JWT so that STATLESS would be better
but STATELESS cause one problem
it's impossible to dispaly authentication object in thymeleaf
<h1>[[${#authentication }]]</h1>
if I changed session policy I could display authentication object
but but that's not what i want
in short
can i use authentication object with thymeleaf when spring's session policy is STATELESS
Form based log in requires a session, so marking as stateless would mean that the user is not available. Likely you can see the page because it is marked as permitAll which means no user is necessary to see it.
To fix this, you can switch to a form of authentication that is stateless too (i.e. it is included in every request). For example:
// #formatter:off
http
.authorizeRequests()
.mvcMatchers("/index", "/main").permitAll()
.and()
.httpBasic()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// #formatter:on
I'm also not sure about the syntax the themleaf template is using. For me, I use something like this:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Test</title>
</head>
<body>
<h1 th:text="${authentication?.name}"></h1>
</body>
</html>
Then I use the following to expose the Authentication as a model attribute:
#Controller
public class IndexController {
#GetMapping("/")
String index() {
return "index";
}
#ModelAttribute
Authentication authentication(Authentication authentication) {
return authentication;
}
}
I have a test that validates it works
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class DemoApplicationTests {
#Autowired
TestRestTemplate rest;
#Test
void indexWhenAnonymous() throws Exception{
ResponseEntity<String> result = rest.exchange(RequestEntity.get(URI.create("/")).build(), String.class);
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(result.getBody()).doesNotContain("user");
}
#Test
void indexWhenAuthenticated() throws Exception{
ResponseEntity<String> result = rest.exchange(RequestEntity.get(URI.create("/")).headers(h -> h.setBasicAuth("user", "password")).build(), String.class);
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(result.getBody()).contains("user");
}
}
You can find the complete sample at https://github.com/rwinch/spring-security-sample/tree/display-auth-stateless-thymeleaf which allows log in with the username user and password password.

Spring Boot 2 Security downloading font file upon login

I've setup a spring boot 2 application with a login form, however, when you login, instead of redirecting to /admin like it's supposed to, it downloads a font file referenced by the stylesheet via an #import.
Here is my security setup;
#Configuration
#EnableWebSecurity()
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserService userService;
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
// These pages don't require the user to be logged in
http.authorizeRequests()
.antMatchers("/", "/login", "/logout", "/report/**").permitAll()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.anyRequest().authenticated();
// When the user has logged in as XX.
// But access a page that requires role YY,
// AccessDeniedException will be thrown.
http.authorizeRequests().and().exceptionHandling().accessDeniedPage("/403");
// Config for Login Form
http.authorizeRequests().and().formLogin()//
// Submit URL of login page.
.loginProcessingUrl("/j_spring_security_check") // Submit URL
.loginPage("/login")//
.defaultSuccessUrl("/admin")//
.failureUrl("/login?error=true")//
.usernameParameter("username")//
.passwordParameter("password")
// Config for Logout Page
.and().logout().logoutUrl("/logout").logoutSuccessUrl("/login?logout=true");
}
}
Where am I going wrong? From what I can see, I'm enabling access to Spring resources that are stored in the static folder.
I figured this one out, I read the code that allows access to resources and noticed it said 'atCommonLocations', and guess this adds access to folders such as css, js, img, images etc. I had fonts in a folder labelled webfonts, so I updated my security configuration;
http.authorizeRequests()
.antMatchers("/", "/login", "/logout", "/report/**", "/webfonts/**").permitAll()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.anyRequest().authenticated();

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

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

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

Spring Security Ant Matchers for home root / - spring boot 1.4.2 release version

I have a requirement to display custom based login form(/auth/login.html) through spring security when user hits http://localhost:8080. If user login successfully with admin role, redirect the user to /admin/adminsuccess.html. Once admin user redirected to /adminsuccess.html, I need to permit admin user to access other pages e.g. (/admin/assetallocate.html,/admin/assetdeallocate.html..)If user not logging in with admin role, show the same login page with errors..
Below are my code:
#Configuration
public class AssetWebConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("auth/login");
registry.addViewController("/admin/adminsuccess").setViewName("admin/auth-success");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
}
#Configuration
#EnableWebSecurity
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
.antMatchers("/**").access("hasRole('ROLE_ADMIN')")
.and()
.formLogin()
.loginPage("/login")
.usernameParameter("username")
.passwordParameter("password")
.defaultSuccessUrl("/admin/adminsuccess")
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.permitAll()
.and()
.csrf().disable();
}
}
/auth/login.html
<form name="loginform" th:action="#{/login}" method="post" class="form-signin">
Above code whatever i written not working as expected. It could be the issue with ant matches pattern. Please guide.
Update:
When i hit "http://localhost:8080", custom login page is displaying now. But when i enter correct credentials, it's not re-directing to view name '/admin/auth-success.html' based on AssetWebConfig.java configuration. Below is the current response if i enter correct credentials.
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Wed Nov 23 11:42:59 IST 2016
There was an unexpected error (type=Not Found, status=404).
No message available
Yes. Issue is with your ant matchers.
As per my understanding, when you say anyRequest.permitAll , it doesn't comply with antMatchers("/admin/*").access("hasRole('ROLE_ADMIN')")
because you're telling web security to allow every request to go through without authorization.
Change as below,
http.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/admin/**").access("hasRole('ADMIN')")
.and().formLogin().loginPage("/login")
https://github.com/satya-j/rab/blob/master/src/main/java/com/satya/rab/config/WebSecurityConfig.java - refer to this, its my repo where I had earlier tried out with spring security.
EDIT:
Here is an update
WebSecurityConfig
.antMatchers("/login").permitAll()
.antMatchers("/admin/**").access("hasRole('ADMIN')")
.antMatchers("/**").access("hasRole('USER')")
.and()
.formLogin().loginPage("/login")
.usernameParameter("username")
.passwordParameter("password")
.defaultSuccessUrl("/index")
.failureUrl("/login?error");
You can use a authentication provider of your choice to set roles based on the user.
CustomeAuthenticationProvider
#Component("authProvider")
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
String username = auth.getName();
String password = auth.getCredentials().toString();
if(username.equals("user") && password.equals("user")) {
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
return new UsernamePasswordAuthenticationToken(username, password, grantedAuths);
} else if(username.equals("admin") && password.equals("admin")) {
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
return new UsernamePasswordAuthenticationToken(username, password, grantedAuths);
} else {
throw new CustomException("Unable to auth against third party systems");
}
}
#Override
public boolean supports(Class<?> auth) {
return auth.equals(UsernamePasswordAuthenticationToken.class);
}
I've used a Custom authentication. As I'm playing with spring security I didn't go for any database configuration. You can implement it in your own way. The above validates auth credentials and sets role(authorities). As admin can be able to view user modules as well(most cases, at least that's my conception), I've attached authorities user& admin when admin logs in. In simple words,
1. When a user log in he'll be able access every /** , but not /admin/**
2. When a admin log in he'll be able access every /** and /admin/**
I've tested the scenarios, and the entire code you can go though here - https://github.com/satya-j/rab

Resources