I am relatively new to the Spring boot frame work. I had a basic web application built in Angular with Spring boot connected and to a Mongodb. The application allowed users to add todo lists and register for the the website. When the application started it returned the todolists stored in mongodb to the view. The user could register, and there details were stored in a Mongo repository.
When I added and implemented spring security I got the error message
Circular view path [login]: would dispatch back to the current handler URL [/login] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
What I want to happen is, when the webapp loads, I want the index.html to be injected with todo.html. Then if a user logs in they will be directed to another page or some Ui feature to become available. At the moment I am stuck in this Circular view pathloop.
I have looked through the different answers but I still am confused as to what exactly is causing the issue. I believe it is in the WebSecurityConfig class
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
UserDetailsService userDS;
#Override
protected void configure(HttpSecurity http) throws Exception{
http
.authorizeRequests()
.antMatchers("/api/todos/*").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDS);
}
#Override
protected UserDetailsService userDetailsService() {
return userDS;
}
}
AuthUserDetailsService
#Repository
public class AuthUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository users;
private org.springframework.security.core.userdetails.User userdetails;
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
// TODO Auto-generated method stub
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
todoapp.models.User user = getUserDetail(username);
userdetails = new User (user.getUsername(),
user.getPassword(),
enabled,
accountNonExpired,
credentialsNonExpired,
accountNonLocked,
getAuthorities(user.getRole())
);
return userdetails;
}
public List<GrantedAuthority> getAuthorities(Integer role) {
List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
if (role.intValue() == 1) {
authList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
} else if (role.intValue() == 2) {
authList.add(new SimpleGrantedAuthority("ROLE_USER"));
}
return authList;
}
private todoapp.models.User getUserDetail(String username){
todoapp.models.User user = users.findByUsername(username);
return user;
}
}
TodoController
#RestController
#RequestMapping("/api/todos")
public class TodoController {
#Autowired
TodoRepository todoRepository;
#RequestMapping(method=RequestMethod.GET)
public List<Todo> getAllTodos() {
return todoRepository.findAll();
}
#RequestMapping(method=RequestMethod.POST)
public Todo createTodo(#Valid #RequestBody Todo todo) {
return todoRepository.save(todo);
}
#RequestMapping(value="{id}", method=RequestMethod.GET)
public ResponseEntity<Todo> getTodoById(#PathVariable("id") String id) {
Todo todo = todoRepository.findOne(id);
if(todo == null) {
return new ResponseEntity<Todo>(HttpStatus.NOT_FOUND);
} else {
return new ResponseEntity<Todo>(todo, HttpStatus.OK);
}
}
#RequestMapping(value="{id}", method=RequestMethod.PUT)
public ResponseEntity<Todo> updateTodo(#Valid #RequestBody Todo todo, #PathVariable("id") String id) {
Todo todoData = todoRepository.findOne(id);
if(todoData == null) {
return new ResponseEntity<Todo>(HttpStatus.NOT_FOUND);
}
todoData.setTitle(todo.getTitle());
todoData.setCompleted(todo.getCompleted());
Todo updatedTodo = todoRepository.save(todoData);
return new ResponseEntity<Todo>(updatedTodo, HttpStatus.OK);
}
#RequestMapping(value="{id}", method=RequestMethod.DELETE)
public void deleteTodo(#PathVariable("id") String id) {
todoRepository.delete(id);
}
}
RecourceController
#Configuration
public class ResourceController extends WebMvcConfigurerAdapter{
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/api/todos").setViewName("home");
registry.addViewController("/register").setViewName("register");
registry.addViewController("/login").setViewName("login");
}
}
Any help will be greatly appreciated.
This is the Project Layout.
You forgot to add .html to your view names:
registry.addViewController("/").setViewName("app/views/index.html");
registry.addViewController("/api/todos").setViewName("app/views/home.html");
registry.addViewController("/register").setViewName("app/views/register.html");
registry.addViewController("/login").setViewName("app/views/login.html");
Spring Boot registers a ResourceHttpRequestHandler which is capable of resolving static resources under static folder.
Because you set login as view name ResourceHttpRequestHandler tries to load static/login which apparently does not exist.
Change it to app/views/login.html so that static/login becomes static/app/views/login.html.
Related
I was developing a backend with spring boot for the first time. I chose email password login over username password login which is the default behavior of spring boot.
I have two objects: AppUser and User. AppUser is used for authentication and User holds every other data necessary for the application.
AppUser:
public class AppUser implements UserDetails {
#Autowired
AuthRepository repository;
#Id
private String id;
#NonNull
#Indexed(unique = true)
#Field(value = "email")
String email;
#NonNull
#Field(value = "password")
String password;
#Field(value = "authorities")
private List<Role> authorities;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> auth = new HashSet<>();
authorities.forEach(index -> auth.add(new SimpleGrantedAuthority(String.valueOf(index.getRole()))));
return auth;
}
#Override
public String getUsername() {
return email;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
AuthController looks like:
#RestController
#RequestMapping("/api/v1/Auth")
public class AuthController {
Logger logger = LoggerFactory.getLogger(AuthController.class);
#Autowired
UserDetailsManager userDetailsManager;
#Autowired
UserService userService;
#Autowired
AuthService authService;
#Autowired
TokenGenerator tokenGenerator;
#Autowired
DaoAuthenticationProvider daoAuthenticationProvider;
#Autowired
#Qualifier("jwtRefreshTokenAuthProvider")
JwtAuthenticationProvider refreshTokenAuthProvider;
#PostMapping("/SignUp")
public ResponseEntity signup(#RequestBody SignUpRequest signUpRequest) {
logger.info(signUpRequest.getUsername());
User user = new User(
signUpRequest.getEmail(),
signUpRequest.getPassword(),
signUpRequest.getUsername(),
signUpRequest.getFirstName(),
signUpRequest.getLastName(),
signUpRequest.getCountry(),
null,
false,
false
);
AppUser appUser = new AppUser(
signUpRequest.getEmail(),
signUpRequest.getPassword()
);
boolean alreadyExists = authService.existsByEmail(signUpRequest.getEmail());
if(alreadyExists){
return ResponseEntity.status(HttpStatus.CONFLICT).body("Email Already Exists");
}else{
userDetailsManager.createUser(appUser);
User newUser = userService.addUser(user);
Authentication authentication = UsernamePasswordAuthenticationToken.authenticated(appUser, signUpRequest.getPassword(), Collections.EMPTY_LIST);
return ResponseEntity.ok(tokenGenerator.signup(authentication, newUser));
}
}
#PostMapping("/SignIn")
public ResponseEntity signin(#RequestBody SignInRequest signInRequest) {
logger.info(signInRequest.getEmail());
logger.info(signInRequest.getPassword());
Authentication authentication = daoAuthenticationProvider.authenticate(UsernamePasswordAuthenticationToken.unauthenticated(signInRequest.getEmail(), signInRequest.getPassword()));
logger.info(authentication.getCredentials().toString());
User user = new User();
return ResponseEntity.ok(tokenGenerator.signin(authentication, user));
}
}
The issue is with signin. SignUp works fine when I am providing email and password. Login is not working.
I am quite new so have zero idea where the issue is happening. Any reference code with explanation will help a lot.
Github Link
N.B: using spring boot, mongodb, oauth2
In my understanding based on your WebSecurity.class, your are using OAuth2 authentication.
What I would do is create 2 Configurations inside the same class with a different Order and in the second one a custom AuthenticationProvider. This was Spring will try to authenticate first based on the first class and then on the second one
#Configuration
#EnableWebSecurity
public class SecurityConfiguration {
//here we set as the first in order the below config if this fails the next one in order will be used until any of them succeeds
#Configuration
#Order(1)
public static class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private MyLoginRequestFilter myLoginRequestFilter ;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
http
.antMatchers("/api/v1/Auth/*").permitAll()
.addFilterBefore(myLoginRequestFilter, UsernamePasswordAuthenticationFilter.class )
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.cors()
.disable()
.csrf()
.disable()
;
}
}
Then insert your class below with Order 2 and then close the whole file/class
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Slf4j
#Order(2)
//this is your class that is uploaded in the git. We actually encapsulate inside a generic Security Config class
public class WebSecurity {
#Autowired
JwtToUserConverter jwtToUserConverter;
...
}
} //to close the java class that was started in the previous block of code
You could even add a new function that will handle the HttpSecurity item so that you dont duplicate your code:
public HttpSecurity myHandler(HttpSecurity http) {
return http
.authorizeHttpRequests((authorize) -> authorize
.antMatchers("/api/v1/Auth/*").permitAll()
.antMatchers("/api/v1/Home/*").permitAll()
.anyRequest().authenticated()
)
.csrf().disable()
.cors().disable()
;
}
Note that in the Order(1) example, I have added a filter, but you can leave it without a filter and add a custom AuthenticationProvider with inside the class:
#Autowired
private YourAuthenticationProvider authenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
YourAuthenticationProvider could look like
#Component
public class YourAuthenticationProvider implements AuthenticationProvider {
#Autowired
AuthService authService; //need to move here the authenticate method from the controller Authentication authentication = daoAuthenticationProvider.authenticate(UsernamePasswordAuthenticationToken.unauthenticated(signInRequest.getEmail(), signInRequest.getPassword()));
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
final String email = authentication.getName();
final Object credentials = authentication.getCredentials();
if ( credentials == null ) {
return authentication;
}
//I suggest using a password encoder to save the password and then check it with PasswordEncoder (you could create a bean)
final Authentication auth = authService.authenticate(email, credentials.toString());
if (auth != null) {
return auth;
}
throw new BadCredentialsException( "Wrong username and/or password" );
}
}
Hope these help
I am building a Multitenant saas application using single database with multiple schema; one schema per client. I am using Spring Boot 2.1.5, Hibernate 5.3.10 with compatible spring data jpa and postgres 11.2.
I have followed this blogpost https://dzone.com/articles/spring-boot-hibernate-multitenancy-implementation.
Tried debugging the code, below are my findings:
* For the default schema provided in the datasource configuration, hibernate properly validates schema. It creates the tables/entity in the default schema which are missing or new.
* Tenant Identifier is properly resolved and hibernate builds a session using this tenant.
I have uploaded the code in below repo :
https://github.com/naveentulsi/multitenant-lithium
Some important classes I have added here.
#Component
#Log4j2
public class MultiTenantConnectionProviderImpl implements
MultiTenantConnectionProvider {
#Autowired
DataSource dataSource;
#Override
public Connection getAnyConnection() throws SQLException {
return dataSource.getConnection();
}
#Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
#Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
if (!StringUtils.isEmpty(tenantIdentifier)) {
String setTenantQuery = String.format(AppConstants.SCHEMA_CHANGE_QUERY, tenantIdentifier);
connection.createStatement().execute(setTenantQuery);
final ResultSet resultSet = connection.createStatement().executeQuery("select current_schema()");
if(resultSet != null){
final String string = resultSet.getString(1);
log.info("Current Schema" + string);
}
System.out.println("Statement execution");
} else {
connection.createStatement().execute(String.format(AppConstants.SCHEMA_CHANGE_QUERY, AppConstants.DEFAULT_SCHEMA));
}
} catch (SQLException se) {
throw new HibernateException(
"Could not change schema for connection [" + tenantIdentifier + "]",
se
);
}
return connection;
}
#Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
try {
String Query = String.format(AppConstants.DEFAULT_SCHEMA, tenantIdentifier);
connection.createStatement().executeQuery(Query);
} catch (SQLException se) {
throw new HibernateException(
"Could not change schema for connection [" + tenantIdentifier + "]",
se
);
}
connection.close();
}
#Override
public boolean supportsAggressiveRelease() {
return true;
}
#Override
public boolean isUnwrappableAs(Class unwrapType) {
return false;
}
#Override
public <T> T unwrap(Class<T> unwrapType) {
return null;
}
}
#Configuration
#EnableJpaRepositories
public class ApplicationConfiguration implements WebMvcConfigurer {
#Autowired
JpaProperties jpaProperties;
#Autowired
TenantInterceptor tenantInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tenantInterceptor);
}
#Bean
public DataSource dataSource() {
return DataSourceBuilder.create().username(AppConstants.USERNAME).password(AppConstants.PASS)
.url(AppConstants.URL)
.driverClassName("org.postgresql.Driver").build();
}
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, MultiTenantConnectionProviderImpl multiTenantConnectionProviderImpl, CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
properties.put("hibernate.hbm2ddl.auto", "update");
properties.put("hibernate.ddl-auto", "update");
properties.put("hibernate.jdbc.lob.non_contextual_creation", "true");
properties.put("show-sql", "true");
properties.put("hikari.maximum-pool-size", "3");
properties.put("hibernate.default_schema", "master");
properties.put("maximum-pool-size", "2");
if (dataSource instanceof HikariDataSource) {
((HikariDataSource) dataSource).setMaximumPoolSize(3);
}
properties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
properties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProviderImpl);
properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
properties.put(Environment.FORMAT_SQL, true);
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.saas");
em.setJpaVendorAdapter(jpaVendorAdapter());
em.setJpaPropertyMap(properties);
return em;
}
}
#Component
public class TenantResolver implements CurrentTenantIdentifierResolver {
private static final ThreadLocal<String> TENANT_IDENTIFIER = new ThreadLocal<>();
public static void setTenantIdentifier(String tenantIdentifier) {
TENANT_IDENTIFIER.set(tenantIdentifier);
}
public static void reset() {
TENANT_IDENTIFIER.remove();
}
#Override
public String resolveCurrentTenantIdentifier() {
String currentTenant = TENANT_IDENTIFIER.get() != null ? TENANT_IDENTIFIER.get() : AppConstants.DEFAULT_SCHEMA;
return currentTenant;
}
#Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
On successful injection of TenantId by the TenantResolver, the entityManager should be able to store the entities into the corresponding tenant schema in database. That is, if we create an object of an entity and persist same in db, it should be successfully saved in db. But in my case, entities are not getting saved into any schema other than the default one.
Update 1: I was able to do multi-tenant schema switching using mysql 8.0.12. Still not able to do it with postgres.
you should be using AbstractRoutingDataSource to achieve this, it does all the magic behind the scenes, there are many examples online and you can find one at https://www.baeldung.com/spring-abstract-routing-data-source
In your Class "ApplicationConfiguration.java";
You have to remove this "properties.put("hibernate.default_schema", "master");", Why because of when ever your changing the schema it's able to change but when it's reach this line again and again set the default schema
I hope you got the answer
Thank you all
Take care!
I am trying to secure Rest APIs using spring boot and JWT. Right now I have been able to piece together pieces of the configuration to get a token generated with a hard coded username and password. I would like my User class and repository to be used instead.
I have been able to hardcode a user here
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password(passwordEncoder().encode("password"))
.authorities("ROLE_USER");
}
Should I be pointing this to my UserDetailsService? How would I do that?
#Service
public class UserSecurityService implements UserDetailsService {
private static final Logger LOG = LoggerFactory.getLogger(UserSecurityService.class);
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (null == user) {
LOG.warn("username not found");
throw new UsernameNotFoundException("Username" + username + "not found");
}
return user;
}
}
For UserDetailsService, you need DaoAuthenticationProvider to handle any authentication requests.
To do so:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(encoder());
}
// you shouldn't use plain text
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
above internally configures a DaoAuthenticationProvider. Alternatively, you can define a bean to inject:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(encoder());
return authProvider;
}
Hi I am new to mongodb and spring security.I am trying to create an token key basen authorisation for login the below is the link which I followed to create project set up:-
Spring Security with in Memory allocation
The following are the code changes I have made: -
1] Resource Server.
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "my_rest_api";
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID).stateless(false);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.
anonymous().disable()
.requestMatchers().antMatchers("/user/**")
.and().authorizeRequests()
.antMatchers("/user/**").access("hasRole('ADMIN')")
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
2] Authorization Server: -
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private static String REALM="MY_OAUTH_REALM";
#Autowired
private TokenStore tokenStore;
#Autowired
private UserApprovalHandler userApprovalHandler;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("my-trusted-client")
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read", "write", "trust")
.secret("secret")
.accessTokenValiditySeconds(120).//Access token is only valid for 2 minutes.
refreshTokenValiditySeconds(600);//Refresh token is only valid for 10 minutes.
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore).userApprovalHandler(userApprovalHandler)
.authenticationManager(authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.realm(REALM+"/client");
}
}
3] Security Configuration: -
#Configuration
#EnableWebSecurity
public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private ClientDetailsService clientDetailsService;
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("bill").password("abc123").roles("ADMIN").and()
.withUser("bob").password("abc123").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/oauth/token").permitAll();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Bean
#Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){
TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore);
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
handler.setClientDetailsService(clientDetailsService);
return handler;
}
#Bean
#Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
}
4] Method Security: -
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Autowired
private OAuth2SecurityConfiguration securityConfig;
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
5] Rest Controller: -
#RestController
public class HelloWorldRestController {
#Autowired
UserService userService; //Service which will do all data retrieval/manipulation work
//-------------------Retrieve All Users--------------------------------------------------------
#RequestMapping(value = "/user/", method = RequestMethod.GET)
public ResponseEntity<List<User>> listAllUsers() {
List<User> users = userService.findAllUsers();
if(users.isEmpty()){
return new ResponseEntity<List<User>>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND
}
return new ResponseEntity<List<User>>(users, HttpStatus.OK);
}
//-------------------Retrieve Single User--------------------------------------------------------
#RequestMapping(value = "/user/{id}", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE,MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<User> getUser(#PathVariable("id") long id) {
System.out.println("Fetching User with id " + id);
User user = userService.findById(id);
if (user == null) {
System.out.println("User with id " + id + " not found");
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<User>(user, HttpStatus.OK);
}
//-------------------Create a User--------------------------------------------------------
#RequestMapping(value = "/user/", method = RequestMethod.POST)
public ResponseEntity<Void> createUser(#RequestBody User user, UriComponentsBuilder ucBuilder) {
System.out.println("Creating User " + user.getName());
if (userService.isUserExist(user)) {
System.out.println("A User with name " + user.getName() + " already exist");
return new ResponseEntity<Void>(HttpStatus.CONFLICT);
}
userService.saveUser(user);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ucBuilder.path("/user/{id}").buildAndExpand(user.getId()).toUri());
return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}
//------------------- Update a User --------------------------------------------------------
#RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
public ResponseEntity<User> updateUser(#PathVariable("id") long id, #RequestBody User user) {
System.out.println("Updating User " + id);
User currentUser = userService.findById(id);
if (currentUser==null) {
System.out.println("User with id " + id + " not found");
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
}
currentUser.setName(user.getName());
currentUser.setAge(user.getAge());
currentUser.setSalary(user.getSalary());
userService.updateUser(currentUser);
return new ResponseEntity<User>(currentUser, HttpStatus.OK);
}
//------------------- Delete a User --------------------------------------------------------
#RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
public ResponseEntity<User> deleteUser(#PathVariable("id") long id) {
System.out.println("Fetching & Deleting User with id " + id);
User user = userService.findById(id);
if (user == null) {
System.out.println("Unable to delete. User with id " + id + " not found");
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
}
userService.deleteUserById(id);
return new ResponseEntity<User>(HttpStatus.NO_CONTENT);
}
//------------------- Delete All Users --------------------------------------------------------
#RequestMapping(value = "/user/", method = RequestMethod.DELETE)
public ResponseEntity<User> deleteAllUsers() {
System.out.println("Deleting All Users");
userService.deleteAllUsers();
return new ResponseEntity<User>(HttpStatus.NO_CONTENT);
}
}
what is the code change i need to make such that I can make login authentication using Mongdb document by name "user" instead of inmemory authentication.
I have Spring Boot application, everything works fine until I implement spring security in front of my application. This is a RESTful api that has a token based authentication. What's even more weird it works (!) intermittently - by intermittently I mean restarting the application will return the right responses such as 401/403 if unauthenticated and other codes if user is authorized to access them. This is being deployed into WebLogic.
2017-01-05 14:12:51.164 WARN 11252 --- [ (self-tuning)'] o.s.web.servlet.PageNotFound : No mapping found for HTTP request with URI [/user] in DispatcherServlet with name 'dispatcherServlet'
WebApplication.java
#SpringBootApplication(exclude = { SecurityAutoConfiguration.class })
public class WebApplication extends SpringBootServletInitializer implements WebApplicationInitializer {
public static void main(String[] args) {
Object[] sources = new Object[2];
sources[0] = WebConfiguration.class;
sources[1] = WebSecurityConfiguration.class;
SpringApplication.run(sources, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(WebApplication.class);
}
}
WebConfiguration.java
#Configuration
#ComponentScan(basePackages = { "com.controller", "com.service", "com.dao"})
#EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class })
public class WebConfiguration extends WebMvcConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(WebConfiguration.class);
/**
* Setup a simple strategy: use all the defaults and return XML by default
* when not sure.
*/
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON).mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}
#Bean(name = "entityManagerFactory")
public EntityManagerFactory getQmsEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPersistenceUnitName(Config.PERSISTENCE_UNIT_NAME);
em.setPersistenceXmlLocation("META-INF/persistence.xml");
em.setDataSource(getDataSource());
em.setJpaVendorAdapter(getJpaHibernateVendorAdapter());
em.afterPropertiesSet();
return em.getObject();
}
#Bean
public HibernateJpaVendorAdapter getJpaHibernateVendorAdapter() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setShowSql(true);
// adapter.setDatabase("ORACLE");
adapter.setDatabasePlatform("org.hibernate.dialect.Oracle10gDialect");
return adapter;
}
#Bean(name="dataSource", destroyMethod = "")
//http://stackoverflow.com/questions/19158837/weblogic-datasource-disappears-from-jndi-tree
#Qualifier("dataSource")
#Profile("weblogic")
public DataSource dataSource() {
DataSource dataSource = null;
JndiTemplate jndi = new JndiTemplate();
try {
dataSource = (DataSource) jndi.lookup("jdbc/datasource");
} catch (NamingException e) {
logger.error("NamingException for jdbc/datasource", e);
}
return dataSource;
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedMethods("*");
}
};
}
}
WebSecurityConfiguration.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#ComponentScan({
"com.subject",
"com.custom"
})
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private StatelessAuthenticationFilter statelessAuthenticationFilter;
#Autowired
private RestAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private CusAuthenticationProvider cusAuthenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(cusAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.securityContext()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.addFilterBefore(statelessAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler);
}
}
StatelessAuthenticationFilter.java
#Component
public class StatelessAuthenticationFilter extends OncePerRequestFilter {
#Inject
private SubjectLookupService subjectLookupService;
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authenticateUser(request));
filterChain.doFilter(request, response);
}
private Authentication authenticateUser(HttpServletRequest request) {
try {
String application = StringUtils.defaultString(request.getParameter("application"));
UserInfo me = subjectLookupService.getUserInfo();
List<GrantedAuthority> roles = me.getRoles().stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName())).collect(Collectors.toList());
UserDetails user = new User(me.getUsername(), "", roles);
Authentication authentication = new UserAuthentication(user);
return authentication;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
Controller.java
#RestController
public class Controller {
#Autowired
private QService qService;
#PreAuthorize("hasAnyRole('view', 'admin')")
#RequestMapping(value = "/q/{year}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
public ResponseEntity<?> listQuotas(#PathVariable Integer year) {
return new ResponseEntity<>(qService.listQs(year), HttpStatus.OK);
}
#RequestMapping(value = "/user", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
public ResponseEntity<?> user(HttpServletRequest request) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return new ResponseEntity<>( auth.getPrincipal(), HttpStatus.OK);
}
#PreAuthorize("hasRole('shouldntauthorize')")
#RequestMapping(value = "/unauthorized/{year}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
public ResponseEntity<?> unauthorized(#PathVariable Integer year) {
return new ResponseEntity<>(qService.listQs(year), HttpStatus.OK);
}
}
When it works - I am able to hit any of the above methods using HTTP gets and I am getting correct responses. When it's not working, I am constantly getting:
2017-01-05 14:18:47.506 WARN 11252 --- [ (self-tuning)'] o.s.web.servlet.PageNotFound : No mapping found for HTTP request with URI [/user] in DispatcherServlet with name 'dispatcherServlet'
I can verify in the logs that when Spring Boot initializes the application is also sets the correct mapping URL.
Any ideas what could be the problem here?
when you say "intermittently" I tend to think that the problem is with Spring startup configuration.
So, I'd be weary on the fact that you have #ComponentScan twice, and with different packages.
Could you try removing
#ComponentScan(basePackages = { "com.controller", "com.service", "com.dao"})
from class WebConfiguration.java and
#ComponentScan({ "com.subject", "com.custom" })
from class WebSecurityConfiguration.java, and replace them with a single
#ComponentScan(basePackages = { "com.controller", "com.service", "com.dao", "com.subject", "com.custom"})
in the main SpringBoot class?