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: 414 days ago