Skip to content

Commit

Permalink
🍻 complete jwt refresh module
Browse files Browse the repository at this point in the history
  • Loading branch information
sanshengshui committed Sep 29, 2020
1 parent a3bd836 commit d7f4813
Show file tree
Hide file tree
Showing 10 changed files with 474 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package iot.technology.jwt.refresh.config;

import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
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;

/**
* @author james mu
* @date 2020/9/29 15:58
*/
@Component
public class CustomJwtAuthenticationFilter extends OncePerRequestFilter {

@Autowired
private JwtUtil jwtTokenUtil;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
try{
// JWT Token is in the form "Bearer token". Remove Bearer word and
// get only the Token
String jwtToken = extractJwtFromRequest(request);

if (StringUtils.hasText(jwtToken) && jwtTokenUtil.validateToken(jwtToken)) {
UserDetails userDetails = new User(jwtTokenUtil.getUsernameFromToken(jwtToken), "",
jwtTokenUtil.getRolesFromToken(jwtToken));

UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
// 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);
} else {
System.out.println("Cannot set the Security Context");
}
}catch(ExpiredJwtException ex)
{
String isRefreshToken = request.getHeader("isRefreshToken");
String requestURL = request.getRequestURL().toString();
// allow for Refresh Token creation if following conditions are true.
if (isRefreshToken != null && isRefreshToken.equals("true") && requestURL.contains("refreshtoken")) {
allowForRefreshToken(ex, request);
} else
request.setAttribute("exception", ex);
}
catch(BadCredentialsException ex)
{
request.setAttribute("exception", ex);
}
chain.doFilter(request, response);
}

private void allowForRefreshToken(ExpiredJwtException ex, HttpServletRequest request) {

// create a UsernamePasswordAuthenticationToken with null values.
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
null, null, null);
// 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);
// Set the claims so that in controller we will be using it to create
// new JWT
request.setAttribute("claims", ex.getClaims());

}

private String extractJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package iot.technology.jwt.refresh.config;

import iot.technology.jwt.refresh.dao.DaoUser;
import iot.technology.jwt.refresh.dao.UserDTO;
import iot.technology.jwt.refresh.model.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
* @author james mu
* @date 2020/9/29 15:41
*/
@Service
public class CustomUserDetailsService implements UserDetailsService {

@Autowired
private UserDao userDao;

@Autowired
private PasswordEncoder bcryptEncoder;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<SimpleGrantedAuthority> roles = null;
DaoUser user = userDao.findByUsername(username);
if (Objects.nonNull(user)) {
roles = Arrays.asList(new SimpleGrantedAuthority(user.getRole()));
return new User(user.getUsername(), user.getPassword(), roles);
}
throw new UsernameNotFoundException("User not found with the name" + username);
}

public DaoUser save(UserDTO user) {
DaoUser newUser = new DaoUser();
newUser.setUsername(user.getUsername());
newUser.setPassword(bcryptEncoder.encode(user.getPassword()));
newUser.setRole(user.getRole());
return userDao.save(newUser);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package iot.technology.jwt.refresh.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;

/**
* @author james mu
* @date 2020/9/29 16:02
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {

response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);

Exception exception = (Exception) request.getAttribute("exception");

String message;

if (exception != null) {

byte[] body = new ObjectMapper().writeValueAsBytes(Collections.singletonMap("cause", exception.toString()));

response.getOutputStream().write(body);

} else {

if (authException.getCause() != null) {
message = authException.getCause().toString() + " " + authException.getMessage();
} else {
message = authException.getMessage();
}

byte[] body = new ObjectMapper().writeValueAsBytes(Collections.singletonMap("error", message));

response.getOutputStream().write(body);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package iot.technology.jwt.refresh.config;

import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import java.util.*;

/**
* @author james mu
* @date 2020/9/29 15:59
*/
@Service
public class JwtUtil {

private String secret;
private int jwtExpirationInMs;
private int refreshExpirationDateInMs;

@Value("${jwt.secret}")
public void setSecret(String secret) {
this.secret = secret;
}

@Value("${jwt.expirationDateInMs}")
public void setJwtExpirationInMs(int jwtExpirationInMs) {
this.jwtExpirationInMs = jwtExpirationInMs;
}

@Value("${jwt.refreshExpirationDateInMs}")
public void setRefreshExpirationDateInMs(int refreshExpirationDateInMs) {
this.refreshExpirationDateInMs = refreshExpirationDateInMs;
}

public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();

Collection<? extends GrantedAuthority> roles = userDetails.getAuthorities();

if (roles.contains(new SimpleGrantedAuthority("ROLE_ADMIN"))) {
claims.put("isAdmin", true);
}
if (roles.contains(new SimpleGrantedAuthority("ROLE_USER"))) {
claims.put("isUser", true);
}

return doGenerateToken(claims, userDetails.getUsername());
}

private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + jwtExpirationInMs))
.signWith(SignatureAlgorithm.HS512, secret).compact();

}

public String doGenerateRefreshToken(Map<String, Object> claims, String subject) {

return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + refreshExpirationDateInMs))
.signWith(SignatureAlgorithm.HS512, secret).compact();

}

public boolean validateToken(String authToken) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(authToken);
return true;
} catch (SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException ex) {
throw new BadCredentialsException("INVALID_CREDENTIALS", ex);
} catch (ExpiredJwtException ex) {
throw ex;
}
}

public String getUsernameFromToken(String token) {
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
return claims.getSubject();

}

public List<SimpleGrantedAuthority> getRolesFromToken(String token) {
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();

List<SimpleGrantedAuthority> roles = null;

Boolean isAdmin = claims.get("isAdmin", Boolean.class);
Boolean isUser = claims.get("isUser", Boolean.class);

if (isAdmin != null && isAdmin) {
roles = Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"));
}

if (isUser != null && isAdmin) {
roles = Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
}
return roles;

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package iot.technology.jwt.refresh.config;

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.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;

/**
* @author james mu
* @date 2020/9/29 15:40
*/
@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired
private CustomUserDetailsService userDetailsService;

@Autowired
private CustomJwtAuthenticationFilter customJwtAuthenticationFilter;

@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;



@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}


@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}

@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/helloadmin").hasRole("ADMIN")
.antMatchers("/hellouser").hasAnyRole("USER","ADMIN")
.antMatchers("/authenticate", "/register").permitAll().anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).
and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).
and().addFilterBefore(customJwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
Loading

0 comments on commit d7f4813

Please sign in to comment.