BAEL-5395 - Authentication with Spring Security and MongoDB (#11883)
* auth with spring security and mongodb * fix PMD * fix missing application property in tests * fix: code style * fix: static finale case * fix: omit Javadoc
This commit is contained in:
parent
c6bd572eda
commit
bfd778c028
|
@ -1,5 +1,6 @@
|
|||
<?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">
|
||||
<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>
|
||||
<artifactId>spring-security-web-boot-3</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
|
@ -23,6 +24,15 @@
|
|||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
<version>3.3.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package com.baeldung.mongoauth;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import com.baeldung.mongoauth.config.MongoConfig;
|
||||
import com.baeldung.mongoauth.config.SecurityConfig;
|
||||
|
||||
@SpringBootApplication
|
||||
@Import({ SecurityConfig.class, MongoConfig.class })
|
||||
public class MongoAuthApplication {
|
||||
|
||||
public static void main(String... args) {
|
||||
SpringApplication.run(MongoAuthApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package com.baeldung.mongoauth.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
import com.mongodb.client.MongoClients;
|
||||
|
||||
import de.flapdoodle.embed.mongo.MongodExecutable;
|
||||
import de.flapdoodle.embed.mongo.MongodStarter;
|
||||
import de.flapdoodle.embed.mongo.config.ImmutableMongodConfig;
|
||||
import de.flapdoodle.embed.mongo.config.MongodConfig;
|
||||
import de.flapdoodle.embed.mongo.config.Net;
|
||||
import de.flapdoodle.embed.mongo.distribution.Version;
|
||||
import de.flapdoodle.embed.process.runtime.Network;
|
||||
|
||||
@Configuration
|
||||
public class MongoConfig {
|
||||
|
||||
private static final String CONNECTION_STRING = "mongodb://%s:%d";
|
||||
private static final String HOST = "localhost";
|
||||
|
||||
@Bean
|
||||
public MongoTemplate mongoTemplate() throws Exception {
|
||||
|
||||
int randomPort = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
ImmutableMongodConfig mongoDbConfig = MongodConfig.builder()
|
||||
.version(Version.Main.PRODUCTION)
|
||||
.net(new Net(HOST, randomPort, Network.localhostIsIPv6()))
|
||||
.build();
|
||||
|
||||
MongodStarter starter = MongodStarter.getDefaultInstance();
|
||||
MongodExecutable mongodExecutable = starter.prepare(mongoDbConfig);
|
||||
mongodExecutable.start();
|
||||
return new MongoTemplate(MongoClients.create(String.format(CONNECTION_STRING, HOST, randomPort)), "mongo_auth");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package com.baeldung.mongoauth.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled = true)
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
public SecurityConfig(UserDetailsService userDetailsService) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager customAuthenticationManager() throws Exception {
|
||||
return authenticationManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public BCryptPasswordEncoder bCryptPasswordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(@Autowired AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.userDetailsService(userDetailsService)
|
||||
.passwordEncoder(bCryptPasswordEncoder());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.csrf()
|
||||
.disable()
|
||||
.authorizeRequests()
|
||||
.and()
|
||||
.httpBasic()
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.anyRequest()
|
||||
.permitAll()
|
||||
.and()
|
||||
.sessionManagement()
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.baeldung.mongoauth.controller;
|
||||
|
||||
import javax.annotation.security.RolesAllowed;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class ResourceController {
|
||||
|
||||
@RolesAllowed("ROLE_ADMIN")
|
||||
@GetMapping("/admin")
|
||||
public String admin() {
|
||||
return "Hello Admin!";
|
||||
}
|
||||
|
||||
@RolesAllowed({ "ROLE_ADMIN", "ROLE_USER" })
|
||||
@GetMapping("/user")
|
||||
public String user() {
|
||||
return "Hello User!";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.baeldung.mongoauth.domain;
|
||||
|
||||
public class Role {
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package com.baeldung.mongoauth.domain;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoId;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
@Document
|
||||
public class User implements UserDetails {
|
||||
private @MongoId ObjectId id;
|
||||
private String username;
|
||||
private String password;
|
||||
private Set<UserRole> userRoles;
|
||||
|
||||
public ObjectId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public void setUserRoles(Set<UserRole> userRoles) {
|
||||
this.userRoles = userRoles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UserRole> getAuthorities() {
|
||||
return this.userRoles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
User user = (User) o;
|
||||
return Objects.equals(username, user.username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(username);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.baeldung.mongoauth.domain;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
public class UserRole implements GrantedAuthority {
|
||||
|
||||
private Role role;
|
||||
|
||||
@Override
|
||||
public String getAuthority() {
|
||||
return role.getName();
|
||||
}
|
||||
|
||||
public Role getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(Role role) {
|
||||
this.role = role;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.baeldung.mongoauth.repository;
|
||||
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.data.mongodb.repository.Query;
|
||||
|
||||
import com.baeldung.mongoauth.domain.User;
|
||||
|
||||
public interface UserRepository extends MongoRepository<User, String> {
|
||||
|
||||
@Query("{username:'?0'}")
|
||||
User findUserByUsername(String username);
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package com.baeldung.mongoauth.service;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.baeldung.mongoauth.repository.UserRepository;
|
||||
|
||||
@Service
|
||||
public class MongoAuthUserDetailService implements UserDetailsService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public MongoAuthUserDetailService(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
|
||||
|
||||
com.baeldung.mongoauth.domain.User user = userRepository.findUserByUsername(userName);
|
||||
|
||||
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
|
||||
|
||||
user.getAuthorities()
|
||||
.forEach(role -> {
|
||||
grantedAuthorities.add(new SimpleGrantedAuthority(role.getRole()
|
||||
.getName()));
|
||||
});
|
||||
|
||||
return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package com.baeldung.mongoauth.service;
|
||||
|
||||
public interface SecurityService {
|
||||
boolean login(String username, String password);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package com.baeldung.mongoauth.service;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class SecurityServiceImpl implements SecurityService {
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
public SecurityServiceImpl(AuthenticationManager authenticationManager, UserDetailsService userDetailsService) {
|
||||
this.authenticationManager = authenticationManager;
|
||||
this.userDetailsService = userDetailsService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean login(String username, String password) {
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
|
||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
|
||||
|
||||
authenticationManager.authenticate(usernamePasswordAuthenticationToken);
|
||||
|
||||
if (usernamePasswordAuthenticationToken.isAuthenticated()) {
|
||||
SecurityContextHolder.getContext()
|
||||
.setAuthentication(usernamePasswordAuthenticationToken);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
spring.mongodb.embedded.version=4.4.9
|
|
@ -0,0 +1,118 @@
|
|||
package com.baeldung.mongoauth;
|
||||
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import com.baeldung.mongoauth.domain.Role;
|
||||
import com.baeldung.mongoauth.domain.User;
|
||||
import com.baeldung.mongoauth.domain.UserRole;
|
||||
|
||||
@SpringBootTest(classes = { MongoAuthApplication.class })
|
||||
@AutoConfigureMockMvc
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
class MongoAuthApplicationIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
private MongoTemplate mongoTemplate;
|
||||
|
||||
@Autowired
|
||||
private BCryptPasswordEncoder bCryptPasswordEncoder;
|
||||
|
||||
private MockMvc mvc;
|
||||
|
||||
private static final String USER_NAME = "user@gmail.com";
|
||||
private static final String ADMIN_NAME = "admin@gmail.com";
|
||||
private static final String PASSWORD = "password";
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
|
||||
setUp();
|
||||
|
||||
mvc = MockMvcBuilders.webAppContextSetup(context)
|
||||
.apply(springSecurity())
|
||||
.build();
|
||||
}
|
||||
|
||||
private void setUp() {
|
||||
Role roleUser = new Role();
|
||||
roleUser.setName("ROLE_USER");
|
||||
mongoTemplate.save(roleUser);
|
||||
|
||||
User user = new User();
|
||||
user.setUsername(USER_NAME);
|
||||
user.setPassword(bCryptPasswordEncoder.encode(PASSWORD));
|
||||
|
||||
UserRole userRole = new UserRole();
|
||||
userRole.setRole(roleUser);
|
||||
user.setUserRoles(new HashSet<>(Collections.singletonList(userRole)));
|
||||
mongoTemplate.save(user);
|
||||
|
||||
User admin = new User();
|
||||
admin.setUsername(ADMIN_NAME);
|
||||
admin.setPassword(bCryptPasswordEncoder.encode(PASSWORD));
|
||||
|
||||
Role roleAdmin = new Role();
|
||||
roleAdmin.setName("ROLE_ADMIN");
|
||||
mongoTemplate.save(roleAdmin);
|
||||
|
||||
UserRole adminRole = new UserRole();
|
||||
adminRole.setRole(roleAdmin);
|
||||
admin.setUserRoles(new HashSet<>(Collections.singletonList(adminRole)));
|
||||
mongoTemplate.save(admin);
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenUserCredentials_whenInvokeUserAuthorizedEndPoint_thenReturn200() throws Exception {
|
||||
mvc.perform(get("/user").with(httpBasic(USER_NAME, PASSWORD)))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenUserNotExists_whenInvokeEndPoint_thenReturn401() throws Exception {
|
||||
mvc.perform(get("/user").with(httpBasic("not_existing_user", "password")))
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenUserExistsAndWrongPassword_whenInvokeEndPoint_thenReturn401() throws Exception {
|
||||
mvc.perform(get("/user").with(httpBasic(USER_NAME, "wrong_password")))
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenUserCredentials_whenInvokeAdminAuthorizedEndPoint_thenReturn403() throws Exception {
|
||||
mvc.perform(get("/admin").with(httpBasic(USER_NAME, PASSWORD)))
|
||||
.andExpect(status().isForbidden());
|
||||
}
|
||||
|
||||
@Test
|
||||
void givenAdminCredentials_whenInvokeAdminAuthorizedEndPoint_thenReturn200() throws Exception {
|
||||
mvc.perform(get("/admin").with(httpBasic(ADMIN_NAME, PASSWORD)))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
mvc.perform(get("/user").with(httpBasic(ADMIN_NAME, PASSWORD)))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue