Spring Boot Security + JWT ”Hello World” Example

In this tutorial, we will be developing a Spring Boot application that makes use of JWT authentication for securing an exposed REST API. In this example, we will be making use of hard-coded user values for user authentication. In the next tutorial, we will be implementing聽Spring Boot + JWT + MYSQL JPA for storing and fetching user credentials.聽Any user will be able to consume this API only if it has a valid JSON Web Token (JWT). In a聽previous tutorial, we learned what is JWT and when and how to use it.

This tutorial is explained in the following video:

For better understanding, we will be developing the project in stages:

Develop a Spring Boot application that exposes a simple REST GET API with mapping /hello.

Configure Spring Security for JWT. Expose REST POST API with mapping/authenticate using which User will get a valid JSON Web Token. And then, allow the user access to the API /hello聽only if it has a valid token聽Spring Boot JWT Workflow

Develop a Spring Boot Application That Exposes a GET REST API

The Maven project will look as follows:

Spring Boot REST

The pom.xml is as follows:

4.0.0com.javainusespring-boot-jwt0.0.1-SNAPSHOTorg.springframework.bootspring-boot-starter-parent2.1.1.RELEASEUTF-8UTF-81.8org.springframework.bootspring-boot-starter-web

Create a聽Controller聽class for exposing a GET REST API:

package com.javainuse.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloWorldController {

@RequestMapping({ "/hello" })
public String firstPage() {
return "Hello World";
}

}

Create the bootstrap class with the聽SpringBoot聽annotation:

package com.javainuse;

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

@SpringBootApplication
public class SpringBootHelloWorldApplication {

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

Compile and then run the SpringBootHelloWorldApplication.java as a Java application.
Go to聽localhost:8080/hello
Spring Boot REST output

Spring Security and JWT Configuration

We will be configuring Spring Security and JWT to perform two operations:

Generating JWT:聽Expose a POST API with mapping聽/authenticate. On passing the correct username and password, it will generate a JSON Web Token (JWT).

Validating JWT:聽If a user tries to access the GET API with mapping聽/hello, it will allow access only if a request has a valid JSON Web Token (JWT).

The Maven project will look as follows:

Spring Boot JWT REST

The sequence flow for these operations will look as follows:

Generating JWT

Spring Boot JWT Generate Token

Spring Boot Security Authentication Manager

Validating JWT

Spring Boot JWT Validate Token

Add the Spring Security and JWT dependencies:

4.0.0com.javainusespring-boot-jwt0.0.1-SNAPSHOTorg.springframework.bootspring-boot-starter-parent2.1.1.RELEASEUTF-8UTF-81.8org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-securityio.jsonwebtokenjjwt0.9.1

Define the application.properties. As see in聽previous JWT tutorial, we specify the secret key, which we will be using for the hashing algorithm.聽The secret key is combined with the header and the payload to create a unique hash. We are only able to verify this hash if you have the secret key.

jwt.secret=javainuse

JwtTokenUtil

The聽JwtTokenUtil聽is responsible for performing JWT operations like creation and validation. It makes use of the io.jsonwebtoken.Jwts for achieving this.

package com.javainuse.config;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

@Component
public class JwtTokenUtil implements Serializable {

private static final long serialVersionUID = -2550185165626007488L;

public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;

@Value("${jwt.secret}")
private String secret;

//retrieve username from jwt token
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}

//retrieve expiration date from jwt token
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}

public  T getClaimFromToken(String token, Function claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
    //for retrieveing any information from token we will need the secret key
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}

//check if the token has expired
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}

//generate token for user
public String generateToken(UserDetails userDetails) {
Map claims = new HashMap();
return doGenerateToken(claims, userDetails.getUsername());
}

//while creating the token -
//1. Define  claims of the token, like Issuer, Expiration, Subject, and the ID
//2. Sign the JWT using the HS512 algorithm and secret key.
//3. According to JWS Compact Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1)
//   compaction of the JWT to a URL-safe string 
private String doGenerateToken(Map claims, String subject) {

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

//validate token
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}

JwtAuthenticationController

Expose a POST API /authenticate using the聽JwtAuthenticationController. The POST API gets the username and password in the body. Using the Spring Authentication Manager, we authenticate the username and password. If the credentials are valid, a JWT token is created using the聽JWTTokenUtil聽and is provided to the client.

package com.javainuse.controller;

import java.util.Objects;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.javainuse.service.JwtUserDetailsService;

import com.javainuse.config.JwtTokenUtil;
import com.javainuse.model.JwtRequest;
import com.javainuse.model.JwtResponse;

@RestController
@CrossOrigin
public class JwtAuthenticationController {

@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private JwtTokenUtil jwtTokenUtil;

@Autowired
private JwtUserDetailsService userDetailsService;

@RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity> createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception {

authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());

final UserDetails userDetails = userDetailsService
.loadUserByUsername(authenticationRequest.getUsername());

final String token = jwtTokenUtil.generateToken(userDetails);

return ResponseEntity.ok(new JwtResponse(token));
}

private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
}

JwtRequest

This class is required for storing the username and password we received from the client.

package com.javainuse.model;

import java.io.Serializable;

