hasRole("ROLE_USER") not working in spring boot3 with springsecurity6 - spring

I have created one LoginFilter in spring boot where i have created the user authentication object and after that redirected to dashboard page
my below files are:
LoginFilter.java
import jakarta.servlet.*;
/*import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;*/
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONObject;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.GenericFilterBean;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.List;
#Component
public class LoginFilter extends GenericFilterBean {
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException,ServletException
{
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER");
List<SimpleGrantedAuthority> updatedAuthorities = new ArrayList<SimpleGrantedAuthority>();
updatedAuthorities.add(authority);
authentication = new UsernamePasswordAuthenticationToken("myemail.gamil.com", null, updatedAuthoritie
SecurityContextHolder.getContext().setAuthentication(authentication);
this.redirectStrategy.sendRedirect((HttpServletRequest) request, (HttpServletResponse) response,"/dashboard");
}
}
WebSecurityConfigDashboard.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
#Configuration
public class WebSecurityConfigDashboard {
#Autowired
private LoginFilter loginFilter;
#Bean
//authentication
public UserDetailsService userDetailsService1(PasswordEncoder encoder) {
UserDetails admin = User.withUsername("user1")
.password(encoder.encode("123"))
.roles("ROLE_USER")
.build();
UserDetails user = User.withUsername("user2")
.password(encoder.encode("123"))
.roles("ROLE_USER")
.build();
return new InMemoryUserDetailsManager(admin, user);
}
#Bean
public SecurityFilterChain securityFilterChain1(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeHttpRequests()
.requestMatchers("/dashboard*").hasRole("ROLE_USER")
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
.failureUrl("/loginError")
.and().logout()
.invalidateHttpSession(true)
.logoutSuccessUrl("/logout")
.logoutUrl("/j_spring_security_logout").and()
.sessionManagement()
.maximumSessions(12)
.expiredUrl("/logout");
http.addFilterAfter(loginFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
#Bean
public WebSecurityCustomizer webSecurityCustomizer1() {
return (web) -> web.ignoring().requestMatchers("/resources/images/**");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
LoginController.java
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.security.Principal;
import java.text.SimpleDateFormat;
import java.util.*;
#Controller
public class LoginController {
#RequestMapping(value = "/dashboard")
public String dashboard(Model model,Principal principal,HttpServletRequest request, HttpServletResponse response)
{
System.out.println("/dashboard called:------------------------>");
System.out.println("PRINCIPAL:--------------->"+principal.getName());
return 'dashboard';
}
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(ModelMap model) {
System.out.println("/login called:------------------------>");
return "login";
}
#RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(ModelMap model) {
System.out.println("/logout called:------------------------>");
return "logout";
}
#RequestMapping(value = "/loginError", method = RequestMethod.GET)
public String loginError(ModelMap model) {
model.addAttribute("error", "LOGIN FAILED");
return "login";
}
#RequestMapping(value = "/error", method = RequestMethod.GET)
public String error(ModelMap model,HttpServletRequest request, HttpServletResponse response) {
return "error";
}
}
when I calling /dashboard url in browser the it goes to login page instead of dashboard i have created UsernamePasswordAuthenticationToken in filter class.
If i remove .hasRole("ROLE_USER") and instead put .permitAll() and then if I hit /dashboard then the method with /dashboard is called but principle object getting null
I have using spring boot 3 with spring security 6
I dont't know why this is happening?

A common oversight is that roles() and hasRole() will add the ROLE_ prefix for you.
So, instead of doing hasRole('ROLE_USER'), please do hasRole('USER'). And instead of doing roles('ROLE_USER') when creating the user, do roles('USER').
I've added a Spring Security ticket to clarify this.

Related

Spring security with JWT when authorizing via telegram

I can’t correctly authorize through the telegram token, I seem to have done everything, but when I request I get a 403 error, although the context is correct
I don’t understand what the problem is, the context is authorized and there are roles, everything is configured, but it returns 403
SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=ru.alishev.springcourse.FirstSecurityApp.security.TelegramUserDetails#3e264c5e, Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[Include0]]]
SecurityConfig:
package ru.alishev.springcourse.FirstSecurityApp.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import ru.alishev.springcourse.FirstSecurityApp.services.TelegramDetailService;
/**
* #author Neil Alishev
*/
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final TelegramDetailService telegramDetailService;
private final JWTFilter jwtFilter;
#Autowired
public SecurityConfig(TelegramDetailService telegramDetailService, JWTFilter jwtFilter) {
this.telegramDetailService = telegramDetailService;
this.jwtFilter = jwtFilter;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// конфигурируем сам Spring Security
// конфигурируем авторизацию
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/tg_users/auth/telegram").hasRole("Include0")
.antMatchers("/authh/login", "/authh/registration", "/error", "/api/tg_users", "api/tg_users/auth/telegram").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/api/tg_users")
.loginProcessingUrl("/process_login")
.defaultSuccessUrl("/hello", true)
.failureUrl("/auth/login?error")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/auth/login")
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
// Настраиваем аутентификацию
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(telegramDetailService)
.passwordEncoder(getPasswordEncoder());
}
#Bean
public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
JWTFilter:
package ru.alishev.springcourse.FirstSecurityApp.config;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.JWTVerifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import ru.alishev.springcourse.FirstSecurityApp.security.JWTUtil;
import ru.alishev.springcourse.FirstSecurityApp.security.TelegramUserDetails;
import ru.alishev.springcourse.FirstSecurityApp.services.TelegramDetailService;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Component
public class JWTFilter extends OncePerRequestFilter {
private final JWTUtil jwtUtil;
private final TelegramDetailService telegramDetailService;
#Autowired
public JWTFilter(JWTUtil jwtUtil, TelegramDetailService telegramDetailService) {
this.jwtUtil = jwtUtil;
this.telegramDetailService = telegramDetailService;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && !authHeader.isEmpty() && authHeader.startsWith("Bearer ")) {
String jwt = authHeader.substring(7);
// System.out.println(jwt);
if (jwt.isEmpty()) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Invalid JWT Token in Bearer Header");
} else {
try {
String username = jwtUtil.validateTokenAndRetrieveClaim(jwt);
// System.out.println(username);
TelegramUserDetails telegramUserDetails = telegramDetailService.loadUserByUsername(username);
// System.out.println("tut");
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(telegramUserDetails,
telegramUserDetails.getPassword(),
telegramUserDetails.getAuthorities());
if (SecurityContextHolder.getContext().getAuthentication() == null) {
SecurityContextHolder.getContext().setAuthentication(auth);
System.out.println(SecurityContextHolder.getContext());
}
} catch (JWTVerificationException ex) {
System.out.println("ne proshlo");
}
}
}
filterChain.doFilter(request, response);
}
}
JWTUtil:
package ru.alishev.springcourse.FirstSecurityApp.security;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.time.ZonedDateTime;
#Component
public class JWTUtil {
public String generateToken(String username) {
Date expirationDate = Date.from(ZonedDateTime.now().plusMinutes(60).toInstant());
// 60 минут действиe токена
return JWT.create()
.withSubject("Telegram user details")
.withClaim("username", username)
.withIssuedAt(new Date())
.withIssuer("Quiz game")
.withExpiresAt(expirationDate)
.sign(Algorithm.HMAC256("secret"));
}
public String validateTokenAndRetrieveClaim(String token) throws JWTVerificationException {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256("secret"))
.withSubject("Telegram user details")
.withIssuer("Quiz game")
.build();
DecodedJWT jwt = verifier.verify(token);
return jwt.getClaim("username").asString();
}
}
here is full hardcoding, but according to the idea it should work, but I get a 403 error
please help me fix
full code:
https://github.com/Include5/gameTG
I changed
.antMatchers("/api/tg_users/auth/telegram").hasRole("Include0")
to
.antMatchers("/api/tg_users/auth/telegram").hasAuthority("Include0")
and it worked

Opaque Token Implementation in spring security

im trying to create a secured spring rest api for the security i want to use opaque token stored in the database so that if the client query on the api with a bearer token . the server will check on the database if the token exist if the token is valid and get the user and the privilege and check if the user have the authority to do the request. i've done some research on the net but didn't found result that can be understood by a beginner. how can i implement this.
method 1 found method 2 i have found this two methods but i dont know where too implements the database verification and validation
after a lot of research i've found this and it is working
first i've created a Authorization filter like this :
package com.example.bda_test_11.security;
import com.example.bda_test_11.model.BdaUser;
import com.example.bda_test_11.model.Token;
import com.example.bda_test_11.repository.TokenRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Slf4j
public class AuthorizationFilter extends BasicAuthenticationFilter {
private final TokenRepository tokenRepository;
public AuthorizationFilter(AuthenticationManager authenticationManager,TokenRepository tokenRepository) {
super(authenticationManager);
this.tokenRepository = tokenRepository;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String tokenCode = request.getHeader(HttpHeaders.AUTHORIZATION);
log.info(tokenCode);
if(tokenCode == null ) {
filterChain.doFilter(request,response);
return;
}
Token token = tokenRepository.findByCode(tokenCode).orElse(null);
if (token == null) {
filterChain.doFilter(request,response);
return;
}
BdaUser user = token.getUser();
UsernamePasswordAuthenticationToken userToken = new UsernamePasswordAuthenticationToken(user.getLogin(),null,user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(userToken);
filterChain.doFilter(request,response);
}
}
and a UsernamePasswordAuthenticationFilter like this
package com.example.bda_test_11.security;
import com.example.bda_test_11.security.domain.LoginCredentials;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
#Slf4j
public class JsonObjectAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final ObjectMapper objectMapper = new ObjectMapper();
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
try{
BufferedReader reader = request.getReader();
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line=reader.readLine())!=null){
stringBuilder.append(line);
}
LoginCredentials authRequest = objectMapper.readValue(stringBuilder.toString(),LoginCredentials.class);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
authRequest.getLogin(),
authRequest.getPassword()
);
setDetails(request,token);
log.info(token.toString());
return this.getAuthenticationManager().authenticate(token);
} catch (IOException e){
throw new RuntimeException(e);
}
}
}
if the connection is successful we generate a token like :
package com.example.bda_test_11.security;
import com.example.bda_test_11.model.BdaUser;
import com.example.bda_test_11.model.Token;
import com.example.bda_test_11.repository.TokenRepository;
import com.example.bda_test_11.service.BdaUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Slf4j #Component
public class AuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private final TokenRepository tokenRepository;
private final BdaUserService userService;
#Autowired
public AuthSuccessHandler(TokenRepository tokenRepository, BdaUserService userService) {
this.tokenRepository = tokenRepository;
this.userService = userService;
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
UserDetails principal = (UserDetails) authentication.getPrincipal();
BdaUser user = userService.findByLogin(principal.getUsername());
Token token = new Token(user);
tokenRepository.save(token);
log.info(token.getCode());
response.addHeader("Authorization",token.getCode());
response.addHeader("Content-Type","application/json");
response.getWriter().write("{\"token\":"+token.getCode()+",\"login\":"+user.getLogin());
}
}
and then ive configured the filterChain bean like this
package com.example.bda_test_11.security;
import com.example.bda_test_11.repository.TokenRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
#Configuration
public class BdaSecurity {
private final AuthenticationManager authenticationManager;
private final AuthSuccessHandler authSuccessHandler;
private final TokenRepository tokenRepository;
#Autowired
public BdaSecurity(AuthenticationManager authenticationManager, AuthSuccessHandler authSuccessHandler, TokenRepository tokenRepository) {
this.authenticationManager = authenticationManager;
this.authSuccessHandler = authSuccessHandler;
this.tokenRepository = tokenRepository;
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.authorizeHttpRequests((auth)->{
try {
auth
.antMatchers("/api/admin").hasAuthority("ADMIN")
.antMatchers("/api/user").hasAuthority("USER")
.anyRequest().permitAll()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(authenticationFilter())
.addFilter(new AuthorizationFilter(authenticationManager,tokenRepository))
.exceptionHandling()
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.httpBasic(Customizer.withDefaults());
return http.build();
}
#Bean
public JsonObjectAuthenticationFilter authenticationFilter() {
JsonObjectAuthenticationFilter filter = new JsonObjectAuthenticationFilter();
filter.setAuthenticationSuccessHandler(authSuccessHandler);
filter.setAuthenticationManager(authenticationManager);
return filter;
}
}

How do session time out and and ask for login again in Spring boot

I am trying to build simple JSON rest API using spring boot, I have build the application it works fine, now I need to do the simple Basic authentication and I did this with Spring security. Please find the Code below, My pain point here is when I run the application, I ask for the login first time then I tried to reload the URL again it does not ask for user name password. How to make the URL session expired and ask to prompt for login again. Note : I am not using any UI, It just an API
Security Configuration Class
package com.erplogic.erp.topcon.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
#Configuration
public class SecurityConfiguration {
#Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()
).sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.NEVER)
)
.httpBasic();
return http.build();
}
#Bean
InMemoryUserDetailsManager userDetailsService() {
UserDetails user = User.builder().username("user").password(encoder().encode("pass")).roles("USER").build();
return new InMemoryUserDetailsManager(user);
}
#Bean
PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
#Bean
SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
}
Controller Class
package com.erplogic.erp.topcon.controller;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.erplogic.erp.topcon.service.SapApiService;
import com.erplogic.poc.objects.Root;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.erplogic.maven.plclog.wsdl2java.StandardFaultMessage_Exception;
#RestController
public class OutboundController {
#Autowired
private SapApiService sapApiService;
#RequestMapping(path ="/outbound",produces = MediaType.APPLICATION_JSON_VALUE,method = RequestMethod.GET)
public Root getOutbound() throws KeyManagementException, NoSuchAlgorithmException, StandardFaultMessage_Exception, JsonProcessingException {
return ((SapApiService) sapApiService).processOutboudService();
}
#RequestMapping(value = "/outbound/{outboundId}",produces = MediaType.APPLICATION_JSON_VALUE,method = RequestMethod.GET)
public String getOutboundId(#PathVariable String outboundId) throws KeyManagementException, JsonProcessingException, NoSuchAlgorithmException, StandardFaultMessage_Exception {
return ((SapApiService) sapApiService).processOutboudServiceByID(outboundId);
}
}

