Spring security 6.0 - unable to configure access to public resources - spring-security

Spring Security by default provides forced authentication for all our endpoints, regardless of whether we add role-based security or not.
here is the setup pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
configuration
#EnableWebSecurity(debug = true)
//#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {
#Value("${spring.security.debug:false}")
boolean securityDebug;
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeHttpRequests()
.requestMatchers("/hello/**")
.permitAll()
.requestMatchers(HttpMethod.DELETE)
.hasRole("ADMIN")
.requestMatchers("/admin/**")
.hasAnyRole("ADMIN")
.requestMatchers("/protected/**")
.hasAnyRole("USER", "ADMIN")
.and()
.httpBasic()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
#Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.debug(securityDebug)
.ignoring()
.requestMatchers("/css/**", "/js/**", "/img/**", "/lib/**", "/favicon.ico");
}
}
#Configuration
public class UserDetailServiceConfig {
#Bean
public UserDetailsService userDetailsService(BCryptPasswordEncoder bCryptPasswordEncoder) {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user")
.password(bCryptPasswordEncoder.encode("userPass"))
.roles("USER")
.build());
manager.createUser(User.withUsername("admin")
.password(bCryptPasswordEncoder.encode("adminPass"))
.roles("ADMIN", "USER")
.build());
return manager;
}
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
#RestController
public class AdminRestController {
#GetMapping("admin")
public String getAdminResource(UsernamePasswordAuthenticationToken principal){
String auth = principal
.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
return "/admin -> userName : " + principal.getName() + " - authorities : " + auth;
}
#GetMapping("hello")
public String home() {
return "home";
}
}
401Unauthorized
In addition, this filter can safely skip a user with USER rights to endpoint (/admin).
In order to get to the public endpoint, you need to enter credentials. But the setting I specified was supposed to provide public access.
I have repeatedly encountered such a problem on different versions of Security and have not yet found an explanation.
Maybe someone has ideas on how to get around this problem (when it can occur at all) and why is this happening?

#Configuration
public class MySecurityConfigurer {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeHttpRequests()
.requestMatchers("/home/**")
.permitAll()
.requestMatchers(HttpMethod.DELETE)
.hasRole("ADMIN")
.requestMatchers("/admin/**")
.hasRole("ADMIN")
.requestMatchers("/protected/**")
.hasAnyRole("USER", "ADMIN")
.and()
.httpBasic()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
}
I have removed the annotation #EnableWebSecurity.
Here's what I found about it:
The #EnableWebSecurity annotation is needed to completely disable the default web application security configuration.
#EnableWebSecurity causes Spring Boot auto-configuration to be canceled, however, this will not cancel the default setting for authentication manager configuration or the setting for Actuator's security.
In addition, if you use Spring boot, during the auto-configuration of classes, it will automatically configure the components of the security module.
So if you define #Configuration with #EnableWebSecurity anywhere in your application it will disable the default web application security settings in Spring Boot.
The application now performs the expected behavior.

Spring Security effectively secures all endpoints by default, which is desirable. Because a security breach can happen very quickly if you forget to configure something.
That said, you can explicitly allow certain endpoints to everyone by using the permitAll method without messing with annotations.
in your current setup you can shorten like this :
#Configuration
#EnableWebSecurity
public class MySecurityConfigurer {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeHttpRequests()
.requestMatchers("/home/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/protected/**").hasAnyRole("USER", "ADMIN")
.and()
.httpBasic()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
}
Will produce the effect that home is public, admin is reserved for user with ADMIN role, and protected is reserved for user with ADMIN or USER role.
Does this answer your problem ?

Related

Spring Security (5.7.3): this.authenticationManager is null

I'm trying POST requests to login to the page with JWT Authentication.
I had initially tried to do JWT Authentication using WebSecurityConfigurerAdapter. But WebSecurityConfigurerAdapter is deprecated. I then tried to integrate it according to the new usage.But When I send POST request in Postman, I m getting this error.
"org.springframework.security.authentication.AuthenticationManager.authenticate(org.springframework.security.core.Authentication)" because "this.authenticationManager" is null
My Dependencies are:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.project</groupId>
<artifactId>questapp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>questapp</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
My Controller Class is:
#RestController
#RequestMapping("/auth")
public class AuthenticationController {
private AuthenticationManager authenticationManager;
private JwtTokenProvider jwtTokenProvider;
#PostMapping("/login")
public String login(#RequestBody UserRequest userLoginRequest) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userLoginRequest.getUserName(), userLoginRequest.getPassword());
Authentication auth = authenticationManager.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
String jwtToken = jwtTokenProvider.generateJwtToken(auth);
return "Bearer "+jwtToken;
}
}
My SecurityConfig Class is:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfig {
private final UserDetailsService jwtUserDetailsService;
private final JwtAuthenticationEntryPoint handler;
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
#Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(jwtUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
return authenticationManagerBuilder.build();
}
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception{
httpSecurity
.cors()
.and()
.csrf().disable()
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(handler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(HttpMethod.GET,"/auth/**")
.permitAll()
.antMatchers("/auth/**")
.permitAll()
.anyRequest().authenticated();
return httpSecurity.build();
}
}
Can anyone tell me where I made a mistake?

