Auth websocket session after manual web auth - spring

I am using Spring Security with STOMP WebSocket on SpringBoot. Auth on websocket worked fine with this config when I used simple login form:
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/webjars/**", "/resources/**").permitAll()
.antMatchers("/register").anonymous()
.anyRequest()
.fullyAuthenticated()
.and()
.formLogin()
.loginPage("/login")
.successHandler(customLoginSuccessHandler)
.failureUrl("/login?error")
.permitAll()
.and()
.csrf().disable()
.logout().logoutSuccessHandler(customLogoutSuccessHandler);
}
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.nullDestMatcher().authenticated()
.simpTypeMatchers(CONNECT).authenticated()
.simpSubscribeDestMatchers(Channel.SYSTEM_ERROR.value()).permitAll()
.simpDestMatchers("/app/publish*").hasRole("USER")
.simpSubscribeDestMatchers("/user/**", "/topic/**", "/system/*").hasRole("USER")
.anyMessage().denyAll();
}
But when I wanted to manually auth client after register new user in RegisterController:
#RequestMapping(value = "/register", method = RequestMethod.POST)
public String signup(#Valid #ModelAttribute SignupForm signupForm, Errors errors) {
if (errors.hasErrors()) {
return SIGNUP_VIEW_NAME;
}
User user = signupForm.createAccount();
try {
userService.persist(user);
} catch (EntityExistsException ex) {
errors.rejectValue("login", "user.exists");
return SIGNUP_VIEW_NAME;
}
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user, null, Collections.singletonList(new SimpleGrantedAuthority("USER"))));
return "redirect:/";
}
I've got problem with auth websocket. When I get redirected to page where websocket connects I am getting org.springframework.security.access.AccessDeniedException: Access is denied

So. Problem was in define Role. In controller when I defined new SimpleGrantedAuthority("USER") it should be "ROLE_USER" because Spring adds refix ROLLE_ by default. Sure we can change default behaviour of this by add next in WebSecurity configuration
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**", "/favicon.ico");
web.expressionHandler(new DefaultWebSecurityExpressionHandler() {
#Override
protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, FilterInvocation fi) {
WebSecurityExpressionRoot root = (WebSecurityExpressionRoot) super.createSecurityExpressionRoot(authentication, fi);
root.setDefaultRolePrefix(""); //remove the prefix ROLE_
return root;
}
});
}
. Yes, dummy mistake but so common. So I will leave it here

Related

Spring Security not letting unauthorized users reach the login page

I am using Spring Security and trying to add a custom login form, the browser does get redirected to the correct URL but I get a message along the lines of
The page isn’t redirecting properly
and can't see the login page at all.
Under the network tab (when I press F12) I see multiple requests to my custom login page, so I'm guessing Spring sees I'm unauthorized then redirects me to the login page over and over effectively creating a loop.
This is the code for my security configuration:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/showMyLoginPage")
.permitAll();
return http.build();
}
Tried removing the loginPage() bit, solving the issue but yielding the default login page.
requested image
Request and response show no info..
Controller I'm using
#Controller
public class MainController {
#RequestMapping("/")
public String testMapping()
{
return "home";
}
#RequestMapping("/showMyLoginPage")
public String loginPage()
{
return "users-login";
}
}
My debug log: https://pastebin.com/LagTN71L
My configuration classes: (won't show hibernate or c3p0)
#Configuration
#EnableWebMvc
#ComponentScan("com.user.springsecuritydemo")
public class MainConfig implements WebMvcConfigurer {
#Bean
InternalResourceViewResolver configuInternalResourceViewResolver()
{
return new InternalResourceViewResolver("/WEB-INF/view/", ".jsp");
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
registry
.addResourceHandler("/resources/**")
.addResourceLocations("/resources/");
}
}
public class SpringMVCDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses()
{
// TODO Auto-generated method stub
return null;
}
#Override
protected Class<?>[] getServletConfigClasses()
{
return new Class[] { MainConfig.class };
}
#Override
protected String[] getServletMappings()
{
return new String[] { "/" };
}
}
Okay, I ended up fixing it, so basically my jsp page is under my /WEBINF/ directory, which was not allowed for everyone to access therefore the browser couldn't get to the login page...
This is my SecurityFilterChain method:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception
{
http
.authorizeHttpRequests()
.requestMatchers("/WEB-INF/**")
.permitAll();
http.authorizeHttpRequests()
.anyRequest()
.authenticated();
http
.formLogin()
.loginPage("/login")
.permitAll();
return http.build();
}

Keycloak return 401 instead of 302 when token expire keyclaok

When the token expires Keycloak normally return 302(redirect to logout) ; however , I want to return 401 instead of 302 in the api response .
I am using spring boot framework , here the keyloack configuration
#KeycloakConfiguration
#EnableGlobalMethodSecurity(jsr250Enabled = true)
public class GlobalSecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public KeycloakClientRequestFactory keycloakClientRequestFactory;
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public KeycloakRestTemplate keycloakRestTemplate() {
return new KeycloakRestTemplate(keycloakClientRequestFactory);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.and()
.cors()
.and()
.csrf()
.disable()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers("test/login").permitAll()
.anyRequest()
.authenticated();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
// Starting from Keycloak Spring Boot Adapter 7.0.0,
// due to some issues, the automatic discovery of the Keycloak configuration
// from the application.properties (or application.yml) file will not work.
// To overcome this problem, we need to define a KeycloakSpringBootConfigResolver bean explicitly in a #Configuration class.
#Bean
public KeycloakConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}
but I can not reach in which place It sends 302 when token expire to change the response to 401
According to the documentation, you could use the exceptionHandling() method:
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/web/server/ServerHttpSecurity.html#exceptionHandling(org.springframework.security.config.Customizer)
exceptionHandling
public ServerHttpSecurity
exceptionHandling​(Customizer<ServerHttpSecurity.ExceptionHandlingSpec>
exceptionHandlingCustomizer)
Configures exception handling (i.e. handles when authentication is
requested). An example configuration can be found below:
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// ...
.exceptionHandling((exceptionHandling) ->
exceptionHandling
// customize how to request for authentication
.authenticationEntryPoint(entryPoint)
);
return http.build();
}
Parameters:
exceptionHandlingCustomizer - the Customizer to provide more options for the ServerHttpSecurity.ExceptionHandlingSpec
Returns:
the ServerHttpSecurity to customize
and according to this page, https://github.com/spring-projects/spring-boot/issues/10715
You could simply do:
http.exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
However I think this will always return 401 UNAUTHORIZED on any (Authentication Exceptions) and not specifically Token Expired.
If you look at the documentation further, you can implement a custom ServerAuthenticationEntryPoint
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/server/ServerAuthenticationEntryPoint.html
Example:
#Component
#Slf4j
public class GatewayAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {
#Override
public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) {
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
// resolve response status
if (ex instanceof AccessDeniedException) {
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
} else exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// compose error response
final ErrorResponse error = new ErrorResponse(ex, exchange.getRequest().getURI().getPath(),
exchange.getRequest().getMethod(), HttpStatus.UNAUTHORIZED);
exchange.getResponse()
.writeWith(getEncoder().encode(Mono.just(error), exchange.getResponse().bufferFactory(),
ResolvableType.forInstance(error), MediaType.APPLICATION_JSON,
Hints.from(Hints.LOG_PREFIX_HINT, exchange.getLogPrefix())));
return Mono.error(ex);
}
}

Wrong redirection in Spring MVC app

Im going to be quick. I have a Spring MVC project, and Im using Spring Security, so after I successfully log in the server redirects me to the application context instead of the index page. Any idea why this is happening, I suspect it may be a security problem, but so far I haven´t figure it out, so please I need help on this one.
My login form action is this: ${loginUrl}
And the redirection problem only happens the first time i try to log in, if I log out and log in again the server redirects me correctly.
Here is my code:
Web Security Config class:
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
ServicioInicioSesion inicioSesion;
#Autowired
MessageSource messageSource;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/addUsuarios").permitAll()
.antMatchers("/roles/**", "/usuarios/**").hasAuthority("Administrador")
.antMatchers("/editarPerfil").authenticated()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").defaultSuccessUrl("/index")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutSuccessUrl("/login")
.and()
.exceptionHandling().accessDeniedPage("/403");
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/static/**");
}
#Bean(name = "authenticationManager")
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(inicioSesion);
auth.setMessageSource(messageSource);
auth.setPasswordEncoder(passwordEncoder());
return auth;
}
#Bean
public PasswordEncoder p`enter code here`asswordEncoder() {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder;
}
}
Index Controller class
#Controller
public class IndexController {
#RequestMapping(value = "/index", method = RequestMethod.GET)
public String showIndex() {
return "index";
}
}
Alberto. Try this one:
1 - replace value = "/index" by value = {"/","/index"}
2 - remove method parameter
#RequestMapping(value = {"/","/index"})
When you submit authentication form in the request you have POST data, but in your case you have RequestMethod.GET

Spring Security custom AuthenticationProvider authenticate method called twice

I am developing a Spring Boot that uses an API Key to authenticate. I have created a custom Authentication provider and the authenticate method is called twice. Can anyone tell me why it's being called twice?
This is my authenticate method:
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
ApiAuthenticationToken authenticationToken = (ApiAuthenticationToken) authentication;
/**
* Authenticate the token
*/
ValidateApiKeyRequest request = new ValidateApiKeyRequest(authenticationToken.getApiKey());
ValidateApiKeyResp resp = getValidateApiKeyCommand().execute(request);
/**
* Populate and return a new authenticaiton token
*/
return createSuccessAuthentication(resp);
}
and this is the createSuccessAuthentication method:
protected Authentication createSuccessAuthentication(final ValidateApiKeyResp resp) {
List<GrantedAuthority> authorities = Lists.newArrayList();
authorities.add(new SimpleGrantedAuthority("API_KEY"));
return new ApiAuthenticationToken(resp.getApiKey(), authorities, true);
}
this is the ApiAuthenticationToken constructor:
public ApiAuthenticationToken(final ApiKey apiKey, Collection<? extends GrantedAuthority> authorities, boolean authenticated) {
super(authorities);
setAuthenticated(true);
this.apiKey = apiKey;
}
This is my security configuration:
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher(CONFIGURATION_MATCHER)
.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint())
.and()
.addFilterBefore(apiKeyAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable()
.authorizeRequests().antMatchers(CONFIGURATION_MATCHER).authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authenticationProvider(apiKeyAuthenticationProvider());
Just in case anyone else has this issue:
The problem was related to my spring security configuration. I had several methods annotated with #Bean - see below
#Bean
public ApiKeyAuthenticationProvider apiKeyAuthenticationProvider() {
return new ApiKeyAuthenticationProvider(getValidateApiKeyCommand());
}
#Bean
public RestAuthenticationEntryPoint restAuthenticationEntryPoint() {
return new RestAuthenticationEntryPoint();
}
#Bean
public ApiKeyAuthenticationFilter apiKeyAuthenticationFilter() throws Exception {
ApiKeyAuthenticationFilter apiKeyAuthenticationFilter = new ApiKeyAuthenticationFilter();
apiKeyAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
apiKeyAuthenticationFilter.setAuthenticationSuccessHandler(new ApiKeyAuthenticationSuccessHandler());
apiKeyAuthenticationFilter.setAuthenticationFailureHandler(new ApiKeyAuthenticationFailureHandler());
return apiKeyAuthenticationFilter;
}
But theses beans were getting registered again in the configure(HttpSecurity http) method.
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher(CONFIGURATION_MATCHER)
.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint())
.and()
.addFilterBefore(apiKeyAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable()
.authorizeRequests().antMatchers(CONFIGURATION_MATCHER).authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authenticationProvider(apiKeyAuthenticationProvider());
}
The fix was to remove the #Bean annotation. Seems obvious now :)