Spring WebFlux Request Principal is null in test

We have library which provides a custom filter for our requests, which we use to write information on the request to our logs. We previously only needed servlet support, but now need to add webflux support. I've created a new WebFilter, which works as expected.
However, trying to test this new filter has been difficult. We're relying on the principal in the request for user information for the log, but during the test, it is always null. I've tried many things, including following the examples here: https://docs.spring.io/spring-security/site/docs/5.1.0.RELEASE/reference/html/test-webflux.html
My Filter:
package com.project.utils.security.request.logging.config.autoconfig;
import com.project.utils.logging.LoggingUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
public class ReactiveRequestLoggingFilter implements WebFilter {
private static final Logger logger = LoggerFactory.getLogger(ReactiveRequestLoggingFilter.class);
#Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
return serverWebExchange
.getPrincipal()
.flatMap(
principal -> {
LoggingUtil loggingUtil =
new LoggingUtil(serverWebExchange.getRequest(), principal.getName());
loggingUtil.setEvent("Http Request");
logger.info(loggingUtil.toString());
return webFilterChain.filter(serverWebExchange);
});
}
}
My Test:
package com.project.utils.security.request.logging.config.autoconfig;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
import com.project.utils.security.request.logging.config.testcontrollers.ReactiveTestController;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = {ReactiveTestController.class})
class ReactiveRequestLoggingFilterTest {
#Autowired ReactiveTestController controller;
ReactiveRequestLoggingFilter filter = new ReactiveRequestLoggingFilter();
#Test
void test() {
WebTestClient client =
WebTestClient.bindToController(controller)
.webFilter(filter)
.apply(springSecurity())
.configureClient()
.build()
.mutateWith(mockUser("User"));
client
.get()
.uri("/test")
.exchange()
.expectStatus()
.isOk()
.expectBody(String.class)
.isEqualTo("GET");
}
}
The controller I'm using for the test:
package com.project.utils.security.request.logging.config.testcontrollers;
import com.project.utils.security.request.logging.config.annotations.EnableReactiveRequestLogging;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
#RestController
#RequestMapping("/test")
#EnableReactiveRequestLogging
public class ReactiveTestController {
#GetMapping
Mono<ResponseEntity<String>> get() {
return Mono.just(ResponseEntity.ok("GET"));
}
}
We are using Spring Boot 2.1, which uses Spring Security 5.1. Like I said, the actual functionality works, but we need to get tests written for it, and I can't figure out how to get the principal into the request.
Thanks!
Okay, found a working solution from this answer: https://stackoverflow.com/a/55426458/1370823
My final test looked like:
package com.project.utils.security.request.logging.config.autoconfig;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
import com.project.utils.security.request.logging.config.testcontrollers.ReactiveTestController;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {ReactiveTestController.class, ReactiveRequestLoggingFilter.class})
#WebFluxTest(controllers = {ReactiveTestController.class})
#WithMockUser(value = "Test User", username = "TestUser")
class ReactiveRequestLoggingFilterTest {
#Autowired ReactiveTestController controller;
#Autowired ReactiveRequestLoggingFilter filter;
#Test
void test() {
WebTestClient client =
WebTestClient.bindToController(controller)
.webFilter(new SecurityContextServerWebExchangeWebFilter())
.webFilter(filter)
.apply(springSecurity())
.build();
client
.get()
.uri("/test")
.exchange()
.expectStatus()
.isOk()
.expectBody(String.class)
.isEqualTo("GET");
}
}

