【SpringBoot】1、SpringBoot整合JWT实现Token验证

1.单点登录

单点登录(Single Sign On),简称为SSO。简单来说,指在同一帐号平台下的多个应用系统中,用户只需登录一次,即可访问所有相互信任的系统。简而言之,多个系统,统一登陆。

实现单点登录(SSO)有几种常见的方式,其中一些主要方法包括:

  • 基于cookie + redis实现

1.1 基于cookie + redis + 网关实现

SpringBoot集成redis+cookie实现分布式单点登录
很早之前, 我们尝试过实现单系统(垂直架构单实例)中实现单点登录, 当时采用的方案是cookie + session。
在这里插入图片描述
使用session实现单点登录, 它不适用于服务拆分、多实例的场景. 使用Redis替代基于服务器端Session存储的方式, 可以解决多实例和服务拆分的问题, 并且能够提供更高的性能和可扩展性. Redis作为一种高性能的分布式内存数据库,有以下优势:

  1. 高性能: Redis是一种基于内存的数据库,读写性能非常高。与传统的基于磁盘的Session存储相比,基于Redis的存储能够提供更低的延迟和更高的吞吐量。
  2. 分布式支持: Redis支持分布式部署和数据复制,能够实现数据的高可用性和容错性。这意味着即使一个Redis节点失效,系统仍然可以继续运行。
  3. 持久化: Redis支持分布式部署和数据复制,能够实现数据的高可用性和容错性。这意味着即使一个Redis节点失效,系统仍然可以继续运行。

Redis非常适合用于存储会话信息和实现单点登录功能。通过使用Redis,你可以将用户的认证状态存储在分布式的内存数据库中,从而实现跨多个服务实例的会话共享和管理,提高系统的性能、可靠性和可扩展性。

1.1.1 实现原理

使用cookie + redis可以实现sso, 但在服务拆分的情况下, 需要在每个服务中都设置请求拦截器来判断请求携带的Token是否有效, 这样造成了代码的重复, 所以可以考虑引入一个专门的服务去处理身份认证, 所有的客户端请求都首先发送到身份认证服务。身份认证服务负责验证用户的身份,并根据需要转发请求到后端的服务。我们可以使用网关来实现此功能

  1. 登录成功后生成Token: 网关放行登录请求, 用户登录成功后, 后端生成一个唯一访问令牌(例如UUID), 即Token
  2. 将Token响应给浏览器: 将Token存在cookie中, 并cookie设置有效时间30分钟(模拟30分钟会话)
  3. Token存储在redis中: 将令牌和用户信息以key-value的形式存储在redis中, Token作为key, 用户信息为value, 并设置有效时间30分钟. 这样, 不同服务实例都可以通过Token来识别用户的身份。
  4. 网关统一认证: 所有服务的访问请求都经过网关, 在网关中设置拦截器进行统一认证和授权. 当用户发送请求到网关时, 网关会检查请求中携带的Cookie中是否包含认证令牌.
  5. 身份认证: 网关从cookie中提取认证令牌,然后在redis中验证令牌的有效性。如果令牌有效,则用户已经登录;否则,用户需要重新登录或者跳转到认证页面。
  6. 转发请求: 如果用户已经登录,网关会将请求转发到相应的服务,并将认证令牌一并传递给服务。服务可以通过令牌来获取用户的身份信息,以及进行其他权限验证等操作。
  7. 单点注销: 当用户注销登录时,网关需要通知所有服务将用户的认证令牌从Redis中删除,确保用户在所有服务中都需要重新登录。

1.3 Token机制

1.3.1 传统身份认证

HTTP是一种没有状态的协议,也就是它并不知道是谁在访问应用,这里我们把用户看成是客户端,客户端使用用户名还有密码通过了身份验证,不过下回这个客户端再发送请求时,还得再校验一次。这明显是不合适的,如果客户每次访问应用都需要登录验证、登录验证…客户体验会非常差