public class JwtRequest implements Serializable {

private static final long serialVersionUID = 5926468583005150707L;

private String username;
private String password;

//need default constructor for JSON Parsing
public JwtRequest()
{

}

public JwtRequest(String username, String password) {
this.setUsername(username);
this.setPassword(password);
}

public String getUsername() {
return this.username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return this.password;
}

public void setPassword(String password) {
this.password = password;
}
}

JwtResponse

This class is required for creating a response containing the JWT to be returned to the user.

package com.javainuse.model;

import java.io.Serializable;

public class JwtResponse implements Serializable {

private static final long serialVersionUID = -8091879091924046844L;
private final String jwttoken;

public JwtResponse(String jwttoken) {
this.jwttoken = jwttoken;
}

public String getToken() {
return this.jwttoken;
}
}

JwtRequestFilter

The聽JwtRequestFilter聽extends the Spring Web Filter聽OncePerRequestFilter聽class. For any incoming request, this聽Filter聽class gets executed. It checks if the request has a valid JWT token. If it has a valid JWT Token, then it sets the authentication in context to specify that the current user is authenticated.

package com.javainuse.config;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import com.javainuse.service.JwtUserDetailsService;

import io.jsonwebtoken.ExpiredJwtException;

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

@Autowired
private JwtUserDetailsService jwtUserDetailsService;

@Autowired
private JwtTokenUtil jwtTokenUtil;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {

final String requestTokenHeader = request.getHeader("Authorization");

String username = null;
String jwtToken = null;
// JWT Token is in the form "Bearer token". Remove Bearer word and get
// only the Token
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
logger.warn("JWT Token does not begin with Bearer String");
}

// Once we get the token validate it.
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);

// if token is valid configure Spring Security to manually set
// authentication
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {

UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 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);
}
}
chain.doFilter(request, response);
}

}

JwtAuthenticationEntryPoint

This class will extend Spring’s聽AuthenticationEntryPoint聽class and override its method to commence. It rejects every unauthenticated request and sends error code 401.

package com.javainuse.config;

import java.io.IOException;
import java.io.Serializable;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {

private static final long serialVersionUID = -7858869558953243875L;

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

response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}

聽聽聽聽

WebSecurityConfig

This class extends the聽WebSecurityConfigurerAdapter.聽This is a convenience class that allows customization to both聽WebSecurity聽and聽HttpSecurity.

package com.javainuse.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.method.configuration.EnableGlobalMethodSecurity;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

@Autowired
private UserDetailsService jwtUserDetailsService;

@Autowired
private JwtRequestFilter jwtRequestFilter;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}

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

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

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// We don't need CSRF for this example
httpSecurity.csrf().disable()
// dont authenticate this particular request
.authorizeRequests().antMatchers("/authenticate").permitAll().
// all other requests need to be authenticated
anyRequest().authenticated().and().
// make sure we use stateless session; session won't be used to
// store user's state.
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);

// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}

Then, start the Spring Boot application.

Generate a JSON Web Token

Create a POST request with URL localhost:8080/authenticate. The body should have a valid username and password. In our case, the username is javainuse and the password is password.聽

Spring Boot JWT Tutorial

Validate the JSON Web Token

Try accessing the URL localhost:8080/hello using the above-generated token in the header as follows:Spring Boot JSON Web Token

And there you have it! We hope you enjoyed this demonstration on how to implement Spring Boot security via a JSON Web Token (JWT).

文章来源于互联网:Spring Boot Security + JWT ”Hello World” Example

发布者:小站,转转请注明出处:http://blog.gzcity.top/4246.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022年5月3日 18:09
下一篇 2022年5月3日 18:09

相关推荐

  • 针对越南上市公司的新SPECTRALVIPER后门

    越南上市公司已成为一项正在进行的活动的一部分,该活动部署了一种名为SPECTRALVIPER的新型后门。 “SPECTRALVIPER 是一个严重混淆的、以前未公开的 x64 后门,它带来了 PE 加载和注入、文件上传和下载、文件和目录操作以及令牌模拟功能,”Elastic 安全实验室在周五的一份报告中表示。 这些攻击归因于它跟踪的名为REF2754的行为者…

    2023年6月11日
    16.8K28060
  • How to Improve Your WordPress’ Website Security

    WordPress security has been an important topic ever since the content management system was released back in 2003. As with any highly popular piece of software with a long and rich…

    安全 2022年5月3日
    64540
  • A Simple Blockchain in Java

    I鈥檓 sure we all have heard about cryptocurrency and blockchain and how interrelated they are, which is true too, but they are actually very different and can exist independently. C…

    安全 2022年5月3日
    36440
  • log4j 0day漏洞情况分析及说明

    一、背景简介 2022年7月30日起,各大威胁情报社区及安全圈内开始盛传log4j存在0day漏洞,由于log4j在去年12月爆出严重的jndi注入漏洞,可通过在特定点插入恶意的jndi payload达到执行任意代码进而控制主机的目的。 log4j2(一般简称log4j)是Apache基金会开发维护的开源java日志组件,在以Java开发的系统中大量被直接…

    2022年8月3日
    22000
  • How to Write an Effective Penetration Test Report

    What Is an Effective Penetration Testing Report? Following the recent trend of cyberattacks against IT infrastructure, service organizations have a steady rise in demand to conduct…

    2022年5月3日
    35130

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

评论列表(3条)