diff --git a/pom.xml b/pom.xml index 0679783..1aa7a26 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.7 + 3.4.1 @@ -30,12 +30,12 @@ org.springframework.boot spring-boot-starter-web - + - tomcat-embed-el - org.apache.tomcat.embed - - + tomcat-embed-el + org.apache.tomcat.embed + + org.springframework.boot @@ -47,25 +47,27 @@ - txw2 - org.glassfish.jaxb + txw2 + org.glassfish.jaxb - + - org.springframework.mobile - spring-mobile-device - 1.1.5.RELEASE + io.jsonwebtoken + jjwt-api + 0.12.6 - io.jsonwebtoken - jjwt - 0.6.0 + jjwt-impl + 0.12.6 + runtime - javax.xml.bind - jaxb-api + io.jsonwebtoken + jjwt-jackson + 0.12.6 + runtime @@ -141,5 +143,4 @@ - diff --git a/src/main/java/com/bfwg/Application.java b/src/main/java/com/bfwg/Application.java index d90f6a7..4ba8bba 100644 --- a/src/main/java/com/bfwg/Application.java +++ b/src/main/java/com/bfwg/Application.java @@ -9,6 +9,4 @@ public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } -} - - +} \ No newline at end of file diff --git a/src/main/java/com/bfwg/common/DeviceProvider.java b/src/main/java/com/bfwg/common/DeviceProvider.java deleted file mode 100644 index b0b00b9..0000000 --- a/src/main/java/com/bfwg/common/DeviceProvider.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.bfwg.common; - -import org.springframework.mobile.device.Device; -import org.springframework.mobile.device.DeviceUtils; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; - -/** - * Created by fanjin on 2017-10-16. - */ -@Component -public class DeviceProvider { - - public Device getCurrentDevice(HttpServletRequest request) { - return DeviceUtils.getCurrentDevice(request); - } -} diff --git a/src/main/java/com/bfwg/common/TimeProvider.java b/src/main/java/com/bfwg/common/TimeProvider.java index ce735ea..15b412f 100644 --- a/src/main/java/com/bfwg/common/TimeProvider.java +++ b/src/main/java/com/bfwg/common/TimeProvider.java @@ -11,9 +11,9 @@ @Component public class TimeProvider implements Serializable { - private static final long serialVersionUID = -3301695478208950415L; + private static final long serialVersionUID = -3301695478208950415L; - public Date now() { - return new Date(); - } + public Date now() { + return new Date(); + } } \ No newline at end of file diff --git a/src/main/java/com/bfwg/config/WebConfig.java b/src/main/java/com/bfwg/config/WebConfig.java deleted file mode 100644 index 0adb14c..0000000 --- a/src/main/java/com/bfwg/config/WebConfig.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.bfwg.config; - -import java.util.List; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.mobile.device.DeviceHandlerMethodArgumentResolver; -import org.springframework.mobile.device.DeviceResolverHandlerInterceptor; -import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class WebConfig implements WebMvcConfigurer { - - @Bean - public DeviceResolverHandlerInterceptor deviceResolverHandlerInterceptor() { - return new DeviceResolverHandlerInterceptor(); - } - - @Bean - public DeviceHandlerMethodArgumentResolver deviceHandlerMethodArgumentResolver() { - return new DeviceHandlerMethodArgumentResolver(); - } - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(deviceResolverHandlerInterceptor()); - } - - @Override - public void addArgumentResolvers(List argumentResolvers) { - argumentResolvers.add(deviceHandlerMethodArgumentResolver()); - } - -} diff --git a/src/main/java/com/bfwg/config/WebSecurityConfig.java b/src/main/java/com/bfwg/config/WebSecurityConfig.java index 8f7f047..20ffd83 100644 --- a/src/main/java/com/bfwg/config/WebSecurityConfig.java +++ b/src/main/java/com/bfwg/config/WebSecurityConfig.java @@ -1,5 +1,7 @@ package com.bfwg.config; +import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -7,7 +9,7 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 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.WebSecurityCustomizer; @@ -27,7 +29,7 @@ @Configuration @EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled = true) +@EnableMethodSecurity(prePostEnabled = true) public class WebSecurityConfig { @Autowired @@ -41,8 +43,7 @@ public class WebSecurityConfig { @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { - auth.userDetailsService(jwtUserDetailsService) - .passwordEncoder(passwordEncoder); + auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder); } @Autowired @@ -56,27 +57,20 @@ public AuthenticationManager authenticationManager(AuthenticationConfiguration a @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() - .exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint).and() - .authorizeRequests() - .antMatchers( - HttpMethod.GET, - "/", - "/auth/**", - "/webjars/**", - "/*.html", - "/favicon.ico", - "/**/*.html", - "/**/*.css", - "/**/*.js") - .permitAll() - .antMatchers("/auth/**").permitAll() - .anyRequest().authenticated().and() - .addFilterBefore(new TokenAuthenticationFilter(tokenHelper, jwtUserDetailsService), - BasicAuthenticationFilter.class); - http.csrf().disable(); + http.addFilterBefore(new TokenAuthenticationFilter(tokenHelper, jwtUserDetailsService), + BasicAuthenticationFilter.class) + .authorizeHttpRequests(authorize -> authorize + .requestMatchers(antMatcher(HttpMethod.GET, "/"), antMatcher(HttpMethod.GET, "/auth/**"), + antMatcher(HttpMethod.GET, "/webjars/**"), antMatcher(HttpMethod.GET, "/*.html"), + antMatcher(HttpMethod.GET, "/favicon.ico"), antMatcher(HttpMethod.GET, "/**/*.html"), + antMatcher(HttpMethod.GET, "/**/*.css"), antMatcher(HttpMethod.GET, "/**/*.js")) + .permitAll().requestMatchers("/auth/**").permitAll().anyRequest().authenticated()) + .sessionManagement(sec -> sec.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .exceptionHandling( + exceptionHandler -> exceptionHandler.authenticationEntryPoint(restAuthenticationEntryPoint)) + .csrf(csrf -> csrf.disable()); + return http.build(); } @@ -84,18 +78,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public WebSecurityCustomizer webSecurityCustomizer() { // TokenAuthenticationFilter will ignore the below paths return (web) -> { - web.ignoring().antMatchers( - HttpMethod.POST, - "/auth/login"); - web.ignoring().antMatchers( - HttpMethod.GET, - "/", - "/webjars/**", - "/*.html", - "/favicon.ico", - "/**/*.html", - "/**/*.css", - "/**/*.js"); + web.ignoring().requestMatchers(HttpMethod.POST, "/auth/login").requestMatchers( + antMatcher(HttpMethod.GET, "/"), antMatcher(HttpMethod.GET, "/webjars/**"), + antMatcher(HttpMethod.GET, "/*.html"), antMatcher(HttpMethod.GET, "/favicon.ico"), + antMatcher(HttpMethod.GET, "/**/*.html"), antMatcher(HttpMethod.GET, "/**path/*.css"), + antMatcher(HttpMethod.GET, "/**path/*.js")); }; } } diff --git a/src/main/java/com/bfwg/model/Authority.java b/src/main/java/com/bfwg/model/Authority.java index 4d42424..b8c4d5b 100644 --- a/src/main/java/com/bfwg/model/Authority.java +++ b/src/main/java/com/bfwg/model/Authority.java @@ -1,48 +1,56 @@ package com.bfwg.model; -import com.fasterxml.jackson.annotation.JsonIgnore; import org.springframework.security.core.GrantedAuthority; -import javax.persistence.*; +import com.fasterxml.jackson.annotation.JsonIgnore; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; /** * Created by fan.jin on 2016-11-03. */ @Entity -@Table(name="AUTHORITY") +@Table(name = "AUTHORITY") public class Authority implements GrantedAuthority { - @Id - @Column(name="id") - @GeneratedValue(strategy = GenerationType.IDENTITY) - Long id; - - @Enumerated( EnumType.STRING) - @Column(name="name") - UserRoleName name; - - @Override - public String getAuthority() { - return name.name(); - } - - public void setName(UserRoleName name) { - this.name = name; - } - - @JsonIgnore - public UserRoleName getName() { - return name; - } - - @JsonIgnore - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + + @Enumerated(EnumType.STRING) + @Column(name = "name") + UserRoleName name; + + @Override + public String getAuthority() { + return name.name(); + } + + public void setName(UserRoleName name) { + this.name = name; + } + + @JsonIgnore + public UserRoleName getName() { + return name; + } + + @JsonIgnore + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } } diff --git a/src/main/java/com/bfwg/model/User.java b/src/main/java/com/bfwg/model/User.java index 3ae26b7..ea34088 100644 --- a/src/main/java/com/bfwg/model/User.java +++ b/src/main/java/com/bfwg/model/User.java @@ -4,23 +4,23 @@ import java.util.Collection; import java.util.List; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.JoinTable; -import javax.persistence.ManyToMany; -import javax.persistence.Table; - import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.Table; + /** * Created by fan.jin on 2016-10-15. */ diff --git a/src/main/java/com/bfwg/model/UserRoleName.java b/src/main/java/com/bfwg/model/UserRoleName.java index 3ddf821..c957e62 100644 --- a/src/main/java/com/bfwg/model/UserRoleName.java +++ b/src/main/java/com/bfwg/model/UserRoleName.java @@ -1,6 +1,5 @@ package com.bfwg.model; public enum UserRoleName { - ROLE_USER, - ROLE_ADMIN + ROLE_USER, ROLE_ADMIN } diff --git a/src/main/java/com/bfwg/model/UserTokenState.java b/src/main/java/com/bfwg/model/UserTokenState.java index 9fe5d0b..c8c083b 100644 --- a/src/main/java/com/bfwg/model/UserTokenState.java +++ b/src/main/java/com/bfwg/model/UserTokenState.java @@ -4,32 +4,32 @@ * Created by fan.jin on 2016-10-17. */ public class UserTokenState { - private String access_token; - private Long expires_in; + private String access_token; + private Long expires_in; - public UserTokenState() { - this.access_token = null; - this.expires_in = null; - } + public UserTokenState() { + this.access_token = null; + this.expires_in = null; + } - public UserTokenState(String access_token, long expires_in) { - this.access_token = access_token; - this.expires_in = expires_in; - } + public UserTokenState(String access_token, long expires_in) { + this.access_token = access_token; + this.expires_in = expires_in; + } - public String getAccess_token() { - return access_token; - } + public String getAccess_token() { + return access_token; + } - public void setAccess_token(String access_token) { - this.access_token = access_token; - } + public void setAccess_token(String access_token) { + this.access_token = access_token; + } - public Long getExpires_in() { - return expires_in; - } + public Long getExpires_in() { + return expires_in; + } - public void setExpires_in(Long expires_in) { - this.expires_in = expires_in; - } + public void setExpires_in(Long expires_in) { + this.expires_in = expires_in; + } } \ No newline at end of file diff --git a/src/main/java/com/bfwg/repository/UserRepository.java b/src/main/java/com/bfwg/repository/UserRepository.java index 3eb565b..e6fed9a 100644 --- a/src/main/java/com/bfwg/repository/UserRepository.java +++ b/src/main/java/com/bfwg/repository/UserRepository.java @@ -7,6 +7,5 @@ * Created by fan.jin on 2016-10-15. */ public interface UserRepository extends JpaRepository { - User findByUsername( String username ); + User findByUsername(String username); } - diff --git a/src/main/java/com/bfwg/rest/AuthenticationController.java b/src/main/java/com/bfwg/rest/AuthenticationController.java index 5ac4a37..2fcca32 100644 --- a/src/main/java/com/bfwg/rest/AuthenticationController.java +++ b/src/main/java/com/bfwg/rest/AuthenticationController.java @@ -5,14 +5,10 @@ import java.util.HashMap; import java.util.Map; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.mobile.device.Device; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -24,13 +20,15 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.bfwg.common.DeviceProvider; import com.bfwg.model.User; import com.bfwg.model.UserTokenState; import com.bfwg.security.TokenHelper; import com.bfwg.security.auth.JwtAuthenticationRequest; import com.bfwg.service.UserService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + /** * Created by fan.jin on 2017-05-10. */ @@ -49,19 +47,13 @@ public class AuthenticationController { @Autowired private UserService userService; - @Autowired - private DeviceProvider deviceProvider; - @PostMapping("/login") - public ResponseEntity createAuthenticationToken( - @RequestBody JwtAuthenticationRequest authenticationRequest, - HttpServletResponse response, - Device device) throws AuthenticationException, IOException { + public ResponseEntity createAuthenticationToken(@RequestBody JwtAuthenticationRequest authenticationRequest, + HttpServletResponse response) throws AuthenticationException, IOException { // Perform the security - final Authentication authentication = authenticationManager.authenticate( - new UsernamePasswordAuthenticationToken( - authenticationRequest.getUsername(), + final Authentication authentication = authenticationManager + .authenticate(new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())); // Inject into security context @@ -69,27 +61,23 @@ public ResponseEntity createAuthenticationToken( // token creation User user = (User) authentication.getPrincipal(); - String jws = tokenHelper.generateToken(user.getUsername(), device); - int expiresIn = tokenHelper.getExpiredIn(device); + String jws = tokenHelper.generateToken(user.getUsername()); + int expiresIn = tokenHelper.getExpiredIn(); // Return the token return ResponseEntity.ok(new UserTokenState(jws, expiresIn)); } @PostMapping("/refresh") - public ResponseEntity refreshAuthenticationToken( - HttpServletRequest request, - HttpServletResponse response, + public ResponseEntity refreshAuthenticationToken(HttpServletRequest request, HttpServletResponse response, Principal principal) { String authToken = tokenHelper.getToken(request); - Device device = deviceProvider.getCurrentDevice(request); - if (authToken != null && principal != null) { // TODO check user password last update - String refreshedToken = tokenHelper.refreshToken(authToken, device); - int expiresIn = tokenHelper.getExpiredIn(device); + String refreshedToken = tokenHelper.refreshToken(authToken); + int expiresIn = tokenHelper.getExpiredIn(); return ResponseEntity.ok(new UserTokenState(refreshedToken, expiresIn)); } else { diff --git a/src/main/java/com/bfwg/security/TokenHelper.java b/src/main/java/com/bfwg/security/TokenHelper.java index 710a26f..28ec6a2 100644 --- a/src/main/java/com/bfwg/security/TokenHelper.java +++ b/src/main/java/com/bfwg/security/TokenHelper.java @@ -1,19 +1,21 @@ package com.bfwg.security; -import com.bfwg.common.TimeProvider; -import com.bfwg.model.User; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; +import java.util.Date; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.mobile.device.Device; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; -import javax.servlet.http.HttpServletRequest; -import java.util.Date; +import com.bfwg.common.TimeProvider; +import com.bfwg.model.User; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.MacAlgorithm; +import jakarta.servlet.http.HttpServletRequest; /** * Created by fan.jin on 2016-10-19. @@ -22,157 +24,109 @@ @Component public class TokenHelper { - @Value("${app.name}") - private String APP_NAME; - - @Value("${jwt.secret}") - public String SECRET; - - @Value("${jwt.expires_in}") - private int EXPIRES_IN; - - @Value("${jwt.mobile_expires_in}") - private int MOBILE_EXPIRES_IN; - - @Value("${jwt.header}") - private String AUTH_HEADER; - - static final String AUDIENCE_UNKNOWN = "unknown"; - static final String AUDIENCE_WEB = "web"; - static final String AUDIENCE_MOBILE = "mobile"; - static final String AUDIENCE_TABLET = "tablet"; - - @Autowired - TimeProvider timeProvider; - - private SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS512; - - public String getUsernameFromToken(String token) { - String username; - try { - final Claims claims = this.getAllClaimsFromToken(token); - username = claims.getSubject(); - } catch (Exception e) { - username = null; - } - return username; - } - - public Date getIssuedAtDateFromToken(String token) { - Date issueAt; - try { - final Claims claims = this.getAllClaimsFromToken(token); - issueAt = claims.getIssuedAt(); - } catch (Exception e) { - issueAt = null; - } - return issueAt; - } - - public String getAudienceFromToken(String token) { - String audience; - try { - final Claims claims = this.getAllClaimsFromToken(token); - audience = claims.getAudience(); - } catch (Exception e) { - audience = null; - } - return audience; - } - - public String refreshToken(String token, Device device) { - String refreshedToken; - Date a = timeProvider.now(); - try { - final Claims claims = this.getAllClaimsFromToken(token); - claims.setIssuedAt(a); - refreshedToken = Jwts.builder() - .setClaims(claims) - .setExpiration(generateExpirationDate(device)) - .signWith( SIGNATURE_ALGORITHM, SECRET ) - .compact(); - } catch (Exception e) { - refreshedToken = null; - } - return refreshedToken; - } - - public String generateToken(String username, Device device) { - String audience = generateAudience(device); - return Jwts.builder() - .setIssuer( APP_NAME ) - .setSubject(username) - .setAudience(audience) - .setIssuedAt(timeProvider.now()) - .setExpiration(generateExpirationDate(device)) - .signWith( SIGNATURE_ALGORITHM, SECRET ) - .compact(); - } - - private String generateAudience(Device device) { - String audience = AUDIENCE_UNKNOWN; - if (device.isNormal()) { - audience = AUDIENCE_WEB; - } else if (device.isTablet()) { - audience = AUDIENCE_TABLET; - } else if (device.isMobile()) { - audience = AUDIENCE_MOBILE; - } - return audience; - } - - private Claims getAllClaimsFromToken(String token) { - Claims claims; - try { - claims = Jwts.parser() - .setSigningKey(SECRET) - .parseClaimsJws(token) - .getBody(); - } catch (Exception e) { - claims = null; - } - return claims; - } - - private Date generateExpirationDate(Device device) { - long expiresIn = device.isTablet() || device.isMobile() ? MOBILE_EXPIRES_IN : EXPIRES_IN; - return new Date(timeProvider.now().getTime() + expiresIn * 1000); - } - - public int getExpiredIn(Device device) { - return device.isMobile() || device.isTablet() ? MOBILE_EXPIRES_IN : EXPIRES_IN; - } - - public Boolean validateToken(String token, UserDetails userDetails) { - User user = (User) userDetails; - final String username = getUsernameFromToken(token); - final Date created = getIssuedAtDateFromToken(token); - return ( - username != null && - username.equals(userDetails.getUsername()) && - !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate()) - ); - } - - private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) { - return (lastPasswordReset != null && created.before(lastPasswordReset)); - } - - public String getToken( HttpServletRequest request ) { - /** - * Getting the token from Authentication header - * e.g Bearer your_token - */ - String authHeader = getAuthHeaderFromHeader( request ); - if ( authHeader != null && authHeader.startsWith("Bearer ")) { - return authHeader.substring(7); - } - - return null; - } - - public String getAuthHeaderFromHeader( HttpServletRequest request ) { - return request.getHeader(AUTH_HEADER); - } + @Value("${app.name}") + private String APP_NAME; + + @Value("${jwt.secret}") + public String SECRET; + + @Value("${jwt.expires_in}") + private int EXPIRES_IN; + + @Value("${jwt.header}") + private String AUTH_HEADER; + + @Autowired + TimeProvider timeProvider; + + private MacAlgorithm SIGNATURE_ALGORITHM = Jwts.SIG.HS512; + + public String getUsernameFromToken(String token) { + String username; + try { + final Claims claims = this.getAllClaimsFromToken(token); + username = claims.getSubject(); + } catch (Exception e) { + username = null; + } + return username; + } + + public Date getIssuedAtDateFromToken(String token) { + Date issueAt; + try { + final Claims claims = this.getAllClaimsFromToken(token); + issueAt = claims.getIssuedAt(); + } catch (Exception e) { + issueAt = null; + } + return issueAt; + } + + public String refreshToken(String token) { + String refreshedToken; + Date a = timeProvider.now(); + try { + final Claims claims = getAllClaimsFromToken(token); + refreshedToken = Jwts.builder().claims(claims).issuedAt(a).expiration(generateExpirationDate()) + .signWith(Keys.hmacShaKeyFor(SECRET.getBytes()), SIGNATURE_ALGORITHM).compact(); + } catch (Exception e) { + refreshedToken = null; + } + return refreshedToken; + } + + public String generateToken(String username) { + return Jwts.builder().issuer(APP_NAME).subject(username).issuedAt(timeProvider.now()) + .expiration(generateExpirationDate()) + .signWith(Keys.hmacShaKeyFor(SECRET.getBytes()), SIGNATURE_ALGORITHM).compact(); + } + + private Claims getAllClaimsFromToken(String token) { + Jws claims; + try { + claims = Jwts.parser().verifyWith(Keys.hmacShaKeyFor(SECRET.getBytes())).build().parseSignedClaims(token); + } catch (Exception e) { + claims = null; + } + return claims.getPayload(); + } + + private Date generateExpirationDate() { + long expiresIn = EXPIRES_IN; + return new Date(timeProvider.now().getTime() + expiresIn * 1000); + } + + public int getExpiredIn() { + return EXPIRES_IN; + } + + public Boolean validateToken(String token, UserDetails userDetails) { + User user = (User) userDetails; + final String username = getUsernameFromToken(token); + final Date created = getIssuedAtDateFromToken(token); + return (username != null && username.equals(userDetails.getUsername()) + && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())); + } + + private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) { + return (lastPasswordReset != null && created.before(lastPasswordReset)); + } + + public String getToken(HttpServletRequest request) { + /** + * Getting the token from Authentication header e.g Bearer your_token + */ + String authHeader = getAuthHeaderFromHeader(request); + if (authHeader != null && authHeader.startsWith("Bearer ")) { + return authHeader.substring(7); + } + + return null; + } + + public String getAuthHeaderFromHeader(HttpServletRequest request) { + return request.getHeader(AUTH_HEADER); + } } \ No newline at end of file diff --git a/src/main/java/com/bfwg/security/auth/JwtAuthenticationRequest.java b/src/main/java/com/bfwg/security/auth/JwtAuthenticationRequest.java index 3fe4433..a3e3ab5 100644 --- a/src/main/java/com/bfwg/security/auth/JwtAuthenticationRequest.java +++ b/src/main/java/com/bfwg/security/auth/JwtAuthenticationRequest.java @@ -4,31 +4,31 @@ * Created by fanjin on 2017-10-09. */ public class JwtAuthenticationRequest { - private String username; - private String password; + private String username; + private String password; - public JwtAuthenticationRequest() { - super(); - } + public JwtAuthenticationRequest() { + super(); + } - public JwtAuthenticationRequest(String username, String password) { - this.setUsername(username); - this.setPassword(password); - } + public JwtAuthenticationRequest(String username, String password) { + this.setUsername(username); + this.setPassword(password); + } - public String getUsername() { - return this.username; - } + public String getUsername() { + return this.username; + } - public void setUsername(String username) { - this.username = username; - } + public void setUsername(String username) { + this.username = username; + } - public String getPassword() { - return this.password; - } + public String getPassword() { + return this.password; + } - public void setPassword(String password) { - this.password = password; - } + public void setPassword(String password) { + this.password = password; + } } diff --git a/src/main/java/com/bfwg/security/auth/RestAuthenticationEntryPoint.java b/src/main/java/com/bfwg/security/auth/RestAuthenticationEntryPoint.java index 2e99c8d..b3fd74f 100644 --- a/src/main/java/com/bfwg/security/auth/RestAuthenticationEntryPoint.java +++ b/src/main/java/com/bfwg/security/auth/RestAuthenticationEntryPoint.java @@ -1,5 +1,7 @@ package com.bfwg.security.auth; +import java.io.IOException; + /** * Created by fan.jin on 2016-11-12. */ @@ -8,9 +10,8 @@ import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; /** * Created by fan.jin on 2016-11-07. @@ -18,13 +19,13 @@ @Component public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { - @Override - public void commence(HttpServletRequest request, - HttpServletResponse response, - AuthenticationException authException) throws IOException { - // This is invoked when user tries to access a secured REST resource without supplying any credentials - // We should just send a 401 Unauthorized response because there is no 'login page' to redirect to - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage()); - } + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException { + // This is invoked when user tries to access a secured REST resource without + // supplying any credentials + // We should just send a 401 Unauthorized response because there is no 'login + // page' to redirect to + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage()); + } } - diff --git a/src/main/java/com/bfwg/security/auth/TokenAuthenticationFilter.java b/src/main/java/com/bfwg/security/auth/TokenAuthenticationFilter.java index 0da6b4f..95be82b 100644 --- a/src/main/java/com/bfwg/security/auth/TokenAuthenticationFilter.java +++ b/src/main/java/com/bfwg/security/auth/TokenAuthenticationFilter.java @@ -1,6 +1,7 @@ package com.bfwg.security.auth; -import com.bfwg.security.TokenHelper; +import java.io.IOException; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.security.core.context.SecurityContextHolder; @@ -8,54 +9,51 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.web.filter.OncePerRequestFilter; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; +import com.bfwg.security.TokenHelper; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; /** * Created by fan.jin on 2016-10-19. */ public class TokenAuthenticationFilter extends OncePerRequestFilter { - private final Log logger = LogFactory.getLog(this.getClass()); - - private TokenHelper tokenHelper; - - private UserDetailsService userDetailsService; - - public TokenAuthenticationFilter(TokenHelper tokenHelper, UserDetailsService userDetailsService) { - this.tokenHelper = tokenHelper; - this.userDetailsService = userDetailsService; - } - - - @Override - public void doFilterInternal( - HttpServletRequest request, - HttpServletResponse response, - FilterChain chain - ) throws IOException, ServletException { - - String username; - String authToken = tokenHelper.getToken(request); - - if (authToken != null) { - // get username from token - username = tokenHelper.getUsernameFromToken(authToken); - if (username != null) { - // get user - UserDetails userDetails = userDetailsService.loadUserByUsername(username); - if (tokenHelper.validateToken(authToken, userDetails)) { - // create authentication - TokenBasedAuthentication authentication = new TokenBasedAuthentication(userDetails); - authentication.setToken(authToken); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - } - } - chain.doFilter(request, response); - } + private final Log logger = LogFactory.getLog(this.getClass()); + + private TokenHelper tokenHelper; + + private UserDetailsService userDetailsService; + + public TokenAuthenticationFilter(TokenHelper tokenHelper, UserDetailsService userDetailsService) { + this.tokenHelper = tokenHelper; + this.userDetailsService = userDetailsService; + } + + @Override + public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws IOException, ServletException { + + String username; + String authToken = tokenHelper.getToken(request); + + if (authToken != null) { + // get username from token + username = tokenHelper.getUsernameFromToken(authToken); + if (username != null) { + // get user + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + if (tokenHelper.validateToken(authToken, userDetails)) { + // create authentication + TokenBasedAuthentication authentication = new TokenBasedAuthentication(userDetails); + authentication.setToken(authToken); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + } + chain.doFilter(request, response); + } } \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 5ea37fe..39ad835 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,4 +5,4 @@ jwt: header: Authorization expires_in: 300 # 5 minutes mobile_expires_in: 600 # 10 minutes - secret: queenvictoria + secret: p2H(++:$j*xKfKvK6n5k=},@@]CWkbj7d0iQ%6/;0}x6?[wNWR,$=0/HHx*&pJAq diff --git a/src/test/java/com/bfwg/ApplicationTest.java b/src/test/java/com/bfwg/ApplicationTest.java index 32a710d..cec6329 100644 --- a/src/test/java/com/bfwg/ApplicationTest.java +++ b/src/test/java/com/bfwg/ApplicationTest.java @@ -12,7 +12,7 @@ @SpringBootTest public class ApplicationTest { - @Test - public void contextLoads() { - } + @Test + public void contextLoads() { + } } diff --git a/src/test/java/com/bfwg/rest/AuthenticationControllerTest.java b/src/test/java/com/bfwg/rest/AuthenticationControllerTest.java index e0a4ed6..de1f9a1 100644 --- a/src/test/java/com/bfwg/rest/AuthenticationControllerTest.java +++ b/src/test/java/com/bfwg/rest/AuthenticationControllerTest.java @@ -1,6 +1,5 @@ package com.bfwg.rest; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.when; @@ -22,7 +21,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.mobile.device.Device; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.util.ReflectionTestUtils; @@ -30,12 +28,10 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; -import com.bfwg.common.DeviceProvider; import com.bfwg.common.TimeProvider; import com.bfwg.model.Authority; import com.bfwg.model.User; import com.bfwg.model.UserRoleName; -import com.bfwg.security.DeviceDummy; import com.bfwg.security.TokenHelper; import com.bfwg.service.impl.CustomUserDetailsService; @@ -65,19 +61,10 @@ public class AuthenticationControllerTest { @Autowired private WebApplicationContext context; - @MockBean - private DeviceProvider deviceProvider; - - @Autowired - private DeviceDummy device; - @Before public void setup() { - mvc = MockMvcBuilders - .webAppContextSetup(context) - .apply(springSecurity()) - .build(); + mvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build(); User user = new User(); user.setUsername("username"); @@ -88,23 +75,18 @@ public void setup() { user.setAuthorities(authorities); user.setLastPasswordResetDate(new Timestamp(DateUtil.yesterday().getTime())); when(this.userDetailsService.loadUserByUsername(eq("testUser"))).thenReturn(user); - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); ReflectionTestUtils.setField(tokenHelper, "EXPIRES_IN", 100); // 100 sec - ReflectionTestUtils.setField(tokenHelper, "MOBILE_EXPIRES_IN", 200); // 200 sec - ReflectionTestUtils.setField(tokenHelper, "SECRET", "queenvictoria"); + ReflectionTestUtils.setField(tokenHelper, "SECRET", + "wb!.V.G]e&;4Q78&b,n[6F]PQg613!)-Gpy*CN{/[@LN.Z1Mn*aKH!*zg/pXLp/4"); - device.setMobile(false); - device.setNormal(false); - device.setTablet(false); } @Test public void shouldGetEmptyTokenStateWhenGivenValidOldToken() throws Exception { - when(timeProviderMock.now()) - .thenReturn(DateUtil.yesterday()); - this.mvc.perform(post("/auth/refresh") - .header("Authorization", "Bearer 123")) + when(timeProviderMock.now()).thenReturn(DateUtil.yesterday()); + this.mvc.perform(post("/auth/refresh").header("Authorization", "Bearer 123")) .andExpect(content().json("{access_token:null,expires_in:null}")); } @@ -112,58 +94,24 @@ public void shouldGetEmptyTokenStateWhenGivenValidOldToken() throws Exception { @WithMockUser(roles = "USER") public void shouldRefreshNotExpiredWebToken() throws Exception { - given(timeProviderMock.now()) - .willReturn(new Date(30L)); + given(timeProviderMock.now()).willReturn(new Date(30L)); - device.setNormal(true); - String token = createToken(device); - String refreshedToken = tokenHelper.refreshToken(token, device); - given(deviceProvider.getCurrentDevice(any())) - .willReturn(device); - this.mvc.perform(post("/auth/refresh") - .header("Authorization", "Bearer " + token)) + String token = createToken(); + String refreshedToken = tokenHelper.refreshToken(token); + this.mvc.perform(post("/auth/refresh").header("Authorization", "Bearer " + token)) .andExpect(content().json("{access_token:" + refreshedToken + ",expires_in:100}")); } - @Test - @WithMockUser(roles = "USER") - public void shouldRefreshNotExpiredMobileToken() throws Exception { - given(timeProviderMock.now()) - .willReturn(new Date(30L)); - device.setMobile(true); - String token = createToken(device); - String refreshedToken = tokenHelper.refreshToken(token, device); - given(deviceProvider.getCurrentDevice(any())) - .willReturn(device); - this.mvc.perform(post("/auth/refresh") - .header("Authorization", "Bearer " + token)) - .andExpect(content().json("{access_token:" + refreshedToken + ",expires_in:200}")); - } - @Test public void shouldNotRefreshExpiredWebToken() throws Exception { Date beforeSomeTime = new Date(DateUtil.now().getTime() - 15 * 1000); - when(timeProviderMock.now()) - .thenReturn(beforeSomeTime); - device.setNormal(true); - String token = createToken(device); - this.mvc.perform(post("/auth/refresh") - .header("Authorization", "Bearer " + token)) - .andExpect(content().json("{access_token:null,expires_in:null}")); - } - - @Test - public void shouldRefreshExpiredMobileToken() throws Exception { - Date beforeSomeTime = new Date(DateUtil.now().getTime() - 15 * 1000); - when(timeProviderMock.now()) - .thenReturn(beforeSomeTime); - device.setNormal(true); - String token = createToken(device); + when(timeProviderMock.now()).thenReturn(beforeSomeTime); + String token = createToken(); this.mvc.perform(post("/auth/refresh").header("Authorization", "Bearer " + token)) .andExpect(content().json("{access_token:null,expires_in:null}")); } - private String createToken(Device device) { - return tokenHelper.generateToken(TEST_USERNAME, device); + private String createToken() { + return tokenHelper.generateToken(TEST_USERNAME); } } diff --git a/src/test/java/com/bfwg/rest/UserControllerTest.java b/src/test/java/com/bfwg/rest/UserControllerTest.java index 58c179d..c07b8df 100644 --- a/src/test/java/com/bfwg/rest/UserControllerTest.java +++ b/src/test/java/com/bfwg/rest/UserControllerTest.java @@ -23,53 +23,44 @@ @SpringBootTest public class UserControllerTest { - private MockMvc mvc; + private MockMvc mvc; - @Autowired - private WebApplicationContext context; + @Autowired + private WebApplicationContext context; - @Before - public void setup() { - mvc = MockMvcBuilders - .webAppContextSetup(context) - .apply(springSecurity()) - .build(); - } + @Before + public void setup() { + mvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build(); + } - @Test - @WithAnonymousUser - public void shouldGetUnauthorizedWithoutRole() throws Exception { + @Test + @WithAnonymousUser + public void shouldGetUnauthorizedWithoutRole() throws Exception { - this.mvc.perform(get("/user")) - .andExpect(status().isUnauthorized()); - } + this.mvc.perform(get("/user")).andExpect(status().isUnauthorized()); + } - @Test - @WithMockUser(roles = "USER") - public void getPersonsSuccessfullyWithUserRole() throws Exception { - this.mvc.perform(get("/api/whoami")) - .andExpect(status().is2xxSuccessful()); - } + @Test + @WithMockUser(roles = "USER") + public void getPersonsSuccessfullyWithUserRole() throws Exception { + this.mvc.perform(get("/api/whoami")).andExpect(status().is2xxSuccessful()); + } - @Test - @WithAnonymousUser - public void getPersonsFailWithAnonymousUser() throws Exception { - this.mvc.perform(get("/api/whoami")) - .andExpect(status().is4xxClientError()); - } + @Test + @WithAnonymousUser + public void getPersonsFailWithAnonymousUser() throws Exception { + this.mvc.perform(get("/api/whoami")).andExpect(status().is4xxClientError()); + } - @Test - @WithMockUser(roles = "ADMIN") - public void getAllUserSuccessWithAdminRole() throws Exception { - this.mvc.perform(get("/api/user/all")) - .andExpect(status().is2xxSuccessful()); - } + @Test + @WithMockUser(roles = "ADMIN") + public void getAllUserSuccessWithAdminRole() throws Exception { + this.mvc.perform(get("/api/user/all")).andExpect(status().is2xxSuccessful()); + } - @Test - @WithMockUser(roles = "USER") - public void getAllUserFailWithUserRole() throws Exception { - this.mvc.perform(get("/api/user/all")) - .andExpect(status().is4xxClientError()); - } + @Test + @WithMockUser(roles = "USER") + public void getAllUserFailWithUserRole() throws Exception { + this.mvc.perform(get("/api/user/all")).andExpect(status().is4xxClientError()); + } } - diff --git a/src/test/java/com/bfwg/security/DeviceDummy.java b/src/test/java/com/bfwg/security/DeviceDummy.java deleted file mode 100644 index 0ec0f28..0000000 --- a/src/test/java/com/bfwg/security/DeviceDummy.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.bfwg.security; - -import org.springframework.mobile.device.Device; -import org.springframework.mobile.device.DevicePlatform; -import org.springframework.stereotype.Component; - -/** - * Created by fanjin on 2017-10-07. - */ -@Component -public class DeviceDummy implements Device { - private boolean normal; - private boolean mobile; - private boolean tablet; - - @Override - public boolean isNormal() { - return normal; - } - - @Override - public boolean isMobile() { - return mobile; - } - - @Override - public boolean isTablet() { - return tablet; - } - - @Override - public DevicePlatform getDevicePlatform() { - return null; - } - - public void setNormal(boolean normal) { - this.normal = normal; - } - - public void setMobile(boolean mobile) { - this.mobile = mobile; - } - - public void setTablet(boolean tablet) { - this.tablet = tablet; - } -} diff --git a/src/test/java/com/bfwg/security/TokenHelperTest.java b/src/test/java/com/bfwg/security/TokenHelperTest.java index d22ee90..d423442 100644 --- a/src/test/java/com/bfwg/security/TokenHelperTest.java +++ b/src/test/java/com/bfwg/security/TokenHelperTest.java @@ -1,161 +1,118 @@ package com.bfwg.security; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.sql.Timestamp; +import java.util.Date; -import com.bfwg.common.TimeProvider; -import com.bfwg.model.User; import org.assertj.core.util.DateUtil; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.springframework.mobile.device.Device; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.test.util.ReflectionTestUtils; -import java.sql.Timestamp; -import java.util.Date; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import com.bfwg.common.TimeProvider; +import com.bfwg.model.User; /** * Created by fan.jin on 2017-01-08. */ public class TokenHelperTest { - private static final String TEST_USERNAME = "testUser"; + private static final String TEST_USERNAME = "testUser"; + + @InjectMocks + private TokenHelper tokenHelper; + + @Mock + private TimeProvider timeProviderMock; + + @Before + public void init() { + MockitoAnnotations.openMocks(this); + + ReflectionTestUtils.setField(tokenHelper, "EXPIRES_IN", 10); // 10 sec + ReflectionTestUtils.setField(tokenHelper, "SECRET", + "Y0[yCzX7Ym;${,[+hj*(E*erX:-%-JU=K!}Sp/m:$gZ.D[EW,fQec3Ha1.rwy)TX"); + } + + @Test + public void testGenerateTokenGeneratesDifferentTokensForDifferentCreationDates() throws Exception { + when(timeProviderMock.now()).thenReturn(DateUtil.yesterday()).thenReturn(DateUtil.now()); + + final String token = createToken(); + final String laterToken = createToken(); + + assertThat(token).isNotEqualTo(laterToken); + } + + @Test + public void tokenShouldExpire() { + Date beforeSomeTime = new Date(DateUtil.now().getTime() - 20 * 1000); + + when(timeProviderMock.now()).thenReturn(beforeSomeTime); + + UserDetails userDetails = mock(User.class); + when(userDetails.getUsername()).thenReturn(TEST_USERNAME); + + final String mobileToken = createToken(); + assertThat(tokenHelper.validateToken(mobileToken, userDetails)).isFalse(); + } + + @Test + public void getUsernameFromToken() throws Exception { + when(timeProviderMock.now()).thenReturn(DateUtil.now()); + + final String token = createToken(); + + assertThat(tokenHelper.getUsernameFromToken(token)).isEqualTo(TEST_USERNAME); + } + + @Test + public void getCreatedDateFromToken() { + final Date now = DateUtil.now(); + when(timeProviderMock.now()).thenReturn(now); + + final String token = createToken(); + + assertThat(tokenHelper.getIssuedAtDateFromToken(token)).isInSameMinuteWindowAs(now); + } - @InjectMocks - private TokenHelper tokenHelper; + @Test + public void expiredTokenCannotBeRefreshed() { + when(timeProviderMock.now()).thenReturn(DateUtil.yesterday()); - @Mock - private TimeProvider timeProviderMock; + String token = createToken(); + tokenHelper.refreshToken(token); + } - @InjectMocks - DeviceDummy device; + @Test + public void changedPasswordCannotBeRefreshed() throws Exception { + when(timeProviderMock.now()).thenReturn(DateUtil.now()); - @Before - public void init() { - MockitoAnnotations.initMocks(this); + User user = mock(User.class); + when(user.getLastPasswordResetDate()).thenReturn(new Timestamp(DateUtil.tomorrow().getTime())); + String token = createToken(); + assertThat(tokenHelper.validateToken(token, user)).isFalse(); + } - ReflectionTestUtils.setField(tokenHelper, "EXPIRES_IN", 10); // 10 sec - ReflectionTestUtils.setField(tokenHelper, "MOBILE_EXPIRES_IN", 20); // 20 sec - ReflectionTestUtils.setField(tokenHelper, "SECRET", "mySecret"); - } + @Test + public void canRefreshToken() throws Exception { + when(timeProviderMock.now()).thenReturn(DateUtil.now()).thenReturn(DateUtil.tomorrow()); + String firstToken = createToken(); + String refreshedToken = tokenHelper.refreshToken(firstToken); + Date firstTokenDate = tokenHelper.getIssuedAtDateFromToken(firstToken); + Date refreshedTokenDate = tokenHelper.getIssuedAtDateFromToken(refreshedToken); + assertThat(firstTokenDate).isBefore(refreshedTokenDate); + } - @Test - public void testGenerateTokenGeneratesDifferentTokensForDifferentCreationDates() throws Exception { - when(timeProviderMock.now()) - .thenReturn(DateUtil.yesterday()) - .thenReturn(DateUtil.now()); - - final String token = createToken(device); - final String laterToken = createToken(device); - - assertThat(token).isNotEqualTo(laterToken); - } - - @Test - public void mobileTokenShouldLiveLonger() { - Date beforeSomeTime = new Date(DateUtil.now().getTime() - 15 * 1000); - - UserDetails userDetails = mock(User.class); - when(userDetails.getUsername()).thenReturn(TEST_USERNAME); - - when(timeProviderMock.now()) - .thenReturn(beforeSomeTime); - device.setMobile(true); - final String mobileToken = createToken(device); - assertThat(tokenHelper.validateToken(mobileToken, userDetails)).isTrue(); - } - - @Test - public void mobileTokenShouldExpire() { - Date beforeSomeTime = new Date(DateUtil.now().getTime() - 20 * 1000); - - when(timeProviderMock.now()) - .thenReturn(beforeSomeTime); - - UserDetails userDetails = mock(User.class); - when(userDetails.getUsername()).thenReturn(TEST_USERNAME); - - device.setMobile(true); - final String mobileToken = createToken(device); - assertThat(tokenHelper.validateToken(mobileToken, userDetails)).isFalse(); - } - - @Test - public void getUsernameFromToken() throws Exception { - when(timeProviderMock.now()).thenReturn(DateUtil.now()); - - final String token = createToken(device); - - assertThat(tokenHelper.getUsernameFromToken(token)).isEqualTo(TEST_USERNAME); - } - - @Test - public void getCreatedDateFromToken() { - final Date now = DateUtil.now(); - when(timeProviderMock.now()).thenReturn(now); - - final String token = createToken(device); - - assertThat(tokenHelper.getIssuedAtDateFromToken(token)).isInSameMinuteWindowAs(now); - } - - @Test - public void expiredTokenCannotBeRefreshed() { - when(timeProviderMock.now()) - .thenReturn(DateUtil.yesterday()); - - String token = createToken(device); - tokenHelper.refreshToken(token, device); - } - - @Test - public void getAudienceFromToken() throws Exception { - when(timeProviderMock.now()).thenReturn(DateUtil.now()); - device.setNormal(true); - final String token = createToken(this.device); - - assertThat(tokenHelper.getAudienceFromToken(token)).isEqualTo(tokenHelper.AUDIENCE_WEB); - } - - @Test - public void getAudienceFromMobileToken() throws Exception { - when(timeProviderMock.now()).thenReturn(DateUtil.now()); - device.setMobile(true); - final String token = createToken(this.device); - assertThat(tokenHelper.getAudienceFromToken(token)).isEqualTo(tokenHelper.AUDIENCE_MOBILE); - } - - @Test - public void changedPasswordCannotBeRefreshed() throws Exception { - when(timeProviderMock.now()) - .thenReturn(DateUtil.now()); - - User user = mock(User.class); - when(user.getLastPasswordResetDate()).thenReturn(new Timestamp(DateUtil.tomorrow().getTime())); - String token = createToken(device); - assertThat(tokenHelper.validateToken(token, user)).isFalse(); - } - - @Test - public void canRefreshToken() throws Exception { - when(timeProviderMock.now()) - .thenReturn(DateUtil.now()) - .thenReturn(DateUtil.tomorrow()); - String firstToken = createToken(device); - String refreshedToken = tokenHelper.refreshToken(firstToken, device); - Date firstTokenDate = tokenHelper.getIssuedAtDateFromToken(firstToken); - Date refreshedTokenDate = tokenHelper.getIssuedAtDateFromToken(refreshedToken); - assertThat(firstTokenDate).isBefore(refreshedTokenDate); - } - - private String createToken(Device device) { - return tokenHelper.generateToken(TEST_USERNAME, device); - } + private String createToken() { + return tokenHelper.generateToken(TEST_USERNAME); + } } diff --git a/src/test/java/com/bfwg/security/UserDetailsDummy.java b/src/test/java/com/bfwg/security/UserDetailsDummy.java index 612be45..a4fda0d 100644 --- a/src/test/java/com/bfwg/security/UserDetailsDummy.java +++ b/src/test/java/com/bfwg/security/UserDetailsDummy.java @@ -10,44 +10,44 @@ */ public class UserDetailsDummy implements UserDetails { - private final String username; - - public UserDetailsDummy(String username) { - this.username = username; - } - - @Override - public Collection getAuthorities() { - return null; - } - - @Override - public String getPassword() { - return null; - } - - @Override - public String getUsername() { - return 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; - } + private final String username; + + public UserDetailsDummy(String username) { + this.username = username; + } + + @Override + public Collection getAuthorities() { + return null; + } + + @Override + public String getPassword() { + return null; + } + + @Override + public String getUsername() { + return 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; + } }