Provider:
package com.example.ec.security; import com.example.ec.domain.Role; import io.jsonwebtoken.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Component; import java.util.*; import java.util.stream.Collectors; /** * Utility Class for common Java Web Token operations * * Created by Mary Ellen Bowman */ @Component public class JwtProvider{ private final String ROLES_KEY = "roles"; private JwtParser parser; private String secretKey; private long validityInMilliseconds; @Autowired public JwtProvider(@Value("${security.jwt.token.secret-key}") String secretKey, @Value("${security.jwt.token.expiration}")long validityInMilliseconds) { this.secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); this.validityInMilliseconds = validityInMilliseconds; } /** * Create JWT string given username and roles. * * @param username * @param roles * @return jwt string */ public String createToken(String username, List<Role> roles) { //Add the username to the payload Claims claims = Jwts.claims().setSubject(username); //Convert roles to Spring Security SimpleGrantedAuthority objects, //Add to Simple Granted Authority objects to claims claims.put(ROLES_KEY, roles.stream().map(role ->new SimpleGrantedAuthority(role.getAuthority())) .filter(Objects::nonNull) .collect(Collectors.toList())); //Build the Token Date now = new Date(); return Jwts.builder() .setClaims(claims) .setIssuedAt(now) .setExpiration(new Date(now.getTime() + validityInMilliseconds)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); } /** * Validate the JWT String * * @param token JWT string * @return true if valid, false otherwise */ public boolean isValidToken(String token) { try { Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); return true; } catch (JwtException | IllegalArgumentException e) { return false; } } /** * Get the username from the token string * * @param token jwt * @return username */ public String getUsername(String token) { return Jwts.parser().setSigningKey(secretKey) .parseClaimsJws(token).getBody().getSubject(); } /** * Get the roles from the token string * * @param token jwt * @return username */ public List<GrantedAuthority> getRoles(String token) { List<Map<String, String>> roleClaims = Jwts.parser().setSigningKey(secretKey) .parseClaimsJws(token).getBody().get(ROLES_KEY, List.class); return roleClaims.stream().map(roleClaim -> new SimpleGrantedAuthority(roleClaim.get("authority"))) .collect(Collectors.toList()); } }
Filter:
package com.example.ec.security; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; 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; import java.util.Optional; /** * Filter for Java Web Token Authentication and Authorization * * Created by Mary Ellen Bowman */ public class JwtTokenFilter extends GenericFilterBean { private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenFilter.class); private static final String BEARER = "Bearer"; private ExploreCaliUserDetailsService userDetailsService; public JwtTokenFilter(ExploreCaliUserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } /** * Determine if there is a JWT as part of the HTTP Request Header. * If it is valid then set the current context With the Authentication(user and roles) found in the token * * @param req Servlet Request * @param res Servlet Response * @param filterChain Filter Chain * @throws IOException * @throws ServletException */ @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException { LOGGER.info("Process request to check for a JSON Web Token "); //Check for Authorization:Bearer JWT String headerValue = ((HttpServletRequest)req).getHeader("Authorization"); getBearerToken(headerValue).ifPresent(token-> { //Pull the Username and Roles from the JWT to construct the user details userDetailsService.loadUserByJwtToken(token).ifPresent(userDetails -> { //Add the user details (Permissions) to the Context for just this API invocation SecurityContextHolder.getContext().setAuthentication( new PreAuthenticatedAuthenticationToken(userDetails, "", userDetails.getAuthorities())); }); }); //move on to the next filter in the chains filterChain.doFilter(req, res); } /** * if present, extract the jwt token from the "Bearer <jwt>" header value. * * @param headerVal * @return jwt if present, empty otherwise */ private Optional<String> getBearerToken(String headerVal) { if (headerVal != null && headerVal.startsWith(BEARER)) { return Optional.of(headerVal.replace(BEARER, "").trim()); } return Optional.empty(); } }
Security Config:
package com.example.ec.security; import com.example.ec.repo.RoleRepository; 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.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired RoleRepository roleRepository; @Override protected void configure(HttpSecurity http) throws Exception { // Entry points http.authorizeRequests() .antMatchers("/packages/**").permitAll() .antMatchers("/tours/**").permitAll() .antMatchers("/ratings/**").permitAll() .antMatchers("/users/signin").permitAll() // Disallow everything else.. .anyRequest().authenticated(); // Disable CSRF (cross site request forgery) http.csrf().disable(); // No session will be created or used by spring security http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.addFilterBefore(new JwtTokenFilter(userDetailsService), UsernamePasswordAuthenticationFilter.class); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); } }
Usage to the endpoints:
package com.example.ec.web; import com.example.ec.domain.User; import com.example.ec.service.UserService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.HttpServerErrorException; import javax.validation.Valid; import java.util.List; @RestController @RequestMapping("/users") public class UserController { private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class); @Autowired private UserService userService; @PostMapping("/signin") public String login(@RequestBody @Valid LoginDto loginDto) { return userService.signin(loginDto.getUsername(), loginDto.getPassword()).orElseThrow(()-> new HttpServerErrorException(HttpStatus.FORBIDDEN, "Login Failed")); } @PostMapping("/signup") @PreAuthorize("hasRole('ROLE_ADMIN')") @ResponseStatus(HttpStatus.CREATED) public User signup(@RequestBody @Valid LoginDto loginDto){ return userService.signup(loginDto.getUsername(), loginDto.getPassword(), loginDto.getFirstName(), loginDto.getLastName()).orElseThrow(() -> new HttpServerErrorException(HttpStatus.BAD_REQUEST,"User already exists")); } @GetMapping @PreAuthorize("hasRole('ROLE_ADMIN')") public List<User> getAllUsers() { return userService.getAll(); } }