diff --git a/users/build.gradle b/users/build.gradle index fff3186..8c9d32d 100644 --- a/users/build.gradle +++ b/users/build.gradle @@ -38,6 +38,7 @@ dependencies { compile('mysql:mysql-connector-java') compile('org.hibernate:hibernate-validator') compile('org.springframework.cloud:spring-cloud-starter-eureka') + compile('io.jsonwebtoken:jjwt:0.7.0') compile project(':common') testCompile('org.springframework.cloud:spring-cloud-starter-eureka-server') diff --git a/users/src/main/java/ba/steleks/UsersConfig.java b/users/src/main/java/ba/steleks/UsersConfig.java index e24f9fe..707e12f 100644 --- a/users/src/main/java/ba/steleks/UsersConfig.java +++ b/users/src/main/java/ba/steleks/UsersConfig.java @@ -11,7 +11,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class UsersConfig { @Bean - public PasswordEncoder providePasswordEncoder(){ + public static PasswordEncoder providePasswordEncoder(){ return new BCryptPasswordEncoder(); } } diff --git a/users/src/main/java/ba/steleks/model/UserRole.java b/users/src/main/java/ba/steleks/model/UserRole.java index 5338eff..e0955d9 100644 --- a/users/src/main/java/ba/steleks/model/UserRole.java +++ b/users/src/main/java/ba/steleks/model/UserRole.java @@ -19,6 +19,13 @@ public class UserRole { private String roleName; + public UserRole() { + } + + public UserRole(String roleName) { + this.roleName = roleName; + } + public long getId() { return id; } @@ -34,4 +41,19 @@ public class UserRole { public void setRoleName(String roleName) { this.roleName = roleName; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + UserRole userRole = (UserRole) o; + + return roleName != null ? roleName.equals(userRole.roleName) : userRole.roleName == null; + } + + @Override + public int hashCode() { + return roleName != null ? roleName.hashCode() : 0; + } } diff --git a/users/src/main/java/ba/steleks/security/AccountCredentials.java b/users/src/main/java/ba/steleks/security/AccountCredentials.java new file mode 100644 index 0000000..491623c --- /dev/null +++ b/users/src/main/java/ba/steleks/security/AccountCredentials.java @@ -0,0 +1,36 @@ +package ba.steleks.security;/** + * Created by ensar on 28/05/17. + */ + +public class AccountCredentials { + + private String username; + + private String password; + + private String role; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } +} \ No newline at end of file diff --git a/users/src/main/java/ba/steleks/security/JWTAuthenticationFilter.java b/users/src/main/java/ba/steleks/security/JWTAuthenticationFilter.java new file mode 100644 index 0000000..19eb439 --- /dev/null +++ b/users/src/main/java/ba/steleks/security/JWTAuthenticationFilter.java @@ -0,0 +1,26 @@ +package ba.steleks.security;/** + * Created by ensar on 28/05/17. + */ + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.GenericFilterBean; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +public class JWTAuthenticationFilter extends GenericFilterBean { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) + throws IOException, ServletException { + Authentication authentication = TokenAuthenticationService.getAuthentication((HttpServletRequest) request); + + SecurityContextHolder.getContext().setAuthentication(authentication); + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/users/src/main/java/ba/steleks/security/JWTLoginFilter.java b/users/src/main/java/ba/steleks/security/JWTLoginFilter.java new file mode 100644 index 0000000..72b46fe --- /dev/null +++ b/users/src/main/java/ba/steleks/security/JWTLoginFilter.java @@ -0,0 +1,42 @@ +package ba.steleks.security;/** + * Created by ensar on 28/05/17. + */ + +import ba.steleks.model.UserRole; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Collections; +import java.util.Set; + +public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter { + + public JWTLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) { + super(defaultFilterProcessesUrl); + setAuthenticationManager(authenticationManager); + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { + AccountCredentials creds = new ObjectMapper().readValue(request.getInputStream(), AccountCredentials.class); + return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(creds.getUsername(), + creds.getPassword(), Collections.emptyList())); + } + + @Override + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { + super.successfulAuthentication(request, response, chain, authResult); + Set userRoles = UserRoleFactory.fromGrantedAuthorities(authResult.getAuthorities()); + + TokenAuthenticationService.addAuthenticationHeader(response, authResult.getName(), userRoles); + } +} diff --git a/users/src/main/java/ba/steleks/security/SecurityConfig.java b/users/src/main/java/ba/steleks/security/SecurityConfig.java index f5e7eef..fdd00b3 100644 --- a/users/src/main/java/ba/steleks/security/SecurityConfig.java +++ b/users/src/main/java/ba/steleks/security/SecurityConfig.java @@ -6,15 +6,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity @@ -22,23 +19,26 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean - public AuthenticationProvider provideAuthenticationProvider() { - DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); - authenticationProvider.setUserDetailsService(new SteleksUsersDetailsService()); - return authenticationProvider; + public UserDetailsService provideUserDetailsService() { + return new SteleksUsersDetailsService(); } @Autowired - private AuthenticationProvider authProvider; + private UserDetailsService userDetailsService; @Override protected void configure( AuthenticationManagerBuilder auth) throws Exception { - auth.authenticationProvider(authProvider); + auth.userDetailsService(userDetailsService); } @Override protected void configure(HttpSecurity http) throws Exception { - http.authorizeRequests().anyRequest().authenticated(); + http.csrf().disable().authorizeRequests() + .anyRequest().authenticated() + .and() + .addFilterBefore(new JWTLoginFilter("/accesstoken", authenticationManager()), + UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } } \ No newline at end of file diff --git a/users/src/main/java/ba/steleks/security/SteleksUsersDetailsService.java b/users/src/main/java/ba/steleks/security/SteleksUsersDetailsService.java index b635d9b..c2629e3 100644 --- a/users/src/main/java/ba/steleks/security/SteleksUsersDetailsService.java +++ b/users/src/main/java/ba/steleks/security/SteleksUsersDetailsService.java @@ -3,19 +3,22 @@ package ba.steleks.security;/** */ import ba.steleks.model.User; +import ba.steleks.model.UserRole; import ba.steleks.repository.UsersJpaRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; +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 java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.logging.Logger; +import java.util.stream.Collectors; public class SteleksUsersDetailsService implements UserDetailsService { - private static final Logger logger = - Logger.getLogger(SteleksUsersDetailsService.class.getName()); @Autowired private UsersJpaRepository usersJpaRepository; @@ -23,42 +26,17 @@ public class SteleksUsersDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = usersJpaRepository.findByUsername(username); - UserDetails userDetails = new UserDetails() { - @Override - public Collection getAuthorities() { - return null; - } - @Override - public String getPassword() { - return null; - } + if(user == null) { + throw new UsernameNotFoundException(username); + } - @Override - public String getUsername() { - return null; - } + List authorities = + UserRoleFactory.toGrantedAuthorities(user.getUserRoles()); - @Override - public boolean isAccountNonExpired() { - return false; - } - @Override - public boolean isAccountNonLocked() { - return false; - } - - @Override - public boolean isCredentialsNonExpired() { - return false; - } - - @Override - public boolean isEnabled() { - return false; - } - }; - return userDetails; + return new org.springframework.security.core.userdetails.User(user.getUsername(), + user.getPasswordHash(), + authorities); } } diff --git a/users/src/main/java/ba/steleks/security/TokenAuthenticationService.java b/users/src/main/java/ba/steleks/security/TokenAuthenticationService.java new file mode 100644 index 0000000..5187e40 --- /dev/null +++ b/users/src/main/java/ba/steleks/security/TokenAuthenticationService.java @@ -0,0 +1,73 @@ +package ba.steleks.security; + +import ba.steleks.model.UserRole; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.sql.Date; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +/** + * Created by ensar on 28/05/17. + */ + +class TokenAuthenticationService { + + static final long EXPIRATION_TIME = 864_000_000; // 10 days + static final String SECRET = "ASteleksSecret"; + static final String TOKEN_PREFIX = "Bearer"; + static final String HEADER_STRING = "Authorization"; + static final String ROLES = "roles"; + + static void addAuthenticationHeader(HttpServletResponse res, String username, Set userRoleSet) { + String JWT = Jwts.builder() + .setSubject(username) + .claim(ROLES, UserRoleFactory.toString(userRoleSet)) + .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) + .signWith(SignatureAlgorithm.HS512, SECRET).compact(); + res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + JWT); + } + + static Authentication getAuthentication(HttpServletRequest request) { + String token = request.getHeader(HEADER_STRING); + + if (token != null) { + // parse the token. + Claims claims = Jwts.parser() + .setSigningKey(SECRET) + .parseClaimsJws(token.replaceFirst(TOKEN_PREFIX, "")) + .getBody(); + + String userName = claims.getSubject(); + Set userRoles = UserRoleFactory.fromString(claims.get(ROLES, String.class)); + + if (userName != null) { + boolean access = false; + + UserRole theUserRole = new UserRole("theUser"); + + if(userRoles.contains(theUserRole)) { + access = true; + } + + if (access) { + return new UsernamePasswordAuthenticationToken(userName, + null, + UserRoleFactory.toGrantedAuthorities(userRoles)); + } + else { + return new UsernamePasswordAuthenticationToken(userName, + null); + } + } + } + return null; + } +} \ No newline at end of file diff --git a/users/src/main/java/ba/steleks/security/UserRoleFactory.java b/users/src/main/java/ba/steleks/security/UserRoleFactory.java new file mode 100644 index 0000000..5e7868d --- /dev/null +++ b/users/src/main/java/ba/steleks/security/UserRoleFactory.java @@ -0,0 +1,48 @@ +package ba.steleks.security;/** + * Created by ensar on 28/05/17. + */ + +import ba.steleks.model.UserRole; +import com.google.common.collect.Lists; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import java.util.*; +import java.util.stream.Collectors; + +public class UserRoleFactory { + + public static String toString(Collection userRoleSet) { + // Transform to list + List userRoleList = Lists.newArrayList(userRoleSet); + // Sort by role name - to make different sets look the same in the end + userRoleList.sort(Comparator.comparing(UserRole::getRoleName)); + // Transform to string + return userRoleList.stream().map(UserRole::getRoleName).collect(Collectors.joining(",")); + } + + public static Set fromString(String userRoleString) { + Set userRoles = new HashSet<>(); + Arrays.stream(userRoleString.split(",")).map(UserRole::new).forEach(userRoles::add); + return userRoles; + } + + public static List toGrantedAuthorities(Collection userRoleSet) { + return userRoleSet + .stream() + // get role name + .map(UserRole::getRoleName) + // create authority + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + } + + public static Set fromGrantedAuthorities(Collection grantedAuthorities) { + return grantedAuthorities + .stream() + .map(GrantedAuthority::getAuthority) + .map(UserRole::new) + .collect(Collectors.toSet()); + } + +}