Not able to recognize user ROLE when redirecting page using Spring Security

I am working on my project with Spring security and Thymeleaf. I have basic Spring Security integration.
SecurityConfig.java
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private DataSource dataSource;
#Autowired
public void configureGlobal (AuthenticationManagerBuilder auth) throws Exception
{
auth
.jdbcAuthentication()
.dataSource(dataSource);
}
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/success", true)
.and()
.httpBasic();
}
}
SecurityWebApplicationInitializer.java
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer
{
public SecurityWebApplicationInitializer(){
super(SecurityConfig.class);
}
}
Controller.java
#Controller
public class HomeController {
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String loginPage(Model model) {
return "login";
}
#RequestMapping("/success")
public String loginPageRedirect(HttpServletRequest httpServletRequest){
if(httpServletRequest.isUserInRole("ROLE_ADMIN")) {
return "index1";
} else if(httpServletRequest.isUserInRole("ROLE_USER")) {
return "index2";
} else {
return "index3";
}
}
}
When I have successful login my user is redirected, but to wrong page. My user has role ROLE_USER but method loginPageRedirect is redirecting him to page index3 when it should be index2. I guess my user role is not recognize. How can I do that? Should I add something as parameter to loginPageRedirect so it recognizes role?
I found solution that works for me.
I edited my loginPageRedirect method like this:
#RequestMapping("/success")
public void loginPageRedirect(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException, ServletException {
String role = authResult.getAuthorities().toString();
if(role.contains("ROLE_ADMIN")){
response.sendRedirect(response.encodeRedirectURL(request.getContextPath() + "/index1"));
}
else if(role.contains("ROLE_USER")) {
response.sendRedirect(response.encodeRedirectURL(request.getContextPath() + "/index2"));
}
}
Hope it helps someone with same issue :)

Resources