Deployed Spring App is not getting routed to correct URL on first logins

I deployed a Spring App to Heroku. I am using Spring Security for logging in and registration. My problem is that for new users, when they initially log-in, it takes them to the base URL (the URL that Heroku gave me for my site). All of my main html files are in a folder named "cheese". The problem is that it directs me to the main URL (instead of "/cheese/account", which is where I direct it to be routed in my SecurityConfig), and I get a white label error.
This only happens the first time. When they log on again, it takes them to the correct URL, which is "/cheese/account". Also, once in a while, I will click on the base URL that heroku gave for my site, and it gives me just that URL, and doesn't direct me to "/cheese/login". This will happen if I try to access the URL from an incognito window.
I dont have this problem at all when running it locally. Here is the relevant code...Let me know if you need anything, in addition.
SecurityConfig
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.sql.DataSource;
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select email as principal, password as credentials, true from customer where email=?")
.authoritiesByUsernameQuery("select customer_email as principal, role_id as role from user_roles where customer_email=?")
.passwordEncoder(passwordEncoder()).rolePrefix("ROLE_");
}
#Override
protected void configure(HttpSecurity http) throws Exception{
http
.csrf().disable()
.authorizeRequests()
.antMatchers(
"/**/webjars/**",
"/cheese/signup",
"/cheese/login",
"/cheese/success").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/cheese/login")
.defaultSuccessUrl("/cheese/account")
.permitAll();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
UserController
package com.example.demo.controllers;
import com.example.demo.models.Customer;
import com.example.demo.models.data.CustomerDao;
import com.example.demo.models.services.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
#Controller
#RequestMapping("cheese")
public class UserController {
#Autowired
private CustomerDao customerDao;
#Autowired
UserService userService;
#RequestMapping(value = "login")
public String loginPage(Model model) {
model.addAttribute("title", "Login Page");
model.addAttribute("customer", new Customer());
return "cheese/login";
}
#RequestMapping(value = "account")
public String accountInfo(Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Customer customer = customerDao.findByEmail(authentication.getName());
model.addAttribute("name", customer.getName());
model.addAttribute("customer", customer);
return "cheese/account";
}
#GetMapping("signup")
public String displaySignUpForm(Model model) {
model.addAttribute("title", "Sign Up");
model.addAttribute("customer", new Customer());
return "cheese/signup";
}
#PostMapping(value = "signup")
public String processSignUp(Model model, #ModelAttribute Customer customer, Errors errors) {
if (errors.hasErrors()) {
return "cheese/signup";
}
userService.createUser(customer);
return "cheese/success";
}
}
MainController
package com.example.demo.controllers;
import com.example.demo.models.Cheese;
import com.example.demo.models.data.CheeseDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
#RequestMapping(value = "cheese")
#Controller
public class MainController {
#Autowired
CheeseDao cheeseDao;
#RequestMapping(value = "")
public String hello(Model model) {
model.addAttribute("title", "Grocery List");
model.addAttribute("cheeses", cheeseDao.findAll());
return "cheese/index";
}
#GetMapping("add")
public String displayAddCheeseForm(Model model) {
model.addAttribute("title", "Add Cheese");
model.addAttribute("cheese", new Cheese());
return "cheese/add";
}
#PostMapping("add")
public String processAddCheeseForm(Model model,
#ModelAttribute #Valid Cheese cheese,
Errors errors) {
if (errors.hasErrors()) {
return "cheese/add";
}
cheeseDao.save(cheese);
return "redirect:";
}
#RequestMapping(value = "remove", method = RequestMethod.GET)
public String displayRemoveCheeseForm(Model model) {
model.addAttribute("cheeses", cheeseDao.findAll());
model.addAttribute("title", "Remove Cheese");
return "cheese/remove";
}
#RequestMapping(value = "remove", method = RequestMethod.POST)
public String processRemoveCheeseForm(Model model, #RequestParam int[] cheeseIds) {
for (int id : cheeseIds) {
cheeseDao.deleteById(id);
}
return "redirect:";
}
}

Resources