minte9
LearnRemember



App

Web Spring application with token auth, jjwt dependency.
 
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
    <dependency>
        <groupId>jakarta.xml.bind</groupId>
        <artifactId>jakarta.xml.bind-api</artifactId>
    </dependency>
<dependencies>
 
package com.minte9.jwt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

Controller

REST controller that implements the auth with username/password.
 
/**
 * REST Controller
 * 
 * In real-life applciations will have three servers: our API, 
 * authentication sever, authorization server. As demo, we can implement 
 * all three functionalities in a single application.
 * 
 * With Jwts builder() we create the JWT token with: user's name, authorities, 
 * issued and expiration dates, and signature.
 * 
 * In Spring Security, GrantedAuthority is an interface that represents 
 * a granted authority or permission (ROLE_USER, ROLE_ADMIN, etc) 
 */
package com.minte9.jwt;

import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

@RestController
public class AppRestController {

    @ Autowired
    private Environment env;

    @RequestMapping("/hello")
    public String hello() {
        return "Hello World! \n";
    }

    @PostMapping("/token")
    public User genToken(
            @RequestParam("user") String username, 
            @RequestParam("password") String pwd) {
        
        User user = new User();    
        user.setUser(username);
        String envUser = env.getProperty("spring.security.user.name");
        String envPass = env.getProperty("spring.security.user.password");

        if(username.equals(envUser) && pwd.equals(envPass)) { // Look Here
            String token = getJWTToken(username);
            user.setToken(token);
        }

        return user;
    }

    private String getJWTToken(String username) {
        String secretKey = "myTokenSecretKey";
        List<GrantedAuthority> grantedAuthorities = AuthorityUtils
            .commaSeparatedStringToAuthorityList("ROLE_USER");
        
        String token = Jwts
            .builder()
            .setId("minte9JWT")
            .setSubject(username)
            .claim("authorities", grantedAuthorities.stream()
                    .map(GrantedAuthority::getAuthority)
                    .collect(Collectors.toList()))
            .setIssuedAt(new Date(System.currentTimeMillis()))
            .setExpiration(new Date(System.currentTimeMillis() + 600000))
            .signWith(SignatureAlgorithm.HS512, secretKey.getBytes())
            .compact();

        return "Bearer " + token;
    }
}

Config

Calls to /user are allowed, but all other calls require authentication.
 
/**
 * JWT Authorization Security Configuration
 * 
 * Extend WebSecurityConfigurerAdapter class to provide 
 * custom security configuration.
 * 
 * Add JWT authorization filter after UsernamePasswordAuthenticationFilter.
 * Allow unauthenticated access to /token POST requests.
 * Require authentication for all other requests.
 */

package com.minte9.jwt;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.*;
import org.springframework.security.web.authentication.*;

@EnableWebSecurity
@Configuration
class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .addFilterAfter(
                new JWTAuthorizationFilter(), 
                UsernamePasswordAuthenticationFilter.class // Look Here
            )
            .authorizeRequests()
            .antMatchers(HttpMethod.POST, "/token").permitAll()
            .anyRequest().authenticated();
    }
}

Authorization

Implement the autorization process with JWTAuthorizationFilter class.
 
/**
 * JWT Authorization
 * 
 * JSON Web Token is an open source standard for creating access tokens 
 * that allow us to secure communications between client and server.
 * 
 * Token authentication is more secure than session authentication 
 * because a token cannot be tampered with.
 */

package com.minte9.jwt;

import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.*;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;

public class JWTAuthorizationFilter extends OncePerRequestFilter {

    private final String HEADER = "Authorization";
    private final String PREFIX = "Bearer ";
    private final String SECRET = "myTokenSecretKey";

    /**
     * Intercept HTTP requests and performs JWT token authentication. 
     * 
     * If the token is valid, it sets up Spring authentication by setting 
     * the security context with the authenticated user's information. 
     * 
     * If the token is invalid, it clears the authentication context. 
     * If an exception occurs during validation, it returns a 403
     */
    @Override
    protected void doFilterInternal(
            HttpServletRequest request, 
            HttpServletResponse response, 
            FilterChain chain) 
                throws ServletException, IOException {
        try {
            if (checkJWTToken(request, response)) {
                Claims claims = validateToken(request);
                if (claims.get("authorities") != null) {
                    setUpSpringAuthentication(claims);
                } else {
                    SecurityContextHolder.clearContext();
                }
            }else {
                SecurityContextHolder.clearContext();
            }
            chain.doFilter(request, response);
        } catch (ExpiredJwtException | 
                     UnsupportedJwtException | MalformedJwtException e) {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response = (HttpServletResponse) response;
            response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
            return;
        }
    }    

    /**
     * Extract the JWT token from the HTTP request header and parses it 
     * to retrieve the token's claims. 
     * 
     * It returns the token's claims after validating the token's signature
     * using the application's secret key.
     */
    private Claims validateToken(HttpServletRequest request) {
        String jwtToken = request.getHeader(HEADER).replace(PREFIX, "");
        JwtParser jwtParser = Jwts.parser();

        jwtParser.setSigningKey(SECRET.getBytes());
        return jwtParser.parseClaimsJws(jwtToken).getBody();
    }

    /**
     * Set up Spring authentication instance with the authenticated 
     * user's details and setting it in the security context.
     */
    private void setUpSpringAuthentication(Claims claims) {
        @SuppressWarnings("unchecked")
        List<String> authorities = (List<String>) claims.get("authorities");

        UsernamePasswordAuthenticationToken auth = 
            new UsernamePasswordAuthenticationToken(
                claims.getSubject(), null, authorities.stream()
                    .map(SimpleGrantedAuthority::new)
                    .collect(Collectors.toList()
            )
        );
        SecurityContextHolder.getContext().setAuthentication(auth);

    }

    /**
     * Checks if the Authorization header of the HTTP request contains 
     * a JWT token with the correct Bearer prefix.
     */
    private boolean checkJWTToken(
            HttpServletRequest request, 
            HttpServletResponse res) {
        String authenticationHeader = request.getHeader(HEADER);
        if ( authenticationHeader == null ||
            !authenticationHeader.startsWith(PREFIX))
            return false;
        return true;
    }

}

Token

The client uses token to access the protected resources.
 
curl http://localhost:8080/hello
    # Access denied

URL='http://localhost:8080/token?user=myuser&password=mypass'
RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" $URL)
TOKEN=$(echo $RESPONSE | jq -r '.token')
echo $TOKEN
    # Bearer eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiJzb2Z0dG...

curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/hello
    # Hello World



  Last update: 211 days ago