Skip to content
Permalink
Browse files

Backend API secured - Login functionality added. User authentication …

…and authorization handled with JWT. Tests updated.
  • Loading branch information
little-pinecone committed Jan 7, 2019
1 parent 40e2d0e commit 6b4c29c6c11ca0cf435203475cde4f6d1809c6bf
@@ -44,6 +44,17 @@
<artifactId>spring-security-test</artifactId>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-data</artifactId>
</dependency>

<dependency>
<groupId>in.keepgrowing</groupId>
<artifactId>frontend</artifactId>
@@ -1,21 +1,40 @@
package in.keepgrowing.jwtspringbootangularscaffolding.config;

import in.keepgrowing.jwtspringbootangularscaffolding.security.AuthenticationFilter;
import in.keepgrowing.jwtspringbootangularscaffolding.security.AuthorizationFilter;
import in.keepgrowing.jwtspringbootangularscaffolding.security.CustomUserDetailsService;
import in.keepgrowing.jwtspringbootangularscaffolding.security.TokenProperties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletResponse;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Value("${cors.enabled:false}")
private boolean corsEnabled;

private final TokenProperties tokenProperties;
private final BCryptPasswordEncoder passwordEncoder;
private final CustomUserDetailsService userDetailsService;

public SecurityConfig(TokenProperties tokenProperties,
BCryptPasswordEncoder passwordEncoder,
CustomUserDetailsService userDetailsService) {
this.tokenProperties = tokenProperties;
this.passwordEncoder = passwordEncoder;
this.userDetailsService = userDetailsService;
}

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
applyCors(httpSecurity)
@@ -24,8 +43,14 @@ protected void configure(HttpSecurity httpSecurity) throws Exception {
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedResponse())
.and()
.logout()
.and()
.addFilter(new AuthenticationFilter(authenticationManagerBean(), tokenProperties))
.addFilterAfter(new AuthorizationFilter(tokenProperties), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(HttpMethod.POST, tokenProperties.getLoginPath()).permitAll()
.antMatchers(HttpMethod.POST, "/api/users").permitAll()
.antMatchers("/api/users/**").hasRole("ADMIN")
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll();
}
@@ -41,4 +66,9 @@ private HttpSecurity applyCors(HttpSecurity httpSecurity) throws Exception {
private AuthenticationEntryPoint unauthorizedResponse() {
return (req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
}
@@ -0,0 +1,14 @@
package in.keepgrowing.jwtspringbootangularscaffolding.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension;

@Configuration
public class SecurityEvaluationContextConfig {

@Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
}
@@ -0,0 +1,86 @@
package in.keepgrowing.jwtspringbootangularscaffolding.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import in.keepgrowing.jwtspringbootangularscaffolding.user.model.UserCredentials;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
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.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final ObjectMapper objectMapper;
private final AuthenticationManager authenticationManager;
private final TokenProperties tokenProperties;

public AuthenticationFilter(AuthenticationManager authenticationManager, TokenProperties tokenProperties) {
this.authenticationManager = authenticationManager;
this.tokenProperties = tokenProperties;
objectMapper = new ObjectMapper();
setLoginPath(tokenProperties);
}

private void setLoginPath(TokenProperties tokenProperties) {
setRequiresAuthenticationRequestMatcher(
new AntPathRequestMatcher(tokenProperties.getLoginPath(), "POST"));
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
try {
UserCredentials credentials = getCredentials(request);
UsernamePasswordAuthenticationToken token = createAuthenticationToken(credentials);
return authenticationManager.authenticate(token);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private UserCredentials getCredentials(HttpServletRequest request) throws IOException {
return objectMapper.readValue(request.getInputStream(), UserCredentials.class);
}

private UsernamePasswordAuthenticationToken createAuthenticationToken(UserCredentials credentials) {
return new UsernamePasswordAuthenticationToken(
credentials.getUsername(),
credentials.getPassword(),
Collections.emptyList()
);
}

@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication auth) {
response.addHeader(tokenProperties.getHeader(), tokenProperties.getPrefix() + createToken(auth));
}

private String createToken(Authentication auth) {
long now = System.currentTimeMillis();
List<String> authorities = auth.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
return Jwts.builder()
.setSubject(auth.getName())
.claim("authorities", authorities)
.setIssuedAt(new Date(now))
.setExpiration(new Date(now + tokenProperties.getExpiration() * 1000))
.signWith(SignatureAlgorithm.HS512, tokenProperties.getSecret().getBytes())
.compact();
}
}
@@ -0,0 +1,85 @@
package in.keepgrowing.jwtspringbootangularscaffolding.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
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 java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class AuthorizationFilter extends OncePerRequestFilter {

private final TokenProperties tokenProperties;

public AuthorizationFilter(TokenProperties tokenProperties) {
this.tokenProperties = tokenProperties;
}

@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain filterChain
) throws ServletException, IOException {
String header = httpServletRequest.getHeader(tokenProperties.getHeader());

if (headerIsValid(header)) {
try {
Claims claims = getClaims(getToken(header));
Optional.ofNullable(claims.getSubject())
.ifPresent(username -> setUserContext(claims, username));
} catch (Exception e) {
SecurityContextHolder.clearContext();
}
}

goToNextFilter(httpServletRequest, httpServletResponse, filterChain);
}

private boolean headerIsValid(String header) {
return header != null && header.startsWith(tokenProperties.getPrefix());
}

private String getToken(String header) {
return header.replace(tokenProperties.getPrefix(), "");
}

private Claims getClaims(String token) {
return Jwts.parser()
.setSigningKey(tokenProperties.getSecret().getBytes())
.parseClaimsJws(token)
.getBody();
}

private void setUserContext(Claims claims, String username) {
User userDetails = new User(username, "", Collections.emptyList());
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
userDetails,
null,
getGrantedAuthorities(claims)
);
SecurityContextHolder.getContext().setAuthentication(auth);
}

@SuppressWarnings("unchecked")
private List<SimpleGrantedAuthority> getGrantedAuthorities(Claims claims) {
return ((List<String>) claims.get("authorities")).stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}

private void goToNextFilter(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
@@ -0,0 +1,41 @@
package in.keepgrowing.jwtspringbootangularscaffolding.security;

import in.keepgrowing.jwtspringbootangularscaffolding.user.UserService;
import in.keepgrowing.jwtspringbootangularscaffolding.user.model.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class CustomUserDetailsService implements UserDetailsService {

private UserService userService;

public CustomUserDetailsService(UserService userService) {
this.userService = userService;
}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userService.findByUsername(username)
.map(this::getUserDetails)
.orElseThrow(() -> new UsernameNotFoundException(String.format("Username: %s not found", username)));
}

private org.springframework.security.core.userdetails.User getUserDetails(User u) {
return new org.springframework.security.core.userdetails.User(
u.getUserCredentials().getUsername(),
u.getUserCredentials().getPassword(),
getGrantedAuthorities(u));
}

private List<GrantedAuthority> getGrantedAuthorities(User u) {
return AuthorityUtils
.commaSeparatedStringToAuthorityList(u.getUserCredentials().getRole());
}
}
@@ -0,0 +1,39 @@
package in.keepgrowing.jwtspringbootangularscaffolding.security;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Service;

@Service
@ConfigurationProperties(prefix = "security.jwt")
public class TokenProperties {

private String loginPath = "/api/login";

private String header = "Authorization";

private String prefix = "Bearer ";

private int expiration = 86400;

private String secret = "JwtSecretKey";

public String getLoginPath() {
return loginPath;
}

public String getHeader() {
return header;
}

public String getPrefix() {
return prefix;
}

public int getExpiration() {
return expiration;
}

public String getSecret() {
return secret;
}
}
@@ -5,6 +5,8 @@
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class UserService {

@@ -27,4 +29,9 @@ private void setPasswordAndRole(User user) {
.setPassword(encoder.encode(user.getUserCredentials().getPassword()));
user.getUserCredentials().setRole(DEFAULT_ROLE);
}

public Optional<User> findByUsername(String username) {
return userRepository.findByUserCredentialsUsername(username);
}

}
@@ -2,5 +2,9 @@

import org.springframework.data.repository.CrudRepository;

import java.util.Optional;

public interface UserRepository extends CrudRepository<User, Long> {

Optional<User> findByUserCredentialsUsername(String username);
}

0 comments on commit 6b4c29c

Please sign in to comment.
You can’t perform that action at this time.