SpringBoot - 集成Auth0 JWT

前言

说说JWT,先说下互联网服务常见的两种用户认证方式:


session认证与Token认证

session认证

传统的Session认证的大体流程可以表示为用户提供用户名和密码登录后由服务器存储一份用户登录信息并传递给浏览器保存为Cookie,并在下次请求中根据Cookie来识别用户,但这种方式缺陷明显:

  • Session都是保存在内存中,随着认证用户的增多,服务端的开销明显增大
  • 保存在内存中的Session限制了分布式的应用
  • Cookie容易被截获伪造
Token认证

Token 泛指身份验证时使用的令牌,Token鉴权机制从某些角度而言与Cookie是一个作用,其目的是让后台知道请求是来自于受信的客户端,其通过实现了某种算法加密的Token字符串来完成鉴权工作,其优点在于:

  • 服务器不需要保存 Session 数据(无状态),容易实现扩展
  • 有效避免Cookie被截获引发的CSRF攻击
  • 可以存储一些业务逻辑所必要的非敏感信息
  • 便于传输,其构成非常简单,字节占用小

JWT简介

JWT定义
  • JWT全称为Json web token,也就是 Json 格式的 web token,可以这么理解:
Token // 个人证件 
JWT  // 个人身份证

JWT数据结构
  • JWT由三段字符串组成,中间用.分隔,如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsInNjb3BlIjo4LCJleHAiOjE3MTU3NDAyMjIsImlhdCI6MTYyOTM0MDIyMn0.wuRsF5wvLHbDF_21Pocas8SeXQ315rgBl6wm1LRL2bQ
  • JWT 的三个部分依次如下:
Header(头部)// Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。
Payload(负载)// Payload 部分是一个 JSON 对象,用来存放实际需要传递的数据
Signature(签名)// Signature 部分是对前两部分的签名,防止数据篡改
  • 第一段字符串Header:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9将其 Base64 解码后得到:
{
	"typ": "JWT", // TOKEN TYPE ,token类型
	"alg": "HS256"  //ALGORITHM,算法 哈希256
}
  • 第二段字符串Payload:eyJ1aWQiOjEsInNjb3BlIjo4LCJleHAiOjE3MTU3NDAyMjIsImlhdCI6MTYyOTM0MDIyMn0
PAYLOAD是数据载体,可以有自定义数据
{
  "uid": "1234567890" // 自定义数据
}
  • 第三段字符串Signature:wuRsF5wvLHbDF_21Pocas8SeXQ315rgBl6wm1LRL2bQ
Signature 部分是对前两部分的签名,防止数据篡改。

JWT的类库
  • Java 中的 JWT 有很多类库,关于其优缺点可以在官网查看:https://jwt.io/,这里我们介绍Auth0的JWT的集成使用方式
Auth0 实现的 com.auth0 / java-jwt / 3.3.0
Brian Campbell 实现的 org.bitbucket.b_c / jose4j / 0.6.3
connect2id 实现的 com.nimbusds / nimbus-jose-jwt / 5.7
Les Hazlewood 实现的 io.jsonwebtoken / jjwt / 0.9.0
FusionAuth 实现的 io.fusionauth / fusionauth-jwt / 3.1.0
Vert.x 实现的 io.vertx / vertx-auth-jwt / 3.5.1

具体实现

JWT配置

  • pom.xml
<!-- jwt -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.8.1</version>
</dependency>
  • application.yml
coisini:
  security:
    jwt-key: coisini
    # 过期时间
    token-expired-in: 86400000

JWT工具类

  • JwtUtil.java
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.*;

/**
 * @Description JWT工具类
 * @author coisini
 * @date Aug 18, 2021
 * @Version 1.0
 */
@Component
public class JwtUtil {

    /**
     * key
     */
    private static String jwtKey;

    /**
     * 过期时间
     */
    private static Integer expiredTimeIn;

    /**
     * JWT KEY
     * @param jwtKey
     */
    @Value("${coisini.security.jwt-key}")
    public void setJwtKey(String jwtKey) {
        JwtUtil.jwtKey = jwtKey;
    }

