I have a project running with JWT authentication, it works, but now I need to implement Multi-Tenancy using the following approach:
Requirements:
A user can have access to one or more tenants
Access permissions are defined by user and tenant
Getting subdomain through #RequestAttribute in requests
Generate the token containing the tenant ID (subdomain).
Validate the tenant on all requests
Implemented:
Created JWT Autentication.
Created TenantInterceptor.
Getting subdomain using #RequestAttribute on requests.
Created existsByUsernameAndSubdomain validation.
I'm having trouble implementing this new feature, can you point me to an implementation example or tutorial that can help me?
I thank you for your help!
Below are my classes or if you prefer clone on GitHub!
My classes
Models:
/** ERole **/
public enum ERole {
ROLE_USER,
ROLE_MODERATOR,
ROLE_ADMIN
}
/** Role **/
#Entity
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "roles")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Enumerated(EnumType.STRING)
#Column(length = 20)
private ERole name;
}
/** Tenant **/
#Entity
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "tenants",
uniqueConstraints = {
#UniqueConstraint(columnNames = "subdomain", name = "un_subdomain")
})
public class Tenant {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 20)
private String subdomain;
#NotBlank
private String name;
}
/** User **/
#Entity
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "users",
uniqueConstraints = {
#UniqueConstraint(columnNames = "username", name = "un_username")
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 20)
private String username;
#NotBlank
#Size(max = 120)
#JsonIgnore
private String password;
// Remove
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "users_roles",
joinColumns = {#JoinColumn(name = "user_id",
foreignKey = #ForeignKey(name = "fk_users_roles_users1"))},
inverseJoinColumns = {#JoinColumn(name = "role_id",
foreignKey = #ForeignKey(name = "fk_users_roles_roles1"))})
private Set<Role> roles = new HashSet<>();
// Include
#EqualsAndHashCode.Exclude
#OneToMany(mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY)
#JsonManagedReference
private List<UserTenant> tenants = new ArrayList<>();
public User(String username, String password) {
this.username = username;
this.password = password;
}
}
/** UserTenant **/
#Entity
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "users_tenants",
uniqueConstraints = {
#UniqueConstraint(columnNames = "user_id", name = "un_user_id"),
#UniqueConstraint(columnNames = "tenant_id", name = "un_tenant_id")
})
public class UserTenant {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id",
nullable = false,
foreignKey = #ForeignKey(
name = "fk_users_tenants_user1"))
#JsonBackReference
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "tenant_id",
nullable = false,
foreignKey = #ForeignKey(
name = "fk_users_tenants_tenant1"))
#JsonBackReference
private Tenant tenant;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "users_tenants_roles",
joinColumns = {#JoinColumn(name = "user_tenant_id",
foreignKey = #ForeignKey(name = "fk_users_tenants_user_tenant1"))},
inverseJoinColumns = {#JoinColumn(name = "role_id",
foreignKey = #ForeignKey(name = "fk_users_tenants_roles1"))})
private Set<Role> roles = new HashSet<>();
}
Payloads:
/** LoginRequest **/
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class LoginRequest {
#NotBlank
private String username;
#NotBlank
private String password;
}
/** SignupRequest **/
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class SignupRequest {
#NotBlank
#Size(max = 20)
private String username;
#NotBlank
#Size(max = 40)
private String password;
private Set<String> role;
}
/** JwtResponse **/
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class JwtResponse {
private Long id;
private String username;
private List<String> roles;
private String tokenType = "Bearer";
private String accessToken;
public JwtResponse(String accessToken, Long id, String username,
List<String> roles) {
this.id = id;
this.username = username;
this.roles = roles;
this.accessToken = accessToken;
}
}
/** MessageResponse **/
#Data
#Builder
#NoArgsConstructor
public class MessageResponse {
private String message;
public MessageResponse(String message) {
this.message = message;
}
}
Repositories:
/** RoleRepository **/
#Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
Optional<Role> findByName(ERole name);
}
/** UserRepository **/
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Boolean existsByUsername(String username);
}
/** UserTenantRepository **/
#Repository
public interface UserTenantRepository extends JpaRepository<UserTenant, Long> {
#Query("SELECT ut FROM UserTenant ut WHERE ut.user.username = :username AND ut.tenant.subdomain = :subdomain ")
Optional<UserTenant> findByUserAndSubdomain(String username, String subdomain);
#Query("SELECT " +
"CASE WHEN COUNT(ut) > 0 THEN true ELSE false END " +
"FROM UserTenant ut " +
"WHERE ut.user.username = :username " +
"AND ut.tenant.subdomain = :subdomain ")
Boolean existsByUsernameAndSubdomain(String subdomain, String username);
}
Services:
/** AuthService **/
#Service
#RequiredArgsConstructor
public class AuthService {
private final UserRepository userRepository;
private final AuthenticationManager authenticationManager;
private final JwtUtils jwtUtils;
private final PasswordEncoder encoder;
private final RoleRepository roleRepository;
public JwtResponse authenticateUser(String subdomain, LoginRequest loginRequest) {
System.out.println(subdomain);
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
System.out.println(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtUtils.generateJwtToken(authentication);
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
List<String> roles = userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
return new JwtResponse(jwt,
userDetails.getId(),
userDetails.getUsername(),
roles);
}
#Transactional
public MessageResponse registerUser(SignupRequest signUpRequest) {
// Create new user's account
User user = new User(
signUpRequest.getUsername(),
encoder.encode(signUpRequest.getPassword()));
Set<String> strRoles = signUpRequest.getRole();
Set<Role> roles = new HashSet<>();
if (strRoles == null) {
Role userRole = roleRepository.findByName(ERole.ROLE_USER)
.orElseThrow(() -> new RuntimeException("Error: Role is not found."));
roles.add(userRole);
} else {
strRoles.forEach(role -> {
switch (role) {
case "admin":
Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN)
.orElseThrow(() -> new RuntimeException("Error: Role is not found."));
roles.add(adminRole);
break;
case "mod":
Role modRole = roleRepository.findByName(ERole.ROLE_MODERATOR)
.orElseThrow(() -> new RuntimeException("Error: Role is not found."));
roles.add(modRole);
break;
default:
Role userRole = roleRepository.findByName(ERole.ROLE_USER)
.orElseThrow(() -> new RuntimeException("Error: Role is not found."));
roles.add(userRole);
}
});
}
user.setRoles(roles);
userRepository.save(user);
return new MessageResponse("User registered successfully!");
}
}
/** UserDetailsImpl **/
public class UserDetailsImpl implements UserDetails {
private static final long serialVersionUID = 1L;
private final Long id;
private final String username;
#JsonIgnore
private final String password;
private final Collection<? extends GrantedAuthority> authorities;
public UserDetailsImpl(Long id, String username, String password,
Collection<? extends GrantedAuthority> authorities) {
this.id = id;
this.username = username;
this.password = password;
this.authorities = authorities;
}
public static UserDetailsImpl build(User user) {
List<GrantedAuthority> authorities = user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getName().name()))
.collect(Collectors.toList());
return new UserDetailsImpl(
user.getId(),
user.getUsername(),
user.getPassword(),
authorities);
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
public Long getId() {
return id;
}
#Override
public String getPassword() {
return password;
}
#Override
public String getUsername() {
return username;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
#Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
UserDetailsImpl user = (UserDetailsImpl) o;
return Objects.equals(id, user.id);
}
}
/** UserDetailsServiceImpl **/
#Service
#RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
private final UserTenantRepository userTenantRepository;
#Override
#Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username));
return UserDetailsImpl.build(user);
}
}
Controller
/** AuthController **/
#RestController
#RequestMapping("/auth")
#RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
private final UserRepository userRepository;
private final UserTenantRepository userTenantRepository;
#PostMapping("/signin")
public ResponseEntity<?> authenticateUser(
#RequestAttribute String subdomain,
#Valid #RequestBody LoginRequest loginRequest
) {
if (!userTenantRepository.existsByUsernameAndSubdomain(subdomain, loginRequest.getUsername())) {
return ResponseEntity
.badRequest()
.body(new MessageResponse("Unauthorized: This username and tenant is not authorized!"));
}
return ResponseEntity.ok(authService.authenticateUser(subdomain, loginRequest));
}
#PostMapping("/signup")
public ResponseEntity<?> registerUser(#Valid #RequestBody SignupRequest signUpRequest) {
if (userRepository.existsByUsername(signUpRequest.getUsername())) {
return ResponseEntity
.badRequest()
.body(new MessageResponse("Error: Username is already taken!"));
}
return ResponseEntity.ok(authService.registerUser(signUpRequest));
}
}
JWT:
/** AuthEntryPointJwt **/
#Component
public class AuthEntryPointJwt implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
logger.error("Unauthorized error: {}", authException.getMessage());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: incorrect username or password");
}
}
/** AuthTokenFilter **/
public class AuthTokenFilter extends OncePerRequestFilter {
#Autowired
private JwtUtils jwtUtils;
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response
, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = parseJwt(request);
if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
String username = jwtUtils.getUserNameFromJwtToken(jwt);
String serverName = request.getServerName();
String subdomain = serverName.substring(0, serverName.indexOf("."));
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
System.out.println(userDetails);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
e.printStackTrace();
}
filterChain.doFilter(request, response);
}
private String parseJwt(HttpServletRequest request) {
String headerAuth = request.getHeader("Authorization");
if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7);
}
return null;
}
}
/** JwtUtils **/
#Component
public class JwtUtils {
private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);
#Value("${example.app.jwtSecret}")
private String jwtSecret;
#Value("${example.app.jwtExpirationMs}")
private int jwtExpirationMs;
public String generateJwtToken(Authentication authentication) {
UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();
return Jwts.builder()
.setSubject((userPrincipal.getUsername()))
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public String getUserNameFromJwtToken(String token) {
return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
}
public boolean validateJwtToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
logger.error("Invalid JWT signature: {}", e.getMessage());
} catch (MalformedJwtException e) {
logger.error("Invalid JWT token: {}", e.getMessage());
} catch (ExpiredJwtException e) {
logger.error("JWT token is expired: {}", e.getMessage());
} catch (UnsupportedJwtException e) {
logger.error("JWT token is unsupported: {}", e.getMessage());
} catch (IllegalArgumentException e) {
logger.error("JWT claims string is empty: {}", e.getMessage());
}
return false;
}
}
Utils:
/** TenantInterceptor **/
public class TenantInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String serverName = request.getServerName();
String tenantId = serverName.substring(0, serverName.indexOf("."));
request.setAttribute("subdomain", tenantId);
return true;
}
}
/** WebSecurityConfig **/
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig implements WebMvcConfigurer {
final
UserDetailsServiceImpl userDetailsService;
private final AuthEntryPointJwt unauthorizedHandler;
public WebSecurityConfig(UserDetailsServiceImpl userDetailsService, AuthEntryPointJwt unauthorizedHandler) {
this.userDetailsService = userDetailsService;
this.unauthorizedHandler = unauthorizedHandler;
}
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(
"/auth/**",
"/v3/api-docs/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/configuration/**",
"/swagger-resources/**",
"/webjars/**",
"/api-docs/**").permitAll()
.antMatchers("/api/**").authenticated()
.anyRequest().authenticated();
http.authenticationProvider(authenticationProvider());
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TenantInterceptor());
}
}
I was able to solve the problem by modifying the loadUserByUsername method in the UserDetailsServiceImpl.
See the implementation details on GitHub!
#Service
#RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserTenantRepository userTenantRepository;
#Override
#Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// Getting subdomain from request attributes
HttpServletRequest request =
((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes()))
.getRequest();
String serverName = request.getServerName();
String subdomain = serverName.substring(0, serverName.indexOf("."));
UserTenant userTenant = userTenantRepository.findByUserAndSubdomain(username, subdomain)
.orElseThrow(() -> new UsernameNotFoundException(
"UserTenant Not Found with username: " + username + " and " + subdomain));
// Getting Rules from the UserTenant
List<GrantedAuthority> authorities = userTenant.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getName().name()))
.collect(Collectors.toList());
return new UserDetailsImpl(
userTenant.getUser().getId(),
userTenant.getUser().getUsername(),
userTenant.getUser().getPassword(),
authorities
);
}
}
Inserts in Database
INSERT INTO roles(id, name)
VALUES (1 ,'ROLE_USER'),
(2, 'ROLE_MODERATOR'),
(3, 'ROLE_ADMIN');
INSERT INTO tenants (id, name, subdomain)
VALUES (1, 'Tenant 1', 'tenant1'),
(2, 'Tenant 2', 'tenant2');
# user, password
# user1, user1
# user2, user2
INSERT INTO users (id, username, password)
VALUES (1, 'user1', '$2a$10$wFMJLxdXKGRa8lJO6k2DAOnW9HstAPoHecXUNkDyYNeaNnZJAz.hy'),
(2, 'user2', '$2a$10$Z9/wLkmf5IwfjJqIQU6X.OBFg3TCBUyk3bdfgkGjU0.HI5kVibZxG');
INSERT INTO users_tenants (id, tenant_id, user_id)
VALUES (1, 1, 1),
(2, 2, 2);
INSERT INTO users_tenants_roles (user_tenant_id, role_id)
VALUES (1, 2),
(1, 3),
(2, 1);
INSERT INTO items (id, tenant_id, name)
VALUES (1, 1, 'Product 1 in Tenant 1'),
(2, 1, 'Product 2 in Tenant 1'),
(3, 2, 'Product 1 in Tenant 2'),
(4, 2, 'Product 2 in Tenant 2');
Validations in Postman
Created token variable in Postman:
Set token value in Postman variable:
Added Authorization variable in requests headers:
Validating if the domain exists in sign in:
Validating user access permission on tenant:
Authorized login:
Getting items of tenant1:
Trying to get tenant2 list of items without being logged in tenant2 and having access permissions:
Logging in with user2 in tenant2:
Getting items list tenant2
Trying to get tenant1 list of items without being logged in tenant1 and having access permissions:
I want to create a login method for my spring application. But when I try to call the
getUserByAuthentication
method, I get a null pointer exception. Here is my code:
Function calling the error:
#PostMapping(path = "/online")
public ResponseEntity<?> onlineRequest(#RequestBody OnlineRequest onlineRequest) {
User user = null;
UserManager userManager = new UserManager();
user = userManager.getUserByAuthentication(onlineRequest.getUsername(), onlineRequest.getPassword());
if (user!=null){
user.setLatestTimeStamp(System.currentTimeMillis());
return new ResponseEntity<>("You are now online, Enjoy!", HttpStatus.OK);
} else {
return new ResponseEntity<>("Invalid login", HttpStatus.valueOf(403));
}
}
Get User by Authentication class:
public class UserManager {
#Autowired
private UserRepository userRepository;
public User getUserByID(int id){
return userRepository.findById(id).get();
}
public User getUserByAuthentication(String name, String password){
Iterable<User> userList = userRepository.findAll();
ArrayList<User> users = new ArrayList<>();
userList.forEach(users::add);
User user = null;
for (User u : users){
if (u.getUsername().equals(name) && u.getPassword().equals(password)){
user = u;
}
}
return user;
}
}
Repository:
#Repository
public interface UserRepository extends CrudRepository<User, Integer> {
}
User class:
#Entity
#Table
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
//Initialize
private String username;
private String password;
private boolean hasAccess;
ArrayList<Integer> inChannels;
long latestTimeStamp;
public long getLatestTimeStamp() {
return latestTimeStamp;
}
public void setLatestTimeStamp(long latestTimeStamp) {
this.latestTimeStamp = latestTimeStamp;
}
public ArrayList<Integer> getInChannels() {
return inChannels;
}
public void setInChannels(ArrayList<Integer> inChannels) {
this.inChannels = inChannels;
}
public Long getId() {
return id;
}
public User() {
}
public boolean hasAccess() {
return hasAccess;
}
public void setAccess(boolean hasAccess) {
this.hasAccess = hasAccess;
}
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;
}
}
You can't use #Autowired without one of the annotation that define a component in spring, so in your case, you can use #Service on UserManager like this:
#Service
public class UserManager {
and also don't use static on your method, you have to inject the UserManager component in the controller, as you do with your repository:
#Autowired
private UserManager userManager;
Then you can use:
user = userManager.getUserByAuthentication(onlineRequest.getUsername(), onlineRequest.getPassword());
^^^^^^^^^^^
I fixed it by adding the find function into the repository interface.
I would like to learn, how can I make a project that has this function. (sign-in and sign-up)
These are the classes that I have so far.
this is the entity i have also the class User
#Entity(name = "Users")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id" ,nullable = false)
private int id;
#Column(name = "first_name" )
private String firstname;
#Column(name = "last_name" )
private String lastname;
#Column(name = "Email" )
private String email;
protected UserEntity() {
}
public UserEntity( String firstname, String lastname, String email) {
this.firstname = firstname;
this.lastname = lastname;
this.email = email;
}
this is the Service class where the methods should be
#Service
public class UserService {
private final UserRepository usersRepository;
public UserService(UserRepository usersRepository) {
this.usersRepository = usersRepository;
}
public List<User> findAll (){
List<UserEntity> users = usersRepository.findAll();
return users.stream().map(this::entityToUser).collect(Collectors.toList());
}
public User findUserbyId(int id){
var userentity = usersRepository.findById(id);
return userentity.map(this::entityToUser).orElse(null);
}
public User update(int id , UserCreateRequest request){
var userEntityOptional = usersRepository.findById(id);
if(userEntityOptional.isEmpty()){
return null;
}
var userEntity = userEntityOptional.get();
userEntity.setFirstname(request.getFirstname());
userEntity.setLastname(request.getLastname());
userEntity.setEmail(request.getEmail());
usersRepository.save(userEntity);
return entityToUser(userEntity);
}
public boolean deleteById(int id){
if (!usersRepository.existsById(id)){
return false;
}
usersRepository.existsById(id);
return true;
}
public User create(UserCreateRequest request){
var userEntity = new UserEntity(request.getFirstname(),request.getLastname(),request.getEmail());
userEntity = usersRepository.save(userEntity);
return entityToUser(userEntity);
}
public User entityToUser(UserEntity userEntity){
return new User (
userEntity.getId(),
userEntity.getFirstname(),
userEntity.getLastname(),
userEntity.getEmail());
}
}
here is the User Controller
#RestController
public class UserRestController {
private final UserService userService;
public UserRestController(UserService userService) {
this.userService = userService;
}
#GetMapping(path = "/api/v1/users")
public ResponseEntity<List<User>> fetchUsers() {
return ResponseEntity.ok(userService.findAll()) ;
}
I would like to know what do I need exactly to get this function right in an optimal way
what are the necessary steps?
#Service
public class UserService {
//make this Autowired
#Autowired
private UserRepository usersRepository;
public List<User> findAll (){
List<UserEntity> users = usersRepository.findAll();
return users.stream().map(this::entityToUser).collect(Collectors.toList());
}
public User findUserbyId(int id){
var userentity = usersRepository.findById(id);
return userentity.map(this::entityToUser).orElse(null);
}
public User update(int id , UserCreateRequest request){
var userEntityOptional = usersRepository.findById(id);
if(userEntityOptional.isEmpty()){
return null;
}
var userEntity = userEntityOptional.get();
userEntity.setFirstname(request.getFirstname());
userEntity.setLastname(request.getLastname());
userEntity.setEmail(request.getEmail());
usersRepository.save(userEntity);
return entityToUser(userEntity);
}
public boolean deleteById(int id){
if (!usersRepository.existsById(id)){
return false;
}
usersRepository.existsById(id);
return true;
}
public User create(UserCreateRequest request){
var userEntity = new UserEntity(request.getFirstname(),request.getLastname(),request.getEmail());
userEntity = usersRepository.save(userEntity);
return entityToUser(userEntity);
}
public User entityToUser(UserEntity userEntity){
return new User (
userEntity.getId(),
userEntity.getFirstname(),
userEntity.getLastname(),
userEntity.getEmail());
}
}
here is the User Controller
#RestController
public class UserRestController {
//make this Autowired
#Autowired
private UserService userService;
#GetMapping(path = "/api/v1/users")
public ResponseEntity<List<User>> fetchUsers() {
return ResponseEntity.ok(userService.findAll()) ;
}
Check this to know more about #Autowired https://www.baeldung.com/spring-autowire
I am writing a Spring-MVC application using Spring Boot and Hibernate. The application has a User who has the role of Admin and Customer. I decided to add Spring Security JWT and did everything right. Now that I have everything ready, I want to get a TOKEN, I log in successfully, but in return I get 403 Forbidden. What have I done wrong? Did you configure configs incorrectly or did the roles incorrectly?
Configs:
#RequiredArgsConstructor
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// Fields
//
private final JwtTokenProvider jwtTokenProvider;
//
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().disable().csrf().disable()
.httpBasic().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/auth/login").permitAll()
.anyRequest().authenticated()
.and()
.apply(new JwtConfigurer(jwtTokenProvider));
}
}
Controller:
#RequiredArgsConstructor
#RestController
#RequestMapping("/auth")
public class AuthenticationController {
// Fields
//
private final AuthenticationManager authenticationManager;
private final JwtTokenProvider jwtTokenProvider;
private final UserService userService;
//
// GET-Methods
//
//
#PostMapping("/login")
public ResponseEntity<Map<String, String>> login(#RequestBody AuthenticationRequestDTO requestDto) {
try {
String login = requestDto.getLogin();
authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(login, requestDto.getPassword()));
User user = userService.findByLogin(login);
String token = jwtTokenProvider.createToken(login, user.getRole());
Map<String, String> response = new HashMap<>();
response.put("login", login);
response.put("token", token);
return ResponseEntity.ok(response);
} catch (AuthenticationException e) {
throw new BadCredentialsException("Invalid login or password");
}
}
}
User class:
#Getter
#Entity
#NoArgsConstructor
#Table(name = "users")
public class User {
// Fields
//
#Id
#GeneratedValue
private Long id;
private String name;
#Column(name = "last_name")
private String lastName;
private String login;
private String password;
private String mail;
private boolean isDeleted;
#Enumerated(EnumType.STRING)
private Role role;
}
#RequiredArgsConstructor
#Component
public class JwtTokenProvider {
// Fields
//
private final UserDetailsService userDetailsService;
#Value("${jwt.token.secret}")
private String secret;
#Value("${jwt.token.expired}")
private Long validityInMilliSeconds;
//
// METHODS
//
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(8);
}
#PostConstruct
protected void init() {
secret = Base64.getEncoder().encodeToString(secret.getBytes());
}
/**
* Генерируем ТОКЕН
*
* #param login
* #param role
* #return ТОКЕН
*/
public String createToken(String login, Role role) {
Claims claims = Jwts.claims().setSubject(login);
claims.put("roles", getRoleName(role));
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliSeconds);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public Authentication getAuthentication(String token) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(getLogin(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
public String getLogin(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
}
public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
if (claims.getBody().getExpiration().before(new Date())) {
return false;
}
return true;
} catch (JwtException | IllegalArgumentException e) {
throw new JwtAuthenticationException("JWT token is expired or invalid");
}
}
public String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer_")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
private String getRoleName(Role role) {
String roleName = role.name();
return roleName;
}
}
#RequiredArgsConstructor
public class JwtTokenFilter extends GenericFilterBean {
private final JwtTokenProvider jwtTokenProvider;
#Override
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain) throws IOException, ServletException {
String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);
if (auth != null) {
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
chain.doFilter(request, response);
}
}
#RequiredArgsConstructor
#Service
#Slf4j
public class JwtUserDetailsService implements UserDetailsService {
private final UserService userService;
#Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
User user = userService.findByLogin(login);
JwtUser jwtUser = JwtUserFactory.create(user);
log.info("IN loadUserByUsername - user with login: {} successfully loaded", login);
return jwtUser;
}
}
#AllArgsConstructor
#Getter
public class JwtUser implements UserDetails {
// Fields
//
private Long id;
private String name;
private String lastName;
private String login;
private String password;
private String mail;
private boolean isDeleted;
private Role role;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role));
}
#Override
public String getUsername() {
return null;
}
#Override
public boolean isAccountNonExpired() {
return false;
}
#Override
public boolean isAccountNonLocked() {
return false;
}
#Override
public boolean isCredentialsNonExpired() {
return false;
}
#Override
public boolean isEnabled() {
return false;
}
}
P.S. I will not throw all the code for JWT from the project here, but skip only the most important thing.
LOGS:
2020-08-10 16:36:27.343 INFO 7556 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 64 ms
2020-08-10 16:36:28.454 INFO 7556 --- [nio-8080-exec-2] r.c.security.JwtUserDetailsService : IN loadUserByUsername - user with login: art123 successfully loaded
I am trying to get the current logged in username using Spring's security but the Principal object returns null.
This is my REST controller method:
#RequestMapping("/getCurrentUser")
public User getCurrentUser(Principal principal) {
String username = principal.getName();
User user = new User();
if (null != username) {
user = userService.findByUsername(username);
}
return user;
}
NB: I am running Spring boot 1.5.13 and spring security 4.2.6
This is my security config class:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private Environment env;
#Autowired
private UserSecurityService userSecurityService;
private BCryptPasswordEncoder passwordEncoder() {
return SecurityUtility.passwordEncoder();
}
private static final String[] PUBLIC_MATCHERS = {
"/css/**",
"/js/**",
"/image/**",
"/book/**",
"/user/**"
};
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().disable().httpBasic().and().authorizeRequests()
.antMatchers(PUBLIC_MATCHERS).permitAll().anyRequest().authenticated();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userSecurityService).passwordEncoder(passwordEncoder());
}
#Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
}
}
This is my user security service class:
#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", username);
throw new UsernameNotFoundException("Username "+username+" not found");
}
return user;
}
}
this is my user class:
#Entity
public class User implements UserDetails, Serializable {
private static final long serialVersionUID = 902783495L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="Id", nullable=false, updatable = false)
private Long id;
private String username;
private String password;
private String firstName;
private String lastName;
private String email;
private String phone;
private boolean enabled = true;
#OneToMany(mappedBy = "user", cascade=CascadeType.ALL, fetch = FetchType.EAGER)
#JsonIgnore
private Set<UserRole> userRoles = new HashSet<>();
}
There are multiple ways to do this.
Using SecurityContextHolder
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
Using Principal from a Controller
#RequestMapping(value = "/myusername", method = RequestMethod.GET)
#ResponseBody
public String currentUserName(Principal principal) {
return principal.getName();
}
From HttpServletRequest
#RequestMapping(value = "/myusername", method = RequestMethod.GET)
#ResponseBody
public String getUsername(HttpServletRequest req) {
return req.getUserPrincipal.getName();
}
Assuming your User class implements UserDetails, you can get the User from SecurityContextHolder without dependency injection.
public User getCurrentUser() {
return ((User) SecurityContextHolder.getContext()
.getAuthentication()
.getPrincipal());
}
The previous answer should work fine,
if using spring web MVC controllers you can also have it injected into your controllers automatically out of the box using springs default method argument resolvers (org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver)
A controller can do:
#RestController
public class AcceptConnectionController {
#PostMapping(value = "/")
public void controllerMethod(#AuthenticationPrincipal final MyPrincipal user) {
//...
}
}
In the example above, MyPrincipal extends org.springframework.security.authentication.AbstractAuthenticationToken
You can then pass this principal to your service layer.
As #cosmos asked - are you using Spring Security?
If yes, the following should work:
In your SecurityConfig:
#Bean
public AuthenticationManager customAuthenticationManager() throws Exception {
return authenticationManager();
}
And in your Controller:
private String getPrincipal() {
String userName = null;
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
userName = ((UserDetails) principal).getUsername();
} else {
userName = principal.toString();
}
return userName;
}
Here is a method to used to get a user principal
#PutMapping(value = "/{userId}")
public ResponseEntity<?> updateUser(#Valid #RequestBody UserPutRequest updateRequest, #PathVariable String userId,
Authentication authentication) {
Optional<UserObject> userOptional = userRepository.findById(userId);
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
UserObject currentUser = userRepository.findByEmail(userPrincipal.getEmail());
}
if you are using keycloak you can get more details using this way
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
KeycloakPrincipal principal = (KeycloakPrincipal)auth.getPrincipal();
KeycloakSecurityContext session = principal.getKeycloakSecurityContext();
AccessToken accessToken = session.getToken();
String username = accessToken.getPreferredUsername();
String emailID = accessToken.getEmail();
String lastname = accessToken.getFamilyName();
String firstname = accessToken.getGivenName();
String realmName = accessToken.getIssuer();
AccessToken.Access realmAccess = accessToken.getRealmAccess();