解决方式:当客户请求登录的时候,如果没有问题,我们在服务端生成一条记录,这个记录里可以说明一下登录的用户是谁,然后把这条记录的ID编号发给客户端,客户端收到以后将ID编号存储在Cookie中,下次这个用户再向服务端发送请求的时候,可以携带这个Cookie,这样服务端就会验证这个Cookie里的信息,看看能不能在服务端这里找到对应的记录,如果可以,说明用户已经通过了身份验证,就把用户请求的数据返回给客户端。

上面说的就是HTTP Session,我们需要在服务端存储为登录的用户生成的Session,这些Session可能会存储在内存、磁盘、或者数据库里。我们可能需要在服务端定期的去清理过期的Session

这种认证出现的问题是:

  • 内存开销问题:每次认证用户发起请求时,服务器需要去创建一个记录来存储信息。当越来越多的用户发请求时,内存的开销也会不断增加。
  • 可扩展性:在服务器的内存中使用Session存储用户信息,伴随而来的是可扩展性问题。

1.3.2 基于Token的身份认证

使用基于Token的身份认证方式, 在服务端不需要存储用户的登录记录, 大概的流程是这样的:

  1. 客户端使用用户名、密码请求登录
  2. 服务端收到请求, 验证用户名、密码
  3. 验证成功后, 服务端会签发一个Token, 再把这个Token发送给客户端
  4. 客户端收到Token以后可以把它存储起来, 比如放在Cookie里或者Local Storage里
  5. 客户端每次向服务端请求资源时携带服务端签发的Token
  6. 服务器收到请求, 然后去验证客户端请求里面带着的Token, 如果验证成功, 就向客户端返回请求的数据

使用Token的优势:

  • 无状态、可扩展
    在客户端存储的Tokens是无状态的, 并且能够被扩展. 基于这种无状态和不存储Session信息, 负载均衡器能够将用户信息从一个服务器传到其他服务器上
  • 安全性
    请求中发送Token而不再是发送Cookie能够防止CSRF(跨站请求伪造). 即使在客户端使用Cokkie存储Token, Cookie也仅仅是一个存储机制而不是用于认证, 不将信息存储在Session中, 让我们少了对Session的操作

1.4 JWT机制

JWT(JSON Web Token)

JWT是一种紧凑自包含的, 用于在多方传递JSON对象的技术. 传递的数据可以使用数字签名增加其安全性. 可以使用HMAC加密算法或RSA公钥/私钥加密方式

  • 紧凑:数据小, 可以通过URL, POST参数, 请求头发送. 且数据小代表传输速度快
  • 自包含:使用payload数据块记录用户必要且不隐私的数据, 可以有效的减少数据库访问次数, 提高代码能力

JWT是一般用于处理用户身份验证数据信息交换

  • 用户身份验证:一旦用户登录, 每个后续请求都将包含JWT, 允许用户访问该令牌允许的路由、服务和资源. 单点登录是当今广泛使用JWT的一项功能, 因为它的开销很小, 能够轻松的跨域使用
  • 数据信息交换:JWT是一种非常方便的多方传递数据的载体, 因为其可以使用数据签名来保证数据的有效性和安全性

1.4.1 JWT数据结构

JWT的数据结构是:A.B.C
由字符点"."来分隔三部分数据

  • A:header 头信息
  • B:payload 有效荷载
  • C:Signature 签名
1.4.1.1 header

数据结构:

{"alg":"加密算法名称", "typ": "JWT"}
  • alg是加密算法定义内容, 例如HMAC、SHA256、RSA
  • typ是token类型, 这里固定为JWT
1.4.1.2 payload

在payload数据块中一般用于记录实体(通常为用户信息)或其他数据的, 主要分为三个部分. 分别是

  • 已注册信息(registered claims).
  • 公开数据(public claims)
  • 私有数据(private claims)

已注册信息(最重要的数据), 包括: iss(发行者), exp(到期时间), sub(主题), aud(受众)等…
公开数据部分一般都会在JWT注册表中增加定义, 避免和已注册信息冲突
公开数据和私有数据可以由程序员任意定义

1.4.1.3 signature

