In the OpenApi30Config configuration file I have:
import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
#Configuration
#OpenAPIDefinition(info = #Info(title = "Users API", version = "2.0", description = "Users Information"))
#SecurityScheme(name = "bearerAuth", type = SecuritySchemeType.HTTP, bearerFormat = "JWT", scheme = "bearer")
public class OpenApi30Config {
}
In the WebSecurityConfig
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private Environment environment;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.addFilterAfter(new JWTAuthorizationFilter(environment), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/auth").permitAll()
.antMatchers("/open-api/**").permitAll()
.anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("someUser")
.password(passwordEncoder().encode("somePassword"))
.authorities("ADMIN");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Controllers:
AuthController
#RestController
public class AuthController implements EnvironmentAware {
private Environment environment;
#PostMapping("auth")
public User login(#RequestParam("user") String name, #RequestParam("password") String pwd) {
String token = getJWTToken(name);
User user = new User();
user.setName(name);
user.setToken(token);
return user;
}
private String getJWTToken(String username) {
List<GrantedAuthority> grantedAuthorities = AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_USER");
String token = Jwts
.builder()
.setId("softtekJWT")
.setSubject(username)
.claim("authorities",
grantedAuthorities.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()))
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + Long.parseLong(environment.getProperty("millisecond.expiration"))))
.signWith(SignatureAlgorithm.HS512,
environment.getProperty("secret.key").getBytes()).compact();
return "Bearer " + token;
}
#Override
public void setEnvironment(final Environment environment) {
this.environment = environment;
}
}
The UserController:
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
#RestController
#RequestMapping("/api/users")
public class UserController {
#Autowired
private UserServiceIface userService;
#Autowired UserPasswordValidator userPasswordValidator;
#Operation(summary = "list Users", security = #SecurityRequirement(name = "bearerAuth"))
#GetMapping
public ResponseEntity<?> list(){
return ResponseEntity.ok().body(userService.findAll());
}
#Operation(summary = "view User", security = #SecurityRequirement(name = "bearerAuth"))
#GetMapping("/{id}")
public ResponseEntity<?> view(#PathVariable Long id) {
Optional<User> optionalStoredUser = userService.findById(id);
if (!optionalStoredUser.isPresent()) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok().body(optionalStoredUser.get());
}
#Operation(summary = "create User", security = #SecurityRequirement(name = "bearerAuth"))
#PostMapping
public ResponseEntity<?> create(#RequestHeader(value="Authorization") String token,
#Valid #RequestBody User user, BindingResult result) {
userPasswordValidator.validate(user, result);
throwExceptionIfErrors(result);
Optional<User> optionalStoredUser = userService.findByEmail(user.getEmail());
if (optionalStoredUser.isPresent()) {
throw new ExistingMailException(user.getEmail());
}
user.setIsactive(true);
user.setToken(token);
User createdUser = userService.save(user);
return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
}
#Operation(summary = "edit User", security = #SecurityRequirement(name = "bearerAuth"))
#PutMapping("/{id}")
public ResponseEntity<?> edit(#RequestHeader(value="Authorization") String token,
#Valid #RequestBody User user, BindingResult result, #PathVariable Long id) {
userPasswordValidator.validate(user, result);
throwExceptionIfErrors(result);
if (user.getEmail() != null && !user.getEmail().isEmpty()) {
boolean emailUsed = userService.findByEmailAndIdNot(user.getEmail(), id).size() > 0;
if (emailUsed) {
throw new ExistingMailException(user.getEmail());
}
}
Optional<User> optionalStoredUser = userService.findById(id);
if (!optionalStoredUser.isPresent()) {
return ResponseEntity.notFound().build();
}
User editedUser = optionalStoredUser.get();
editedUser.setName(user.getName());
editedUser.setEmail(user.getEmail());
editedUser.setPassword(user.getPassword());
editedUser.setPhones(user.getPhones());
editedUser.setModified(new Date());
editedUser.setIsactive(user.isIsactive());
editedUser.setToken(token);
try {
User updatedUser = userService.save(editedUser);
return ResponseEntity.status(HttpStatus.CREATED).body(updatedUser);
} catch (Exception exp) {
throw new DefaultException(exp.getLocalizedMessage());
}
}
#Operation(summary = "delete User", security = #SecurityRequirement(name = "bearerAuth"))
#DeleteMapping("/{id}")
public ResponseEntity<?> delete(#PathVariable Long id) {
userService.deleteById(id);
return ResponseEntity.noContent().build();
}
private void throwExceptionIfErrors(BindingResult result) {
if (result.hasErrors()) {
FieldError passwordFieldError = result.getAllErrors()
.stream().map(e -> (FieldError) e)
.filter(f -> f.getField().equals("password"))
.findFirst().orElse(null);
if (passwordFieldError != null) {
throw new PatternPasswordException(passwordFieldError.getDefaultMessage());
}
FieldError emailFieldError = result.getAllErrors()
.stream().map(e -> (FieldError) e)
.filter(f -> f.getField().equals("email"))
.findFirst().orElse(null);
if (emailFieldError != null) {
throw new PatternEmailException(emailFieldError.getDefaultMessage());
}
}
}
}
In my pom.xml
<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>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</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-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.13</version>
</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>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<!-- <version>5.8.2</version> -->
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<!-- <version>5.8.2</version> -->
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<!-- <version>5.8.2</version> -->
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<!-- <version>4.2.0</version> -->
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<!-- <version>4.2.0</version> -->
</dependency>
</dependencies>
Now UserControllerTest in the test:
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
#WebMvcTest(UserController.class)
class UserControllerTest {
#Autowired
private MockMvc mvc;
#MockBean
private UserServiceIface userService;
#MockBean
UserPasswordValidator userPasswordValidator;
private List<User> users;
#SuppressWarnings("serial")
#BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
users = new ArrayList<User>() {
{
User user = new User();
user.setId(1L);
user.setName("First");
user.setEmail("i#country.com");
user.setPassword("abc1");
add(user);
user = new User();
user.setId(2L);
user.setName("Second");
user.setEmail("you#country.com");
user.setPassword("abc2");
add(user);
user = new User();
user.setId(3L);
user.setName("Third");
user.setEmail("he#country.com");
user.setPassword("abc3");
add(user);
}
};
}
#Test
void testList() throws Exception {
// given
when(userService.findAll()).thenReturn(users);
// then
mvc.perform(MockMvcRequestBuilders.get("/api/users").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.[0].name").value("First"))
;
}
Running I get:
MockHttpServletResponse:
Status = 403
Error message = Access Denied
Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
EDIT 1:
I changed my test class!
package org.bz.ms.app.usuarios.controllers;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import org.junit.jupiter.api.*;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
#WebMvcTest(UserController.class)
class UserControllerTest {
#Autowired
private WebApplicationContext context;
#Autowired
private MockMvc mvc;
#MockBean
private UserServiceIface userService;
#MockBean
UserPasswordValidator userPasswordValidator;
private List<User> users;
#SuppressWarnings("serial")
#BeforeEach
void setUp() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).apply(springSecurity()).build();
// userDao = mock(UserDao.class); // Manually
MockitoAnnotations.openMocks(this);
users = new ArrayList<User>() {
{
User user = new User();
user.setId(1L);
user.setName("First");
user.setEmail("i#country.com");
user.setPassword("abc1");
add(user);
user = new User();
user.setId(2L);
user.setName("Second");
user.setEmail("you#country.com");
user.setPassword("abc2");
add(user);
user = new User();
user.setId(3L);
user.setName("Third");
user.setEmail("he#country.com");
user.setPassword("abc3");
add(user);
}
};
}
#Test
#WithMockUser
void testList() throws Exception {
// given
when(userService.findAll()).thenReturn(users);
// then
mvc.perform(MockMvcRequestBuilders.get("/api/users").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
// .andExpect(content().contentType(MediaType.APPLICATION_JSON))
// .andExpect(jsonPath("$.[0].name").value("First"))
;
}
}
I have doubts about this injection, but I think that I have: WebSecurityConfig and OpenApi30Config.
#Autowired
private WebApplicationContext context;
and this on setUp method:
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).apply(springSecurity()).build();
How to test my controller including the security?
It is because you configure the API can only be accessed by an authenticated user but now you are sending a request as an anonymous user and hence it return 403 access denied error.
The most simplest way is to add #WithMockUser to the test method. It will emulate the test are running with an authenticated user . It also allows to configure what the authorities or role do this user has :
#Test
#WithMockUser
void testList() throws Exception {
// given
when(userService.findAll()).thenReturn(users);
// then
mvc.perform(MockMvcRequestBuilders.get("/api/users").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.[0].name").value("First"))
;
}
Please note that #WithMockUser requires to add the spring-security-test dependency :
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
Related
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();
I have tried to change the swagger URL, right now i have "http://localhost:8080/context-root/rest/swagger-ui.html", i want it to be "http://localhost:8080/swagger". I tried using the DOCKET.Host("swagger"), but browser is spinning. And its not loading screen.
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.4.0</version>
</dependency>
Can any one help with that?
Have you tried Path Provider ?
#Configuration
#EnableSwagger2
#Profile({"!production"})
public class SwaggerConfiguration extends WebMvcConfigurerAdapter {
#Autowired
private ServletContext servletContext;
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.host("localhost")
.pathProvider(new RelativePathProvider(servletContext) {
#Override
public String getApplicationBasePath() {
return "/swagger";
}
})
.protocols(new HashSet<String>(Arrays.asList(protocols)))
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
}
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.
I have a JPA web application with some integration tests against the JPA repositories. There are no integration tests against the Neo4J repositories yet.
Now, I have added some Neo4J functionality to this existing JPA web application.
I'm this now using Neo4J repositories, alongside JPA repositories. My entities and repositories are named differently and are sitting in different packages.
My tests all extend the following class:
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { ApplicationConfiguration.class, WebSecurityTestConfiguration.class, WebConfiguration.class })
#Transactional
public abstract class AbstractControllerTest {
...
}
The tests run fine when the application configuration does not have any
Neo4J configuration:
#Configuration
#ComponentScan(basePackages = { "it.robot.rest.config" })
#Import({ DatabaseConfiguration.class, Log4jWeb.class })
public class ApplicationConfiguration {
}
But they error on an exception when adding the Neo4J configuration:
#Configuration
#ComponentScan(basePackages = { "it.robot.rest.config" })
#Import({ DatabaseConfiguration.class, Neo4JRepositoryConfiguration.class, Log4jWeb.class })
public class ApplicationConfiguration {
}
The exception is:
javax.persistence.TransactionRequiredException: no transaction is in progress
Here is the Neo4J configuration (I tried both Neo4jConfiguration and CrossStoreNeo4jConfiguration classes and I get the same exception):
#Configuration
#EnableNeo4jRepositories(basePackages = { "it.robot.data.neo4j.repository" } )
#EnableTransactionManagement
#ComponentScan(basePackages = { "it.robot.data.neo4j.service" })
public class Neo4JRepositoryConfiguration extends Neo4jConfiguration {
public static final String URL = "http://localhost:7474/db/data/";
public static final String LOGIN = "neo4j";
public static final String PASSWORD = "mypassword";
Neo4JRepositoryConfiguration() {
setBasePackage("it.robot.data.neo4j.domain");
}
#Bean
GraphDatabaseService graphDatabaseService() {
return new SpringCypherRestGraphDatabase(URL, LOGIN, PASSWORD);
}
}
Here is the JPA configuration is:
#Configuration
#Import({ JpaService.class, Log4j.class })
#EnableTransactionManagement
#ComponentScan(basePackages = { "it.robot.data.config" })
#EnableJpaRepositories(basePackages = { "it.robot.data.jpa" }, repositoryFactoryBeanClass = it.robot.data.jpa.repository.GenericRepositoryFactoryBean.class)
public class DatabaseConfiguration {
...
}
It looks like the Neo4jConfiguration class transaction manager has the same name ("transactionManager") as the JPA transaction manager, and overrides it.
I would content myself with Neo4J using the JPA transaction manager provided by Spring but I wonder if that is possible.
Some additional information...
I'm using spring-data-neo4j and spring-data-neo4j-rest version 3.3.2.RELEASE
I'm using a server Neo4J database and not an embedded one and of course the Neo4J server is started.
I disabled the authentication on the database since it was standing in my way and my curl request didn't seem to update the password:
curl -H "Accept:application/json"
-H "Content-Type: application/json"
"http://localhost:7474/user/neo4j/password"
-X POST -d "{ \"password\" : \"myownpassword\" }"
The only user I know of doesn't seem to be too vocal:
stephane#stephane-ThinkPad-X301:~> curl -H "Accept:application/json" -H "Content-Type: application/json" "http://localhost:7474/user/neo4j"
stephane#stephane-ThinkPad-X301:~>
stephane#stephane-ThinkPad-X301:~>
I have not created any "schema/structure" in the graph and am not sure if I should do.
The Neo4J entities:
#NodeEntity
#SequenceGenerator(name = "id_generator", sequenceName = "sq_id_part")
public class Neo4JPart extends BaseEntity {
#Column(nullable = false)
private String name;
#Column(nullable = false, unique = true)
private String serialNumber;
private Integer weight;
#ManyToOne
#JoinColumn(name = "manufacturer_id", nullable = false)
private Neo4JManufacturer manufacturer;
#Fetch
#RelatedTo(type = "part", direction = Direction.BOTH)
public Set<Neo4JPart> parts;
public Neo4JPart() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(String serialNumber) {
this.serialNumber = serialNumber;
}
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
public Neo4JManufacturer getManufacturer() {
return manufacturer;
}
public void setManufacturer(Neo4JManufacturer manufacturer) {
this.manufacturer = manufacturer;
}
public Set<Neo4JPart> getParts() {
return parts;
}
public void setParts(Set<Neo4JPart> parts) {
this.parts = parts;
}
public String toString() {
String results = name + "'s compatible parts include\n";
if (parts != null) {
for (Neo4JPart part : parts) {
results += "\t- " + part.name + "\n";
}
}
return results;
}
}
#MappedSuperclass
public class BaseEntity {
#GraphId
#GeneratedValue(strategy = GenerationType.AUTO, generator = "id_generator")
#Column(name = "id")
private Long id;
#Version
#Column(nullable = false)
private int version;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public int getVersion() {
return this.version;
}
public void setVersion(int version) {
this.version = version;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (this.id == null || obj == null || !(this.getClass().equals(obj.getClass()))) {
return false;
}
BaseEntity that = (BaseEntity) obj;
return this.id.equals(that.getId());
}
#Override
public int hashCode() {
return id == null ? 0 : id.hashCode();
}
}
And the Neo4J repositories:
public interface Neo4JPartRepository extends GraphRepository<Neo4JPart> {
public Neo4JPart findByName(String name);
public Neo4JPart findBySerialNumber(String serialNumber);
public Page<Neo4JPart> findByManufacturer(#Param("manufacturer") Neo4JManufacturer manufacturer, Pageable page);
public List<Neo4JPart> findByManufacturer(#Param("manufacturer") Neo4JManufacturer manufacturer);
public Page<Neo4JPart> findByPartsName(String name, Pageable page);
}
public interface Neo4JManufacturerRepository extends GraphRepository<Neo4JManufacturer> {
Neo4JManufacturer findByName(String name);
}
The Maven dependencies are:
<org.springframework.version>4.1.2.RELEASE</org.springframework.version>
<hibernate.version>4.3.6.Final</hibernate.version>
<dependencies>
<dependency>
<groupId>com.thalasoft</groupId>
<artifactId>toolbox</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.2.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.172</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.6.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-core</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>1.10.2.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>org.lazyluke</groupId>
<artifactId>log4jdbc-remix</artifactId>
<version>0.2.7</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.jadira.usertype</groupId>
<artifactId>usertype.jodatime</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.jasypt</groupId>
<artifactId>jasypt</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>1.6.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
<version>3.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j-rest</artifactId>
<version>3.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j-cross-store</artifactId>
<version>3.3.2.RELEASE</version>
</dependency>
</dependencies>
I tried to upgrade to the 3.4.0 version of the newly available release on search.maven.org but the build now gives the following exception:
AnnotationFormatError: Invalid default: public abstract java.lang.Class org.springframework.data.neo4j.config.EnableNeo4jRepositories.repositoryBaseClass()
I could see nothing about that repositoryBaseClass in the reference documentation http://docs.spring.io/spring-data/neo4j/docs/3.4.0.RELEASE/reference/pdf/spring-data-neo4j-reference.pdf
The source code Javadoc only says:
Configure the repository base class to be used to create repository proxies for this particular configuration.
And that left me scratching my head wondering what is a repository proxy and if one is required in my case.
I could solve the issue with the solution provided at Implementing Spring ChainedTransactionManager according to the "best efforts 1PC" pattern with a chained transaction manager, following a tip by Simon at How do I properly set up cross-store persistence using Spring Data JPA + Neo4j?
I just had to change my Neo4j configuration. I didn't even have to touch anything in the other JPA transaction manager.
Here is my Neo4j configuration:
#EnableNeo4jRepositories(basePackages = { "it.robot.data.neo4j.repository" })
#EnableTransactionManagement
#ComponentScan(basePackages = { "it.robot.data.neo4j.service" })
public class Neo4JRepositoryConfiguration extends Neo4jConfiguration {
private static Logger logger = LoggerFactory.getLogger(Neo4JRepositoryConfiguration.class);
public static final String URL = "http://localhost:7474/db/data/";
public static final String LOGIN = "neo4j";
public static final String PASSWORD = "xxxxx";
Neo4JRepositoryConfiguration() {
setBasePackage("it.robot.data.neo4j.domain");
}
#Bean
GraphDatabaseService graphDatabaseService() {
return new SpringCypherRestGraphDatabase(URL, LOGIN, PASSWORD);
}
#Autowired
LocalContainerEntityManagerFactoryBean entityManagerFactory;
#Override
public PlatformTransactionManager neo4jTransactionManager(
GraphDatabaseService graphDatabaseService) {
return new ChainedTransactionManager(
new JpaTransactionManager(entityManagerFactory.getObject()),
new JtaTransactionManagerFactoryBean(graphDatabaseService).getObject());
}
}
I am new to Spring Boot, and I am trying to configure OAuth 2.0. The problem I am having at this moment is that I keep getting the following message when I attempt to request for an access token:
{
"error": "invalid_grant",
"error_description": "Bad credentials"
}
The error message in the Spring Boot console says that the user cannot be found.
: Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
: User 'stromero' not found
: Returning cached instance of singleton bean 'authenticationAuditListener'
I have implemented a custom user that has already been saved to a database using JPA, I am unable to figure why Spring Security cannot find this user, it may an issue with my logic or configuration. If someone with more experience can look at my code and perhaps guide me to the right direction, that would be greatly appreciated.
This is the HTTP Request:
POST /oauth/token HTTP/1.1
Host: localhost:8181
Authorization: Basic YnJvd3NlcjpzZWNyZXQ=
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
username=stromero&password=password&client_id=browser&client_secret=secret&grant_type=password
These are the classes that I used to implement my custom user and OAuth 2.0
#Repository
public interface UserRepository extends CrudRepository<CustomUser, String> {
public CustomUser findByUsername(String name);
}
Below is the custom user I have created
#Entity
#Table (name = "custom_user")
public class CustomUser {
#Id
#Column(name = "id", nullable = false, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "username", unique=true, nullable = false)
private String username;
#Column(name = "password", nullable = false)
private String password;
#ElementCollection
private List<String> roles = new ArrayList<>();
public List<String> getRoles() {
return roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Below is a customdetails service that reads the user information from the database and returns it as a UserDetails Object
#Service
#Transactional(readOnly = true)
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
CustomUser customUser = userRepository.findByUsername(s);
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
return new User(
customUser .getUsername(),
customUser .getPassword().toLowerCase(),
enabled,
accountNonExpired,
credentialsNonExpired,
accountNonLocked,
getAuthorities(customUser.getRoles()));
}
public Collection<? extends GrantedAuthority> getAuthorities(List<String> roles) {
List<GrantedAuthority> authList = getGrantedAuthorities(roles);
return authList;
}
public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
}
The class below is a data structure that holds both the UserDetailsService and ClientDetailsService
public class ClientAndUserDetailsService implements UserDetailsService,
ClientDetailsService {
private final ClientDetailsService clients;
private final UserDetailsService users;
private final ClientDetailsUserDetailsService clientDetailsWrapper;
public ClientAndUserDetailsService(ClientDetailsService clients,
UserDetailsService users) {
super();
this.clients = clients;
this.users = users;
clientDetailsWrapper = new ClientDetailsUserDetailsService(this.clients);
}
#Override
public ClientDetails loadClientByClientId(String clientId)
throws ClientRegistrationException {
return clients.loadClientByClientId(clientId);
}
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
UserDetails user = null;
try{
user = users.loadUserByUsername(username);
}catch(UsernameNotFoundException e){
user = clientDetailsWrapper.loadUserByUsername(username);
}
return user;
}
}
The class below is my configuration for OAuth 2.0 using Spring Boot
#Configuration
public class OAuth2SecurityConfiguration {
#Configuration
#EnableWebSecurity
protected static class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
protected void registerAuthentication(
final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
#Configuration
#EnableResourceServer
protected static class ResourceServer extends
ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/oauth/token").anonymous();
// Require all GET requests to have client "read" scope
http.authorizeRequests().antMatchers(HttpMethod.GET, "/**")
.access("#oauth2.hasScope('read')");
// Require all POST requests to have client "write" scope
http.authorizeRequests().antMatchers(HttpMethod.POST,"/**")
.access("#oauth2.hasScope('write')");
}
}
#Configuration
#EnableAuthorizationServer
#Order(Ordered.LOWEST_PRECEDENCE - 100)
protected static class AuthorizationServer extends
AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
private ClientAndUserDetailsService combinedService;
public AuthorizationServer() throws Exception {
ClientDetailsService clientDetailsService = new InMemoryClientDetailsServiceBuilder()
.withClient("browser")
.secret("secret")
.authorizedGrantTypes("password")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read","write")
.resourceIds("message")
.accessTokenValiditySeconds(7200)
.and()
.build();
// Create a series of hard-coded users.
UserDetailsService userDetailsService = new CustomUserDetailsService();
combinedService = new ClientAndUserDetailsService(clientDetailsService, userDetailsService);
}
#Bean
public ClientDetailsService clientDetailsService() throws Exception {
return combinedService;
}
#Bean
public UserDetailsService userDetailsService() {
return combinedService;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.withClientDetails(clientDetailsService());
}
}
}
Below is my pom.xml file
<properties>
<tomcat.version>8.0.8</tomcat.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- Postgres JDBC Driver -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.2-1002-jdbc4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Hibernate validator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>17.0</version>
</dependency>
</dependencies>
Yeah, I had the same issue... wanted to use JPA's UserDetailsService but the same problem - user couldn't be found... got resolved it eventually, thanks to Dave Syer's OAuth2 samples on GitHub.
The problem seem to be in authenticationManager instance autowired in #EnableAuthorizationServer AuthorizationServer class. AuthenticationManager is autowired there and seems to initialize with default DAOAuthenticationProvider, and for some reason it doesn't use custom JPA UserDetailsService we initialize authenticationManager with in WebSecurityConfiguration.
In Dave Syer samples authenticationManager is exposed as a bean in WebSecurityConfiguration:
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
then in AuthorizationServer we autowire authenticationManager as follows:
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
Once I did it I finally managed to get my user authenticated against my customer JPA user repository.
I faced the same issue and spent hours investigating the case. As a workaround, if you are using Spring Boot version 1.1.8.RELEASE, downgrade it to 1.0.2.RELEASE. Things went fine that way but I did not investigate yet the reasons of compatibility issue with Spring Boot version 1.1.8.RELEASE.
InitializeUserDetailsBeanManagerConfigurer has default order as
static final int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE - 5000;
So it Initializee DaoAuthenticationProvider before custom one.
#Order(-5001)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { ... }