    /**
     * 过期时间
     * @param expiredTimeIn
     */
    @Value("${coisini.security.token-expired-in}")
    public void setExpiredTimeIn(Integer expiredTimeIn) {
        JwtUtil.expiredTimeIn = expiredTimeIn;
    }

    /**
     * 生成令牌
     * @param uid 用户id
     * @return
     */
    public static String makeToken(Long uid) {
        return JwtUtil.getToken(uid);
    }

    /**
     * 获取令牌
     * @param uid 用户id
     * @param scope 权限分级数字
     * @return
     */
    private static String getToken(Long uid) {
        // 指定算法
        Algorithm algorithm = Algorithm.HMAC256(JwtUtil.jwtKey);

        Map<String, Date> dateMap = JwtUtil.calculateExpiredIssues();

        /**
         * withClaim() 写入自定义数据
         * withExpiresAt() 设置过期时间
         * withIssuedAt() 设置当前时间
         * sign() 签名算法
         */
        return JWT.create()
                    .withClaim("uid", uid)
                    .withExpiresAt(dateMap.get("expiredTime"))
                    .withIssuedAt(dateMap.get("now"))
                    .sign(algorithm);
    }

    /**
     * 获取自定义数据
     * @param token
     * @return
     */
    public static Optional<Map<String, Claim>> getClaims(String token) {
        DecodedJWT decodedJWT;

        // 指定算法
        Algorithm algorithm = Algorithm.HMAC256(JwtUtil.jwtKey);
        JWTVerifier jwtVerifier = JWT.require(algorithm).build();

        try {
            decodedJWT = jwtVerifier.verify(token);
        } catch (JWTVerificationException e) {
            return Optional.empty();
        }

        return Optional.of(decodedJWT.getClaims());
    }

    /**
     * 验证Token
     * @param token
     * @return
     */
    public static boolean verifyToken(String token) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(JwtUtil.jwtKey);
            JWTVerifier jwtVerifier = JWT.require(algorithm).build();
            jwtVerifier.verify(token);
        } catch (JWTVerificationException e) {
            return false;
        }

        return true;
    }

    /**
     * 计算过期时间
     * @return
     */
    private static Map<String, Date> calculateExpiredIssues() {
        Map<String, Date> map = new HashMap<>();
        Calendar calendar = Calendar.getInstance();
        Date now = calendar.getTime();
        calendar.add(Calendar.SECOND, JwtUtil.expiredTimeIn);
        // 当前时间
        map.put("now", now);
        // 过期时间
        map.put("expiredTime", calendar.getTime());
        return map;
    }

}

测试接口

  • JwtController.java
@RestController
@RequestMapping("/jwt")
public class JwtController {

    /**
     * 获取Token
     * @param id
     * @return
     */
    @GetMapping(value = "/get")
    public String getToken(@RequestParam Long id) {
        return JwtUtil.makeToken(id);
    }

    /**
     * 验证Token
     * @param token
     * @return
     */
    @PostMapping("/verify")
    public Map<String, Boolean> verify(@RequestParam String token) {
        Map<String, Boolean> map = new HashMap<>();
        Boolean valid = JwtUtil.verifyToken(token);
        map.put("is_valid", valid);
        return map;
    }

}
  • 测试结果

在这里插入图片描述

在这里插入图片描述

  • JWT生成的Token应该放在请求头内来传输,后端统一拦截验证,这里留在下篇文章吧。。。