签名信息, 这是一个由开发者提供的信息, 是服务器验证的传递的数据是否有效安全的标准, 在生成JWT最终数据之前, 先使用header中定义的加密算法, 将header和payload进行加密, 并使用点进行连接, 如:加密后的head.加密后的payload, 再使用相同的加密算法, 对加密后的数据和签名信息进行加密, 得到最终结果

1.4.2 JWT执行流程

在这里插入图片描述

1.客户端(browser)发起post请求, 请求的路径为/users/login,需要提供用户名和密码
2.服务端(server)对用户名和密码校验, 校验通过后, 会生成一个JWT的加密字符串(secret), 这个字符串就是token
3.把JWT返回给客户端浏览器(browser), 浏览器将JWT存储起来
4.后期, 客户端(browser)再发起请求, 需要在请求头里加上JWT(建议是将JWT放header中)
5.服务端(server)校验JWT, 判断是否登录

1.4.3 JWT代码案例

  • 18
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Spring Boot 集成 JWT 验证 Token 的步骤如下: 1. 引入相关依赖包 2. 实现自定义 JWT 验证过滤器 3. 在 Security 配置中配置 JWT 验证过滤器 4. 在需要验证的接口上添加 @PreAuthorize 注解 5. 处理 Token 相关操作(生成、刷新、验证等) 请注意,以上步骤仅是大致流程,具体实现可能会有所不同。如需细节请参考Spring Boot 官方文档或搜索相关教程。 ### 回答2: Spring Boot是一款非常方便的框架,可以快速搭建应用程序。同样的,JWT(JSON Web Token)也是一种非常流行的身份验证机制,使用标准的JSON格式,并且使用了数字签名方式验证身份。本文将介绍Spring Boot整合JWT验证Token。 首先,通过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> ``` 接下来创建JWTUtils类来处理Token的创建与验证: ``` @Component public class JWTUtils { // 密钥 private static final String SECRET = "mySecret"; // 过期时间毫秒 private static final long EXPIRATION_TIME = 1800000; // 创建token public static String createToken(String username, String role) { return Jwts.builder() .setSubject(username) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .claim("roles", role) .signWith(SignatureAlgorithm.HS512, SECRET) .compact(); } // 验证token public static boolean validateToken(String token) { try { Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token); return true; } catch (SignatureException | ExpiredJwtException e) { return false; } } // 获取用户名 public static String getUsernameFromToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody().getSubject(); } // 获取角色 public static String getRoleFromToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody().get("roles").toString(); } } ``` 其中,createToken方法用于创建Token,validateToken方法用于验证Token,getUsernameFromToken和getRoleFromToken用于获取Token中的信息。 接下来,在Spring Boot中配置WebSecurityConfig: ``` @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 跨域请求会先进行一次options请求 .antMatchers("/auth/**").permitAll() // 登录注册接口放行 .anyRequest().authenticated() .and() .addFilter(new JWTAuthenticationFilter(authenticationManager())) .addFilter(new JWTAuthorizationFilter(authenticationManager())) .headers().cacheControl(); } @Override public void configure(WebSecurity web) { web.ignoring().antMatchers( "/error", "/v2/api-docs", "/configuration/ui", "/swagger-resources/**", "/configuration/**", "/swagger-ui.html", "/webjars/**"); } @Autowired public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } } ``` 其中,configure方法配置了安全规则,addFilter方法添加了JWT的过滤器,configureAuthentication方法配置用户认证方式。 最后,在Controller中使用接口进行调用: ``` @RestController public class UserController { @Autowired private UserService userService; @PostMapping("/auth/register") public String register(@RequestBody UserDTO userDTO) { if (userService.register(userDTO)) { return "注册成功"; } else { return "注册失败"; } } @PostMapping("/auth/login") public ResponseEntity<TokenDTO> login(@RequestParam String username, @RequestParam String password) { String role = "ROLE_USER"; // 这里演示只有ROLE_USER角色 String token = JWTUtils.createToken(username, role); return ResponseEntity.ok(new TokenDTO(token)); } @GetMapping("/user/info") public String userInfo(Authentication authentication) { String username = authentication.getName(); String role = JWTUtils.getRoleFromToken(((JwtUserDetails) authentication.getPrincipal()).getToken()); return username + "-" + role; } } ``` 其中,login方法用于登录获取Token,userInfo方法用于获取当前用户信息。这里使用了Spring Security提供的authentication对象来获取用户信息。 综上,Spring Boot整合JWT验证Token并不复杂,通过相关依赖包、JWTUtils、WebSecurityConfig和Controller的配置,即可完成Token身份验证,保证接口的安全性。 ### 回答3: Spring Boot是一个Java开发框架,它可以减少Web应用程序的开发时间和复杂性。在开发Web应用程序时,安全性是一个关键问题,可以使用JWT(Json Web Token)来提高应用程序的安全性。 JWT是一种基于JSON的安全令牌,可以在用户与应用程序之间传递信息。JWT包含了许多有用的信息,例如用户的ID、角色、权限等等。在Spring Boot中,我们可以使用jwt.io上的库来生成和解码JWT令牌。 首先,在pom.xml文件中添加jwt库的依赖项,如下所示: ``` <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.2</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.2</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.2</version> <scope>runtime</scope> </dependency> ``` 接下来,我们需要创建一个JWTUtils工具类。该类将用于编码、解码和验证JWT令牌。实现代码如下: ``` import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.Date; @Component public class JWTUtils { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; public String generateToken(String username) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + expiration * 1000); return Jwts.builder() .setSubject(username) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public String getUsernameFromToken(String token) { Claims claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); return claims.getSubject(); } public boolean validateToken(String token, UserDetails userDetails) { String username = getUsernameFromToken(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } private boolean isTokenExpired(String token) { Date expiryDate = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody() .getExpiration(); return expiryDate.before(new Date()); } } ``` 在上面的代码中,我们使用@Value注释从application.properties文件中读取秘密和过期时间。generateToken方法是用于生成JWT令牌的方法。setSubject()定义了JWT令牌的主题,setIssuedAt()是JWT生成时间,setExpiration()是JWT令牌的超时时间。最后,我们使用signWith()进行签名和压缩。 getUsernameFromToken()方法获取JWT令牌中的用户名。validateToken()用于验证JWT令牌是否有效。isTokenExpired()方法检查JWT令牌是否已过期。 为了让Spring Boot知道我们在哪里可以找到JWTUtils类,我们需要在Application.java类中添加@ComponentScan注解,如下所示: ``` @SpringBootApplication @ComponentScan({"com.example.jwt"}) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 最后,我们在Spring Boot中创建一个Controller类。该类有一个login()方法,该方法接收用户名和密码并验证该用户是否存在。如果用户存在,则生成JWT令牌并将其返回给客户端。如下所示: ``` import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.web.bind.annotation.*; import com.example.jwt.JWTUtils; @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JWTUtils jwtUtils; @Autowired private UserDetailsService userDetailsService; @PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) throws AuthenticationException { try { authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); } catch (UsernameNotFoundException e) { throw new RuntimeException("User not found"); } final UserDetails userDetails = userDetailsService.loadUserByUsername(loginRequest.getUsername()); final String token = jwtUtils.generateToken(userDetails.getUsername()); return ResponseEntity.ok(new AuthResponse(token)); } } ``` 在上面的代码中,我们使用@Autowired注释从其他类中获取所需的依赖项。login()方法中,我们使用authenticationManager.authenticate()方法验证用户是否存在。如果该用户不存在,则会抛出一个异常。然后,我们使用userDetailsService.loadUserByUsername()方法加载用户详细信息。最后,我们调用JWTUtils.generateToken()方法生成JWT令牌。 在上面的代码中,我们还创建了一个名为AuthResponse的简单类,该类是返回的响应对象,包含JWT令牌。 ``` public class AuthResponse { private String token; public AuthResponse(String token) { this.token = token; } public String getToken() { return token; } public void setToken(String token) { this.token = token; } } ``` 以上就是Spring Boot整合JWT验证token的简单代码示例。该代码示例可以让开发者更加容易地理解Spring Boot如何使用JWT来增强应用程序的安全性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值