From 423e17ecb8d1cd5a54899f4185702502daff3a5e Mon Sep 17 00:00:00 2001 From: Jacob Salvi Date: Tue, 25 Feb 2020 15:03:41 +0100 Subject: [PATCH 1/4] small changes --- .idea/misc.xml | 2 +- build.gradle | 4 +- .../smarthut/SmarthutApplication.java | 2 + .../smarthut/models/SecurityService.java | 42 +++++++++++++++++++ .../sa4/sanmarinoes/smarthut/models/User.java | 25 +++++++---- .../models/UserDetailsServiceImpl.java | 25 +++++++++++ .../smarthut/models/UserRepository.java | 4 +- .../smarthut/models/UserService.java | 22 ++++++++++ 8 files changed, 116 insertions(+), 10 deletions(-) create mode 100644 src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SecurityService.java create mode 100644 src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserDetailsServiceImpl.java create mode 100644 src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserService.java diff --git a/.idea/misc.xml b/.idea/misc.xml index 56d4a95..8f27022 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - + \ No newline at end of file diff --git a/build.gradle b/build.gradle index c65502c..e6c5a36 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { group = 'ch.usi.inf.sa4.sanmarinoes.' version = '0.0.1-SNAPSHOT' -sourceCompatibility = '11' +sourceCompatibility = "11" repositories { mavenCentral() @@ -16,7 +16,9 @@ dependencies { compile 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.security:spring-security-web' implementation 'org.postgresql:postgresql' + implementation('org.springframework.boot:spring-boot-starter-web') { exclude group: 'org.springframework.boot', module: 'spring-boot-starter-json' } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmarthutApplication.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmarthutApplication.java index f215fe7..242f03f 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmarthutApplication.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmarthutApplication.java @@ -2,8 +2,10 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @SpringBootApplication +@EnableJpaRepositories("ch.usi.inf.sa4.sanmarinoes.smarthut.models") public class SmarthutApplication { public static void main(String[] args) { SpringApplication.run(SmarthutApplication.class, args); diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SecurityService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SecurityService.java new file mode 100644 index 0000000..5ff2e3b --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SecurityService.java @@ -0,0 +1,42 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.models; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +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; + +public class SecurityService { + @Autowired private AuthenticationManager manager; + + @Autowired private UserDetailsService service; + + private Logger logger = LoggerFactory.getLogger(SecurityService.class); + + public String loggedUser() { + Object details = SecurityContextHolder.getContext().getAuthentication().getDetails(); + if (details instanceof UserDetails) { + return ((UserDetails) details).getUsername(); + } else { + return null; + } + } + + public void autoLogin(String username, String password) { + UserDetails userDetails = service.loadUserByUsername(username); + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = + new UsernamePasswordAuthenticationToken( + userDetails, password, userDetails.getAuthorities()); + + manager.authenticate(usernamePasswordAuthenticationToken); + + if (usernamePasswordAuthenticationToken.isAuthenticated()) { + SecurityContextHolder.getContext() + .setAuthentication(usernamePasswordAuthenticationToken); + logger.debug(String.format("Auto login %s successfully!", username)); + } + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java index 92202e2..7378306 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java @@ -15,8 +15,11 @@ public class User { /** The full name of the user */ @Column private String name; + /** The full name of the user */ + @Column private String username; + /** A properly salted way to store the password TODO: define the implementation of salt */ - @Column private String hashedPassword; + @Column private String password; /** The user's email TODO: validate email in setters */ @Column private String email; @@ -37,6 +40,14 @@ public class User { return name; } + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + public void setName(String name) { this.name = name; } @@ -49,12 +60,12 @@ public class User { this.email = email; } - public String getHashedPassword() { - return hashedPassword; + public String getPassword() { + return password; } - public void setHashedPassword(String hashedPassword) { - this.hashedPassword = hashedPassword; + public void setPassword(String password) { + this.password = password; } public Set getRooms() { @@ -69,8 +80,8 @@ public class User { + ", name='" + name + '\'' - + ", hashedPassword='" - + hashedPassword + + ", password='" + + password + '\'' + ", email='" + email diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserDetailsServiceImpl.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserDetailsServiceImpl.java new file mode 100644 index 0000000..c1d9ff1 --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserDetailsServiceImpl.java @@ -0,0 +1,25 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.models; + +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.*; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +public class UserDetailsServiceImpl implements UserDetailsService { + @Autowired private UserRepository repository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User toReturn = repository.findByUsername(username); + if (toReturn != null) { + Set authoritySet = Set.of(new SimpleGrantedAuthority("user")); + return new org.springframework.security.core.userdetails.User( + toReturn.getUsername(), toReturn.getPassword(), authoritySet); + } else { + throw new UsernameNotFoundException(username); + } + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserRepository.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserRepository.java index a0a0f1f..c2bf0c2 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserRepository.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserRepository.java @@ -2,4 +2,6 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; import org.springframework.data.repository.CrudRepository; -public interface UserRepository extends CrudRepository {} +public interface UserRepository extends CrudRepository { + User findByUsername(String username); +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserService.java new file mode 100644 index 0000000..3ae7b93 --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserService.java @@ -0,0 +1,22 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.models; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +public class UserService { + + @Autowired private UserRepository userRepository; + + @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; + + public void save(User user) { + user.setPassword(bCryptPasswordEncoder.encode(user.getPassword())); + userRepository.save(user); + } + + public User findByUsername(String username) { + return userRepository.findByUsername(username); + } +} From 7195e999ea34ceea0e999c3d82ad299d05d5d2b5 Mon Sep 17 00:00:00 2001 From: Jacob Salvi Date: Tue, 25 Feb 2020 16:07:48 +0100 Subject: [PATCH 2/4] UserController --- .../smarthut/controller/UserController.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/UserController.java diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/UserController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/UserController.java new file mode 100644 index 0000000..55e9139 --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/UserController.java @@ -0,0 +1,30 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; + +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.SecurityService; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; + +@Controller +public class UserController { + @Autowired private UserService userService; + + @Autowired private SecurityService securityService; + + /* + @PostMapping("/registration") + public String registration(@ModelAttribute("userForm") User userForm, BindingResult bindingResult) { + userValidator.validate(userForm, bindingResult); + + if (bindingResult.hasErrors()) { + return "registration"; + } + + userService.save(userForm); + + securityService.autoLogin(userForm.getUsername(), userForm.getPasswordConfirm()); + + return "redirect:/welcome"; + } + */ +} From ad3e5271ac8910e6c7e418a43895e912c86df585 Mon Sep 17 00:00:00 2001 From: Claudio Maggioni Date: Tue, 25 Feb 2020 17:51:45 +0100 Subject: [PATCH 3/4] Working auth with JWT --- .idea/misc.xml | 2 +- build.gradle | 2 + git-hooks/pre-commit.sh | 7 +- .../config/JWTAuthenticationEntryPoint.java | 21 ++++++ .../smarthut/config/JWTRequestFilter.java | 69 +++++++++++++++++ .../smarthut/config/JWTTokenUtil.java | 72 ++++++++++++++++++ .../smarthut/config/WebSecurityConfig.java | 75 +++++++++++++++++++ .../JWTAuthenticationController.java | 57 ++++++++++++++ .../smarthut/controller/UserController.java | 30 -------- .../smarthut/models/JWTRequest.java | 30 ++++++++ .../smarthut/models/JWTResponse.java | 13 ++++ ...ceImpl.java => JWTUserDetailsService.java} | 10 ++- .../smarthut/models/SecurityService.java | 42 ----------- .../sa4/sanmarinoes/smarthut/models/User.java | 9 ++- .../smarthut/models/UserService.java | 22 ------ src/main/resources/application.properties | 3 +- 16 files changed, 358 insertions(+), 106 deletions(-) create mode 100644 src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTAuthenticationEntryPoint.java create mode 100644 src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTRequestFilter.java create mode 100644 src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTTokenUtil.java create mode 100644 src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/WebSecurityConfig.java create mode 100644 src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/JWTAuthenticationController.java delete mode 100644 src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/UserController.java create mode 100644 src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTRequest.java create mode 100644 src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTResponse.java rename src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/{UserDetailsServiceImpl.java => JWTUserDetailsService.java} (77%) delete mode 100644 src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SecurityService.java delete mode 100644 src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserService.java diff --git a/.idea/misc.xml b/.idea/misc.xml index 8f27022..241f098 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - + \ No newline at end of file diff --git a/build.gradle b/build.gradle index e6c5a36..ea7614f 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,8 @@ dependencies { compile 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'io.jsonwebtoken:jjwt:0.9.1' implementation 'org.springframework.security:spring-security-web' implementation 'org.postgresql:postgresql' diff --git a/git-hooks/pre-commit.sh b/git-hooks/pre-commit.sh index 68ab401..760f9a7 100755 --- a/git-hooks/pre-commit.sh +++ b/git-hooks/pre-commit.sh @@ -13,6 +13,9 @@ if [ "$NO_VERIFY" ]; then fi # list all added/copied/modified/renamed java files -git diff --staged --name-only --diff-filter=ACMR | egrep -a '.java$' | tr "\n" "\0" | +files="$(git diff --staged --name-only --diff-filter=ACMR | egrep -a '.java$' | tr '\n' ' ')" +for f in $files; do # run google-java-format on each file and re-stage any new changes - xargs -0 -I % echo "$format_cmd --aosp -i '%'; git add -f '%'" | sh + $format_cmd --aosp -i "$f" + git add -f "$f" +done diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTAuthenticationEntryPoint.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTAuthenticationEntryPoint.java new file mode 100644 index 0000000..7dfc16d --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTAuthenticationEntryPoint.java @@ -0,0 +1,21 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.config; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +@Component +public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException) + throws IOException { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTRequestFilter.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTRequestFilter.java new file mode 100644 index 0000000..853083b --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTRequestFilter.java @@ -0,0 +1,69 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.config; + +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.JWTUserDetailsService; +import io.jsonwebtoken.ExpiredJwtException; +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@Component +public class JWTRequestFilter extends OncePerRequestFilter { + + @Autowired private JWTUserDetailsService jwtUserDetailsService; + + @Autowired private JWTTokenUtil jwtTokenUtil; + + @Override + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + final String requestTokenHeader = request.getHeader("Authorization"); + String username = null; + String jwtToken = null; + + // JWT Token is in th + // e form "Bearer token". Remove Bearer word and get + // only the Token + if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) { + jwtToken = requestTokenHeader.substring(7); + try { + username = jwtTokenUtil.getUsernameFromToken(jwtToken); + } catch (IllegalArgumentException e) { + System.out.println("Unable to get JWT Token"); + } catch (ExpiredJwtException e) { + System.out.println("JWT Token has expired"); + } + } else { + logger.warn("JWT Token does not begin with Bearer String"); + } // Once we get the token validate it. + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = + this.jwtUserDetailsService.loadUserByUsername( + username); // if token is valid configure Spring Security to manually + // set + // authentication + if (jwtTokenUtil.validateToken(jwtToken, userDetails)) { + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = + new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + usernamePasswordAuthenticationToken.setDetails( + new WebAuthenticationDetailsSource().buildDetails(request)); + // After setting the Authentication in the context, we specify + // that the current user is authenticated. So it passes the + // Spring Security Configurations successfully. + SecurityContextHolder.getContext() + .setAuthentication(usernamePasswordAuthenticationToken); + } + } + chain.doFilter(request, response); + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTTokenUtil.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTTokenUtil.java new file mode 100644 index 0000000..40d369f --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/JWTTokenUtil.java @@ -0,0 +1,72 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.config; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +@Component +public class JWTTokenUtil { + public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60; + + @Value("${jwt.secret}") + private String secret; + + // retrieve username from jwt token + public String getUsernameFromToken(String token) { + return getClaimFromToken(token, Claims::getSubject); + } + + // retrieve expiration date from jwt token + public Date getExpirationDateFromToken(String token) { + return getClaimFromToken(token, Claims::getExpiration); + } + + public T getClaimFromToken(String token, Function claimsResolver) { + final Claims claims = getAllClaimsFromToken(token); + return claimsResolver.apply(claims); + } + + // for retrieveing any information from token we will need the secret key + private Claims getAllClaimsFromToken(String token) { + return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + } // check if the token has expired + + private Boolean isTokenExpired(String token) { + final Date expiration = getExpirationDateFromToken(token); + return expiration.before(new Date()); + } // generate token for user + + public String generateToken(UserDetails userDetails) { + Map claims = new HashMap<>(); + return doGenerateToken(claims, userDetails.getUsername()); + } + + // while creating the token - + // 1. Define claims of the token, like Issuer, Expiration, Subject, and the ID + // 2. Sign the JWT using the HS512 algorithm and secret key. + // 3. According to JWS Compact + // Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1) + // compaction of the JWT to a URL-safe string + private String doGenerateToken(Map claims, String subject) { + return Jwts.builder() + .setClaims(claims) + .setSubject(subject) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)) + .signWith(SignatureAlgorithm.HS512, secret) + .compact(); + } + + // validate token + public Boolean validateToken(String token, UserDetails userDetails) { + final String username = getUsernameFromToken(token); + return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/WebSecurityConfig.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/WebSecurityConfig.java new file mode 100644 index 0000000..a8bcb1d --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/config/WebSecurityConfig.java @@ -0,0 +1,75 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.config; + +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.JWTUserDetailsService; +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.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + @Autowired private JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint; + @Autowired private JWTUserDetailsService jwtUserDetailsService; + @Autowired private JWTRequestFilter jwtRequestFilter; + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + // configure AuthenticationManager so that it knows from where to load + // user for matching credentials + // Use BCryptPasswordEncoder + auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder()); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Override + protected void configure(HttpSecurity httpSecurity) throws Exception { + // We don't need CSRF for this example + httpSecurity + .csrf() + .disable() + // dont authenticate this particular request + .authorizeRequests() + .antMatchers("/auth/login") + .permitAll() + .antMatchers("/auth/register") + .permitAll() + . + // all other requests need to be authenticated + anyRequest() + .authenticated() + .and() + . + // make sure we use stateless session; session won't be used to + // store user's state. + exceptionHandling() + .authenticationEntryPoint(jwtAuthenticationEntryPoint) + .and() + .sessionManagement() + .sessionCreationPolicy( + SessionCreationPolicy + .STATELESS); // Add a filter to validate the tokens with every + // request + httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/JWTAuthenticationController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/JWTAuthenticationController.java new file mode 100644 index 0000000..6e3d01b --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/JWTAuthenticationController.java @@ -0,0 +1,57 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; + +import ch.usi.inf.sa4.sanmarinoes.smarthut.config.JWTTokenUtil; +import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.web.bind.annotation.*; + +@RestController +@CrossOrigin +@RequestMapping("/auth") +public class JWTAuthenticationController { + + @Autowired private AuthenticationManager authenticationManager; + + @Autowired private JWTTokenUtil jwtTokenUtil; + + @Autowired private JWTUserDetailsService userDetailsService; + + @Autowired private UserRepository users; + + private BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + + @PostMapping("/login") + public JWTResponse login(@RequestBody JWTRequest authenticationRequest) throws Exception { + authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword()); + final UserDetails userDetails = + userDetailsService.loadUserByUsername(authenticationRequest.getUsername()); + final String token = jwtTokenUtil.generateToken(userDetails); + return new JWTResponse(token); + } + + @PostMapping("/register") + public User register(@RequestBody User user) { + user.setPassword(encoder.encode(user.getPassword())); + users.save(user); + return user; + } + + private void authenticate(String username, String password) throws Exception { + try { + authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken(username, password)); + } catch (DisabledException e) { + e.printStackTrace(); + throw new Exception("USER_DISABLED", e); + } catch (BadCredentialsException e) { + e.printStackTrace(); + throw new Exception("INVALID_CREDENTIALS", e); + } + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/UserController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/UserController.java deleted file mode 100644 index 55e9139..0000000 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/UserController.java +++ /dev/null @@ -1,30 +0,0 @@ -package ch.usi.inf.sa4.sanmarinoes.smarthut.controller; - -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.SecurityService; -import ch.usi.inf.sa4.sanmarinoes.smarthut.models.UserService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; - -@Controller -public class UserController { - @Autowired private UserService userService; - - @Autowired private SecurityService securityService; - - /* - @PostMapping("/registration") - public String registration(@ModelAttribute("userForm") User userForm, BindingResult bindingResult) { - userValidator.validate(userForm, bindingResult); - - if (bindingResult.hasErrors()) { - return "registration"; - } - - userService.save(userForm); - - securityService.autoLogin(userForm.getUsername(), userForm.getPasswordConfirm()); - - return "redirect:/welcome"; - } - */ -} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTRequest.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTRequest.java new file mode 100644 index 0000000..66d0c75 --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTRequest.java @@ -0,0 +1,30 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.models; + +public class JWTRequest { + private String username; + private String password; + + // need default constructor for JSON Parsing + public JWTRequest() {} + + public JWTRequest(String username, String password) { + this.setUsername(username); + this.setPassword(password); + } + + public String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTResponse.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTResponse.java new file mode 100644 index 0000000..9eb1092 --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTResponse.java @@ -0,0 +1,13 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.models; + +public class JWTResponse { + private final String jwttoken; + + public JWTResponse(String jwttoken) { + this.jwttoken = jwttoken; + } + + public String getToken() { + return this.jwttoken; + } +} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserDetailsServiceImpl.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTUserDetailsService.java similarity index 77% rename from src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserDetailsServiceImpl.java rename to src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTUserDetailsService.java index c1d9ff1..53341d5 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserDetailsServiceImpl.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTUserDetailsService.java @@ -3,21 +3,23 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.*; -import org.springframework.security.core.authority.SimpleGrantedAuthority; 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.Component; -public class UserDetailsServiceImpl implements UserDetailsService { +@Component +public class JWTUserDetailsService implements UserDetailsService { @Autowired private UserRepository repository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User toReturn = repository.findByUsername(username); + System.out.println("malusa: " + username); + System.out.println(toReturn); if (toReturn != null) { - Set authoritySet = Set.of(new SimpleGrantedAuthority("user")); return new org.springframework.security.core.userdetails.User( - toReturn.getUsername(), toReturn.getPassword(), authoritySet); + toReturn.getUsername(), toReturn.getPassword(), Set.of()); } else { throw new UsernameNotFoundException(username); } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SecurityService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SecurityService.java deleted file mode 100644 index 5ff2e3b..0000000 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/SecurityService.java +++ /dev/null @@ -1,42 +0,0 @@ -package ch.usi.inf.sa4.sanmarinoes.smarthut.models; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -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; - -public class SecurityService { - @Autowired private AuthenticationManager manager; - - @Autowired private UserDetailsService service; - - private Logger logger = LoggerFactory.getLogger(SecurityService.class); - - public String loggedUser() { - Object details = SecurityContextHolder.getContext().getAuthentication().getDetails(); - if (details instanceof UserDetails) { - return ((UserDetails) details).getUsername(); - } else { - return null; - } - } - - public void autoLogin(String username, String password) { - UserDetails userDetails = service.loadUserByUsername(username); - UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = - new UsernamePasswordAuthenticationToken( - userDetails, password, userDetails.getAuthorities()); - - manager.authenticate(usernamePasswordAuthenticationToken); - - if (usernamePasswordAuthenticationToken.isAuthenticated()) { - SecurityContextHolder.getContext() - .setAuthentication(usernamePasswordAuthenticationToken); - logger.debug(String.format("Auto login %s successfully!", username)); - } - } -} diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java index 7378306..fcc9b05 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java @@ -2,6 +2,7 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut.models; import java.util.Set; import javax.persistence.*; +import javax.validation.constraints.NotNull; /** A user of the Smarthut application */ @Entity(name = "smarthutuser") @@ -13,16 +14,16 @@ public class User { private Long id; /** The full name of the user */ - @Column private String name; + @Column @NotNull private String name; /** The full name of the user */ - @Column private String username; + @Column @NotNull private String username; /** A properly salted way to store the password TODO: define the implementation of salt */ - @Column private String password; + @Column @NotNull private String password; /** The user's email TODO: validate email in setters */ - @Column private String email; + @Column @NotNull private String email; /** All rooms in the user's house */ @OneToMany(mappedBy = "user") diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserService.java deleted file mode 100644 index 3ae7b93..0000000 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/UserService.java +++ /dev/null @@ -1,22 +0,0 @@ -package ch.usi.inf.sa4.sanmarinoes.smarthut.models; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.stereotype.Service; - -@Service -public class UserService { - - @Autowired private UserRepository userRepository; - - @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; - - public void save(User user) { - user.setPassword(bCryptPasswordEncoder.encode(user.getPassword())); - userRepository.save(user); - } - - public User findByUsername(String username) { - return userRepository.findByUsername(username); - } -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 7cadd1e..bbbffc7 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,4 +9,5 @@ spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.properties.hibernate.format_sql=true -, \ No newline at end of file + +jwt.secret=thiskeymustbeverylongorthethingcomplainssoiamjustgoingtowritehereabunchofgarbageciaomamma \ No newline at end of file From 0cb1f1606a0cbb05a1a8e208ee394f73bca4f8c4 Mon Sep 17 00:00:00 2001 From: Claudio Maggioni Date: Wed, 26 Feb 2020 14:41:11 +0100 Subject: [PATCH 4/4] Updated hibernate DDL policy and solved LazyInitializationException bug on User.toString() --- .../inf/sa4/sanmarinoes/smarthut/models/Device.java | 11 +++++------ .../smarthut/models/JWTUserDetailsService.java | 2 -- .../usi/inf/sa4/sanmarinoes/smarthut/models/User.java | 5 +---- src/main/resources/application.properties | 2 +- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java index 078a7ad..d6108e8 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/Device.java @@ -29,15 +29,15 @@ public abstract class Device { /** * The name for the category of this particular device (e.g 'dimmer'). Not stored in the - * database (thanks to 'transient') but set thanks to constructors + * database but set thanks to constructors */ - private final transient String kind; + @Transient private final String kind; /** - * The way this device behaves in the automation flow. Not stored in the database (thanks to - * 'transient') but set thanks to constructors + * The way this device behaves in the automation flow. Not stored in the database but set thanks + * to constructors */ - private final transient FlowType flowType; + @Transient private final FlowType flowType; public long getId() { return id; @@ -66,6 +66,5 @@ public abstract class Device { public Device(String kind, FlowType flowType) { this.kind = kind; this.flowType = flowType; - this.name = kind + " " + this.id; } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTUserDetailsService.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTUserDetailsService.java index 53341d5..b20832b 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTUserDetailsService.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/JWTUserDetailsService.java @@ -15,8 +15,6 @@ public class JWTUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User toReturn = repository.findByUsername(username); - System.out.println("malusa: " + username); - System.out.println(toReturn); if (toReturn != null) { return new org.springframework.security.core.userdetails.User( toReturn.getUsername(), toReturn.getPassword(), Set.of()); diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java index fcc9b05..7f9ec95 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/models/User.java @@ -86,9 +86,6 @@ public class User { + '\'' + ", email='" + email - + '\'' - + ", rooms=" - + rooms - + '}'; + + "\'}"; } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index bbbffc7..30d24cd 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,7 +6,7 @@ spring.datasource.password= # Hibernate properties spring.jpa.database=POSTGRESQL spring.jpa.show-sql=true -spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.hibernate.ddl-auto=update spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl spring.jpa.properties.hibernate.format_sql=true