- End -
- 个人学习笔记 -
- 仅供参考 -

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,下面是一个简单的 Spring Boot 集成 Spring Security 和 JWT 的示例: 1. 添加依赖 在项目的 pom.xml 文件中添加以下依赖: ``` <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> ``` 2. 配置 Spring Security 在 Spring Boot 项目中,可以通过配置类来配置 Spring Security。创建一个继承自 WebSecurityConfigurerAdapter 的配置类,并重写 configure 方法: ``` @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyUserDetailsService userDetailsService; @Autowired private JwtRequestFilter jwtRequestFilter; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.csrf().disable() .authorizeRequests().antMatchers("/authenticate").permitAll() .anyRequest().authenticated().and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); } } ``` 上述代码中,我们配置了一个用户详细信息服务 MyUserDetailsService,并将其注入到 AuthenticationManagerBuilder 中。此外,我们还配置了一个 BCryptPasswordEncoder,用于加密用户的密码。 我们还配置了一个 HttpSecurity 对象,用于定义 Spring Security 的策略。在这里,我们允许任何人访问 /authenticate 接口,并要求其他接口进行身份验证。我们还配置了一个 JwtRequestFilter,用于检查 JWT 是否有效,并将其添加到过滤器链中。 3. 实现用户详细信息服务 我们需要实现一个用户详细信息服务 MyUserDetailsService,用于从数据库中获取用户信息。创建一个实现了 UserDetailsService 接口的类,并重写 loadUserByUsername 方法: ``` @Service public class MyUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Optional<User> userOptional = userRepository.findByUsername(username); userOptional.orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username)); return userOptional.map(MyUserDetails::new).get(); } } ``` 上述代码中,我们使用 UserRepository 来从数据库中获取用户信息,并将其转换为 UserDetails 对象。 4. 实现 JWT 工具类 我们需要实现一个 JWT 工具类 JwtUtil,用于生成和验证 JWT。创建一个实现了 Serializable 接口的类 JwtUtil: ``` @Component public class JwtUtil implements Serializable { private static final long serialVersionUID = -2550185165626007488L; public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60; @Value("${jwt.secret}") private String secret; public String getUsernameFromToken(String token) { return getClaimFromToken(token, Claims::getSubject); } public Date getExpirationDateFromToken(String token) { return getClaimFromToken(token, Claims::getExpiration); } public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) { final Claims claims = getAllClaimsFromToken(token); return claimsResolver.apply(claims); } private Claims getAllClaimsFromToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } private Boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return doGenerateToken(claims, userDetails.getUsername()); } private String doGenerateToken(Map<String, Object> 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(); } public Boolean validateToken(String token, UserDetails userDetails) { final String username = getUsernameFromToken(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } } ``` 上述代码中,我们使用了 JJWT 库来生成和验证 JWT。我们定义了一些方法来获取 JWT 中的信息,以及生成和验证 JWT。 5. 实现 JWT 请求过滤器 我们需要实现一个 JWT 请求过滤器 JwtRequestFilter,用于检查 JWT 是否有效。创建一个实现了 OncePerRequestFilter 接口的类 JwtRequestFilter: ``` @Component public class JwtRequestFilter extends OncePerRequestFilter { @Autowired private MyUserDetailsService userDetailsService; @Autowired private JwtUtil jwtUtil; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { final String authorizationHeader = request.getHeader("Authorization"); String username = null; String jwt = null; if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { jwt = authorizationHeader.substring(7); username = jwtUtil.getUsernameFromToken(jwt); } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtUtil.validateToken(jwt, userDetails)) { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); usernamePasswordAuthenticationToken .setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } } chain.doFilter(request, response); } } ``` 上述代码中,我们使用了 OncePerRequestFilter 接口来实现一个请求过滤器。在 doFilterInternal 方法中,我们检查 Authorization 头是否存在,并从中获取 JWT。如果 JWT 有效,则将其添加到 Spring Security 的上下文中。 6. 创建控制器 最后,我们创建一个控制器 FileController,用于处理文件的上传和下载请求。创建一个加了 @RestController 注解的类 FileController: ``` @RestController public class FileController { @Autowired private JwtUtil jwtUtil; @PostMapping("/upload") public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file, @RequestHeader("Authorization") String authorizationHeader) { String jwt = authorizationHeader.substring(7); String username = jwtUtil.getUsernameFromToken(jwt); // TODO: 上传文件的逻辑 return ResponseEntity.ok("File uploaded successfully."); } @GetMapping("/download") public void downloadFile(HttpServletResponse response, @RequestHeader("Authorization") String authorizationHeader) { String jwt = authorizationHeader.substring(7); String username = jwtUtil.getUsernameFromToken(jwt); // TODO: 下载文件的逻辑 } } ``` 上述代码中,我们创建了两个接口,一个用于上传文件,一个用于下载文件。在这里,我们检查 Authorization 头是否存在,并从中获取 JWT。如果 JWT 有效,则继续执行相应的逻辑。 这就是一个简单的 Spring Boot 集成 Spring Security 和 JWT 的示例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Maggieq8324

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值