How to allow anonymous access to endpoint

I have a spring cloud architecture and I can't allow anonymous access to an endpoint.
Here is my code:
Gateway =============================
Application:
#SpringBootApplication
#EnableZuulProxy
#EnableEurekaClient
#EnableResourceServer
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
#Bean
public FilterRegistrationBean<?> corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<?> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
Pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.geminiald</groupId>
<artifactId>gateway</artifactId>
<version>1.0.0</version>
<name>gateway</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
bootstrap.properties:
spring.cloud.config.name=gateway
spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/
application.properties:
security.oauth2.resource.user-info-uri=http://localhost:8083/user
Furthermore, I have an auth-service ====================
Application:
#SpringBootApplication
#EnableEurekaClient
#EnableAuthorizationServer
#EnableResourceServer
#EntityScan(basePackages = { "com.geminiald.authservice.models" })
#EnableJpaRepositories(basePackages = { "com.geminiald.authservice.repositories" })
public class AuthServiceApplication {
public static void main(String[] args) {
SpringApplication.run(AuthServiceApplication.class, args);
}
}
#Configuration
public class AuthorizationServerConfig
extends AuthorizationServerConfigurerAdapter {
private BCryptPasswordEncoder passwordEncoder;
#Autowired
public AuthorizationServerConfig(
#Lazy BCryptPasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
#Autowired
private AuthSettings settings;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerSecurityConfigurer security)
throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory().withClient(settings.getClient())
.authorizedGrantTypes(
settings.getAuthorizedGrantTypes())
.authorities(settings.getAuthorities())
.scopes(settings.getScopes())
.resourceIds(settings.getResourceIds())
.accessTokenValiditySeconds(settings
.getAccessTokenValiditySeconds())
.secret(passwordEncoder.encode(settings.getSecret()));
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager);
}
}
#Configuration
public class AuthenticationMananagerProvider
extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
#Autowired
private BCryptPasswordEncoder encoder;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Autowired
public void authenticationManager(AuthenticationManagerBuilder builder,
UserRepository repository) throws Exception {
builder.userDetailsService(userDetailsService).passwordEncoder(encoder);
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
BCryptPasswordEncoder bCryptPasswordEncoder =
new BCryptPasswordEncoder();
return bCryptPasswordEncoder;
}
#Bean
public FilterRegistrationBean<?> corsFilter() {
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<?> bean =
new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
application.properties:
# H2 Database configuration
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.datasource.initialization-mode=always
bootstrap.properties:
spring.cloud.config.name=auth-service
spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/
I have a Dc-tool-box-service:
Application ==========
#SpringBootApplication
#EnableEurekaClient
#EnableResourceServer
#EntityScan(basePackages = { "com.geminiald.dctoolbox.models" })
#EnableJpaRepositories(basePackages = { "com.geminiald.dctoolbox.repositories" })
public class DcToolBoxServiceApplication {
public static void main(String[] args) throws IOException {
SpringApplication.run(DcToolBoxServiceApplication.class, args);
}
}
bootstrap.properties:
spring.cloud.config.name=dc-tool-box-service
spring.cloud.config.discovery.service-id=config
spring.cloud.config.discovery.enabled=true
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/
application.properties:
# H2 Database configuration
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.datasource.initialization-mode=always
spring.servlet.multipart.max-file-size=-1
spring.servlet.multipart.max-request-size=-1
security.oauth2.resource.user-info-uri=http://localhost:8083/user
and there, you can see all *.properties file from the configuration service:
auth-service:
spring.application.name=auth-service
server.port=8083
eureka.client.region = default
eureka.client.registryFetchIntervalSeconds = 5
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/
gateway:
spring.application.name=gateway
server.port=8000
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000
zuul.host.connect-timeout-millis= 15000
zuul.host.socket-timeout-millis= 60000
ribbon.ReadTimeout= 60000
ribbon.ConnectTimeout= 60000
eureka.client.region = default
eureka.client.registryFetchIntervalSeconds = 5
zuul.routes.discovery.path=/discovery/**
zuul.routes.discovery.sensitive-headers=Set-Cookie,Authorization
zuul.routes.discovery.url=http://localhost:8082
hystrix.command.discovery.execution.isolation.thread.timeoutInMilliseconds=600000
zuul.routes.auth-service.path=/auth-service/**
zuul.routes.auth-service.sensitive-headers=Set-Cookie
hystrix.command.auth-service.execution.isolation.thread.timeoutInMilliseconds=600000
zuul.routes.dc-tool-box-service.path=/dc-tool-box-service/**
zuul.routes.dc-tool-box-service.sensitive-headers=Set-Cookie
hystrix.command.dc-tool-box-service.execution.isolation.thread.timeoutInMilliseconds=600000
dc-tool-box-service:
spring.application.name=dc-tool-box-service
server.port=8086
eureka.client.region = default
eureka.client.registryFetchIntervalSeconds = 5
eureka.client.serviceUrl.defaultZone=http://localhost:8082/eureka/
In order to do that, I have two endpoints in the dc-tool-box-service: /persons/signup and /dossiers.
I would like to keep the security on /dossiers, but /persons/signup should be anonymous. So anybody can access without authentication.
That is what I in gateway:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.antMatchers("/dc-tool-box-service/persons/signup").anonymous();
}
}
In in my Postman, I can access the /dossiers using my token, but I get the message:
{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}
when I try to access /persons/signup without providing a property Authorization in my header.
Could someone help me please?! I would be thankful.
You need to use permitAll() instead of anonymous().
Replace: .antMatchers("/dc-tool-box-service/persons/signup").anonymous();
With: .antMatchers("/dc-tool-box-service/persons/signup").permitAll();
This will authorize all users, anonymous and logged in.
Problem with anonymous() is that, only users that have ROLE_ANONYMOUS would able to access that endpoint.
EDIT: Security order matters: It still doesn't work because your first security constraint is that any request to your application should be authenticated, then you have configured to allow /signup request. Change the order of these permissions.
http.authorizeRequests()
.antMatchers("/dc-tool-box-service/persons/signup").permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated();

spring boot custom login page

I am trying to add a custom login page for my boot strap application. I was following this tutorial. I couldn't make work with my custom login page.
Here is my pom.xml:
...
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>test</scope>
</dependency>
...
MvcConfig.java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/index.html");
registry.addViewController("/login").setViewName("login");
}
}
FrontendApp.java:
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
import ch.qos.logback.classic.Logger;
#SpringBootApplication
#Import(value = MvcConfig.class)
public class FrontendApp {
private static Logger logger = (Logger) LoggerFactory.getLogger(FrontendApp.class);
public static void main(String[] args) {
SpringApplication app = new SpringApplication(FrontendApp.class);
app.run(args);
}
}
SecurityConfiguration.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Autowired
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(this.customAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/css/**").permitAll()
.antMatchers("/resources/**").permitAll()
.antMatchers("**").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated().and()
.formLogin()
.loginPage("/login");
}
}
I opened all the url's so Ican just check whether I can see /login or not.
CustomAuthenticationProvider.java
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationProvider.class);
public CustomAuthenticationProvider() {
logger.info("*** CustomAuthenticationProvider created");
}
#Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if(authentication.getName().equals("karan") && authentication.getCredentials().equals("saman")) {
List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
grantedAuths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return new UsernamePasswordAuthenticationToken(authentication.getName(), authentication.getCredentials(), grantedAuths);
} else {
return null;
}
}
}
When I try localhost:8080/login I will get the following error:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
There was an unexpected error (type=Internal Server Error, status=500).
Error resolving template "login", template might not exist or might not be accessible by any of the configured Template Resolvers
However when I try localhost:8080/ it will successfully redirect to index.html as I specified in MvcConfig.java.
Here is my login.html code:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="utf-8" />
<title>k</title>
</head>
I paste my login.html in /src/main/resources/templates and /src/main/webapp/ and /src/main/webapp/templates it still didn't work!
OK, so it was a simple mistake in pom.xml.
<!--<resources>-->
<!--<resource>-->
<!--<directory>src/main/resources</directory>-->
<!--<includes>-->
<!--<include>*</include>-->
<!--</includes>-->
<!--<filtering>true</filtering>-->
<!--</resource>-->
<!--</resources>-->
After I commented out these (as you see) from the pom file it worked perfectly.At least the above codes might be useful to someone else.

Spring Boot and Spring Security not honoring configureGlobal

I created a simple SpringBoot app and added spring security. When I login it is only accepting the default password it is generating.
It won't let me login using the username/password I have configured in WebSecurityConfigurerAdapter.
Here is my code. SsFirstApplication.java
#SpringBootApplication
#EnableAutoConfiguration
public class SsFirstApplication {
public static void main(String[] args) {
SpringApplication.run(SsFirstApplication.class, args);
}
}
My custom security config "SecurityConfig.java"
#Configuration
#EnableWebSecurity
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authBuilder) throws Exception{
logInfo("configureGlobal");
authBuilder
.inMemoryAuthentication()
.withUser("user")
.password("password123");
//.roles("USER");
}
private void logInfo(String strToken){
for(int index=0;index<1;index++){
System.out.println("************************** "+strToken+" ****************************");
}
}
#Override
protected void configure(HttpSecurity http) throws Exception{
logInfo("configure");
http
.authorizeRequests()
.anyRequest().authenticated();
}
}
And finally the Web App Initializer "SecurityWebApplicationInitializer"
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer{
public SecurityWebApplicationInitializer(){
super(SecurityConfig.class);
}
protected Class<?>[] getRootConfigClasses() {
return new Class[] { SecurityConfig.class };
}
}
When I start the SpringBoot app (I am using STS/Eclipse IDE) in the console it is generating and printing out the password for username "user".
When I try http://localhost:8080/admin it won't let me use the username/password I configured.
Here is my pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.test</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>ss-first</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>demo.SsFirstApplication</start-class>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
The configuration of Spring MVC and Spring security needs to be corrected. Following will be the configuration for you.
#SpringBootApplication
#Controller
public class App extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
#RequestMapping(value="/")
public String home() {
return "admin";
}
The SecurityConfig is more or less correct except you need to add the login url information as shown below.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated().and().formLogin()
.loginPage("/login").failureUrl("/login?error").permitAll();
http.csrf().disable();
}
As menionted in the comments above SecurityWebApplicationInitializer is also not needed in your config.

Spring Security OAuth2 AuthorizationServer

I'm playing around with spring-security-oauth2. I try to build some microservices with an authentication backend.
I set up an simple spring boot project with the following dependencies
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
and one Configuration Class
#Configuration
public class SecurityConfiguration {
#Autowired
#Qualifier("clientDetailsServiceBean")
private ClientDetailsService clientDetailsService;
#Autowired
#Qualifier("userDetailsServiceBean")
private UserDetailsService userDetailsService;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(jsr250Enabled = true, securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
#Bean(name = "authenticationManagerBean")
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().permitAll().and().userDetailsService(userDetailsService).formLogin().and().httpBasic();
}
}
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager).tokenStore(tokenStore());
}
#Bean
public ApprovalStore approvalStore() throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore());
return store;
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("permitAll()");
security.allowFormAuthenticationForClients();
}
}
My Implementation of Client- and UserDetailsService are very simple and always returns an object
#Service("clientDetailsServiceBean")
public class ClientDetailsServiceBean implements ClientDetailsService {
private static final Logger LOGGER = LoggerFactory.getLogger(ClientDetailsServiceBean.class);
#Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
LOGGER.info("Load client {}", clientId);
BaseClientDetails details = new BaseClientDetails();
details.setClientId(clientId);
details.setAuthorizedGrantTypes(Arrays.asList("password", "refresh_token", "client_credentials"));
details.setScope(Arrays.asList("trust"));
details.setAutoApproveScopes(Arrays.asList("trust"));
details.setAuthorities(Arrays.asList(new SimpleGrantedAuthority("client_role2")));
details.setResourceIds(Arrays.asList("clients"));
details.setClientSecret("secret");
return details;
}
}
#Service("userDetailsServiceBean")
public class UserDetailsServiceBean implements UserDetailsService {
private static final Logger LOGGER = LoggerFactory.getLogger(UserDetailsServiceBean.class);
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LOGGER.info("Load user {}", username);
return new User(username, "password", Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")) );
}
}
But, when i try to receive an accessToken via
curl http://localhost:8081/oauth/token -d grant_type=client_credentials -d client_id=web_client -d client_secret=secret
i receive an error "Full authentication is required to access this resource" and when i try
curl http://localhost:8081/oauth/token -d grant_type=client_credentials -d client_id=web_client -d client_secret=secret --user web_client:secret
i receive an error "Bad credentials". From my point of view both should work, but it seems like my configuration is missing.
There are other things with OAuth that unclear to me:
I try to build an spring-mvc application with spring-security and a custom login form. It's possible to handle token request and refresh cycles by spring security without redirect to the authentication app?
In case of event driven application, it's possible to ensure the token is valid? In case of failure, the user clicks on button and an event is written but the processing of this will be hours later. How can i process the event with the user credentials?
Your inner #Configuration classes need to be static. I'm surprised the app starts at all, and probably the whole of your SecurityConfiguration is actually not being used.
It's possible to handle token request and refresh cycles by spring security without redirect to the authentication app?
Naturally. Did you read about the password and refresh_token grants in the spec? But in a web UI you are strongly advised to use the auth code grant (with the redirects), so that the user only enters his credentials in a trusted place.
the user clicks on button and an event is written but the processing of this will be hours later. How can i process the event with the user credentials?
Refresh tokens might be the best approach. The event obviously needs to be secure since it will have to contain the refresh token.

Resources