JWT - Securing REST API‘s with JWT Token using Spring Security 6 & Spring Boot 3

这篇文章详细介绍了JSONWebToken(JWT)的概念、用途,特别是用于授权和信息交换。它接着阐述了JWT的结构和工作原理。然后,文章通过一系列步骤展示了如何在SpringSecurity框架中集成JWT,包括添加依赖、创建JWT入口点、配置属性、生成和验证JWTtoken、创建认证过滤器以及登录/签入API返回JWTtoken的实现。
摘要由CSDN通过智能技术生成

What is JWT

  • JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.

  • JWT, or JSON Web Tokens (RFC 7519), is a standard that is mostly used for securing REST APIs.

  • JWT is best way to communicate securely between client and server

  • JWT follows stateless authentication mechanism

When should you use JSON Web Tokens?

  • Authorization

  • Information Exchange

[Authorization] This is the most common senario for using JWT. Once the user is logged in, each subsequent request will include the JWT, allowing the user to access resources that are permitted with that token. Single Sign On is a feature that widely uses JWT nowadays.

What is the JSON Web Token structure?

 

How do JSON Web Token work?

Spring Security JWT Overview

 

JWT - Securing REST API's with JWT Token using Spring Security 6 & Spring Boot 3

1.1 Development Steps

  • Add JWT related Maven dependencies

  • Create JwtAuthenticationEntryPoint

  • Add Jwt properties in application properties file

  • Create JwtTokenProvider-Utility class

  • Create JwtAuthenticationFilter

  • Create JwtAuthResponse DTO

  • Configure JWT in spring Security

  • Change Login/Signin REST API to Return JWT Token

1.2 Implementation

1.2.1 Add JWT related Maven dependencies

<!--JWT related dependencies-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
​
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>
​
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>

1.2.2 Create JwtAuthenticationEntryPoint

It handles the exception that is thrown due to unauthorized user trying to access a resource that requires authentication.

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
​
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED,authException.getMessage());
​
    }
}

1.2.3 Add Jwt properties in application properties file

use SHA256 online encoder

//javaguides
app-jwt-secret=daf66e01593f61a15b857cf433aae03a005812b31234e149036bcc8dee755dbb
//7 days
app-jwt-expiration-milliseconds=604800000

1.2.4 Create JwtTokenProvider-Utility class

Generate and validate Jwt token.

@Component
public class JwtTokenProvider {
    @Value("${app-jwt-secret}")
    private String jwtSecret;
    @Value("${app-jwt-expiration-milliseconds}")
    private long jwtExpirationDate;
​
    //generate JWT token
    public String generateToken(Authentication authentication) {
        String username = authentication.getName();
        Date currentDate = new Date();
​
        Date expireDate = new Date(currentDate.getTime() + jwtExpirationDate);
​
        String token = Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(expireDate)
                .signWith(key())
                .compact();
​
        return token;
    }
​
    private Key key() {
        return Keys.hmacShaKeyFor(
                Decoders.BASE64.decode(jwtSecret)
        );
    }
​
    //get username from Jwt token
    public String getUsername(String token) {
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(key())
                .build()
                .parseClaimsJwt(token)
                .getBody();
​
        String username = claims.getSubject();
        return username;
    }
​
    //validate Jwt token
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder()
                    .setSigningKey(key())
                    .build()
                    .parse(token);
            return true;
        } catch (ExpiredJwtException e) {
            throw new BlogAPIException(HttpStatus.BAD_REQUEST,"Expired JWT token");
        } catch (MalformedJwtException e) {
            throw new BlogAPIException(HttpStatus.BAD_REQUEST,"Invalid JWT token");
        } catch (UnsupportedJwtException e) {
            throw new BlogAPIException(HttpStatus.BAD_REQUEST,"Unsupported JWT token");
        } catch (IllegalArgumentException e) {
            throw new BlogAPIException(HttpStatus.BAD_REQUEST,"JWT claims string is empty");
        }
    }
}

1.2.5 Create JwtAuthenticationFilter

JwtAuthenticationFilter authenticate the JWT token.

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private JwtTokenProvider jwtTokenProvider;
    private UserDetailsService userDetailsService;
​
    public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider, UserDetailsService userDetailsService) {
        this.jwtTokenProvider = jwtTokenProvider;
        this.userDetailsService = userDetailsService;
    }
​
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        //get JWT token from http request
        String token = getTokenFromRequest(request);
​
        //validate token
        if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)){
            //get username from token
            String username = jwtTokenProvider.getUsername(token);
            //load the user associated with token
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
​
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                    userDetails,
                    null,
                    userDetails.getAuthorities()
            );
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
​
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request,response);
    }
​
    private String getTokenFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
​
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7,bearerToken.length());
        }
​
        return null;
    }
}

1.2.6 Create JwtAuthResponse DTO

@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class JWTAuthResponse {
    private String accessToken;
    private String tokenType="Bearer ";
​
}

1.2.7 Configure JWT in spring Security

​
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
    private UserDetailsService userDetailsService;
​
    private JwtAuthenticationEntryPoint authenticationEntryPoint;
​
    private JwtAuthenticationFilter authenticationFilter;
​
    public SecurityConfig(UserDetailsService userDetailsService, JwtAuthenticationEntryPoint authenticationEntryPoint, JwtAuthenticationFilter authenticationFilter) {
        this.userDetailsService = userDetailsService;
        this.authenticationEntryPoint = authenticationEntryPoint;
        this.authenticationFilter = authenticationFilter;
    }
​
    @Bean
    public static PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
​
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }
​
    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeHttpRequests((authorize) ->
                        //authorize.anyRequest().authenticated())
                        authorize.requestMatchers(HttpMethod.GET, "/api/**").permitAll()
                                .requestMatchers("/api/auth/**").permitAll()
                                .anyRequest().authenticated()
                ).exceptionHandling(exception -> exception
                        .authenticationEntryPoint(authenticationEntryPoint)
                ).sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                );
​
        http.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class);
        
        return http.build();
    }
}

1.2.8 Change Login/Signin REST API to Return JWT Token

ServiceImpl
@Override
    public String login(LoginDto loginDto) {
        Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
                loginDto.getUsernameOrEmail(),
                loginDto.getPassword()
        ));
​
        SecurityContextHolder.getContext().setAuthentication(authentication);
​
        String token = jwtTokenProvider.generateToken(authentication);
        return token;
    }
​
Controller
  @PostMapping(value = {"/login", "/signin"})
    public ResponseEntity<JWTAuthResponse> login(@RequestBody LoginDto loginDto) {
        String token = authService.login(loginDto);
        JWTAuthResponse jwtAuthResponse = new JWTAuthResponse();
        jwtAuthResponse.setAccessToken(token);
        return ResponseEntity.ok(jwtAuthResponse);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值